PG사 결제모듈 도입하기 feat.카카오페이
결제모듈 도입하기는 상용 서비스 웹 개발자라면 뗄레야 뗄 수가 없는 개발 소요이다. 이걸 할 수 있어야 돈을 벌 수 있거든 이 개발자에게 피와 살과 돈이 되는 덕목을 자세히 한번 알아보자.
카카오페이의 단순성(과 안전성)
일단 카카오페이를 찬양하면서 시작해야 한다. 우리나라에는 반올림해서 100개의 PG(Payment Gateway)사가 있고, 쉽게 접하지 못하는 솔루션을 제외하더라도 유명한 솔루션만 10가지 정도가 된다. 그런데 그 중에서도 카카오페이는 구현이 압도적으로 쉬운 편이다.
카카오톡 앱을 대한민국 사람 누구나 깔아서 쓰고 있다.
따라서 카카오톡 앱을 통해서 실질적 결제 프로세스를 진행시키면 우리나라가 그토록 신뢰하는 "휴대폰 인증"만큼의 안전성이 확보되면서 보안상 중요한 부분, 노출되지 않는 로직이 필요한 부분은 몽땅 카카오톡 앱으로 넘겨버릴 수 있다.
즉 개발자는 보안에 신경을 안 쓰고 굿이나 보고 떡이나 먹으면 된다.
예를 들어보자. 토스페이먼츠 결제모듈의 경우에는 결제창을 사용자 디바이스에 그대로 띄운다.
이런 창이 내 디바이스에 뜬다는 말이다
그러면 항상 조심해야 할 것은 이 결제창에서 들어오고 나가는 데이터가 가로채지지 않도록 (Intercept), 혹은 변조되지 않도록 (Counterfeit) 보안 솔루션을 구축해야 하고, 이 때문에 결제모듈은 특정 방식으로만 띄울 수 있게 하며 앱의 경우에는 App Sign을 도입해서 앱 설치파일이 변조되었는지 해시 인증서 체킹으로 검증을 한다.
그럼에도 불구하고 변조를 100% 막을 방법은 없는 것으로 알고 있다.ㅠㅠ 힘들게 할 뿐임.
그런데 카카오페이는 무엇이 혁명적이냐? 바로 실질적 결제 프로세스가 전부 "사용자 핸드폰 위의 카카오페이/카카오톡 앱 안"에서 일어난다는 점이다. 보안상으로 기가 맥힌 이득이고, 개발자 입장에서도 할 일이 없어진다.
카카오페이 기본 결제 로직
복잡해 보이지만 이것만 기억하자. "로그인이랑 똑같은데, 1. 인증과정 하나가 더 붙었고 2. Client-Server-PG사 서버 대신에 중간 Server역할을 핸드폰이 수행한다."
먼저 결제요청을 보내면, 카카오페이 서버에서 redirect URL과 함께 결제코드를 발급한다.
URL을 받으면 컴퓨터는 QR을 띄우고 그걸 폰으로 촬영해서 카카오페이 앱으로 가고, 폰은 카카오페이 앱을 바로 띄운다.
앱에서 인증이 완료되면 해당 결제코드가 "승인" 된다. 그러면 진짜 결제가 완료되는 Flow.
진짜 구현하기
한번 코드를 보자. 이건 Graduart에 실제 적용된 코드이다. 좀 더러움 주의
const paymentResponse = await fetch('https://open-api.kakaopay.com/online/v1/payment/ready', {
method: 'POST',
headers: {
Authorization : `SECRET_KEY ${Deno.env.get('KAKAO_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
cid: Deno.env.get('KAKAO_CID'),
partner_order_id: orderId,
partner_user_id: user_id,
item_name: itemNames,
item_code: itemCodes,
quantity: item_ids.length,
total_amount: totalPrice,
tax_free_amount: totalPrice,
approval_url: `${Deno.env.get('FRONT_URL')}/purchaseApprove?oid=${orderId}`, //
fail_url: `${Deno.env.get('FRONT_URL')}/purchaseFail`,
cancel_url: `${Deno.env.get('FRONT_URL')}/purchaseFail`,
}),
});
요렇게 구성되어 있다. 참 쉽죠? 그냥 카카오 서버에 토큰과 파라미터만 맞게 넘겨주면 알아서 응답이 잘 온다.
요청을 잘 보냈으면 이제 승인도 해야 한다.
const approvalRequest = await fetch('https://open-api.kakaopay.com/online/v1/payment/approve', {
method: 'POST',
headers: {
Authorization : `SECRET_KEY ${Deno.env.get('KAKAO_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
cid: Deno.env.get('KAKAO_CID'),
tid: tid,
partner_order_id: oid,
partner_user_id: user_id,
pg_token: pgToken
}),
});
이것도 잘 보내면 되는데, 특이사항으로 tid, oid, user_id, pgToken등등의 파라미터가 있다. 이건 최대한 유저에게 노출이 안 될수록 좋다. (변조 방지) 특히 cid와 tid는 정말 위험하다. 그래서 중간에 프록시 서버를 껴서 구현한다던지, 세션 기반으로 코드를 매치한다던지, Cloud Function (AWS Lambda 등)을 이용해서라도 숨겨야 한다.