[Django] 내가 생각한 비즈니스 모델에 카카오페이 API 적용해보기 | Part 2. 결제 준비부터 승인까지(feat. 3가지 콜백 처리하기)

Chanjong Park
Chan’s Programming Diary
10 min readNov 14, 2021

저번 포스트에서 카카오페이의 결제 흐름과 필요한 정보들을 담는 테이블을 만들어 보았다.

전체적인 흐름이 어느정도 파악이 되었다면(사실 준비와 승인밖에 없지만) 이제 필자가 적용한 코드를 같이 살펴보도록 하자.

살펴보기에 앞서, 이해를 돕기 위해 프로젝트에 구현된 카카오페이 외적의 경우도 곁들여 설명할 수도 있다.

그러므로 카카오페이의 공식문서(이해하기)를 보고 이해가 되었다면, 설명을 스킵하고 아래 코드만 참고하는 것을 추천한다.

KakakoPayClient

Callback API

준비요청 API

프로젝트 등록

먼저 카카오페이 API를 사용하기 위해서는 Admin 키를 필요로 하는데, 개발자 홈페이지에서 프로젝트를 등록을 해야한다.

APP_ADMIN_KEY를 얻으려면 프로젝트 등록을 해야한다.

등록 자체에는 제한이 없고 정말 쉬우니 방법은 생략하도록 하겠다. 생성한 프로젝트에 접근하면 바로 앱 키들을 확인할 수 있는데, 각 플랫폼마다 필요한 키를 사용해서 카카오 플랫폼의 다양한 API들을 사용할 수 있다.

우리는 그중에 Admin 키를 가져와서 서버에 저장하도록 하자.

위의 헤더에 있던 APP_ADMIN_KEY와 Admin키가 동일하다.

Admin 키를 서버에 저장해두기 전에, 무조건 따로 보관해서 관리해야 하는 사실을 잊지 말자.

설정 변수 분리, json 파일로 분리 등 다양한 방법과 레퍼런스가 있기 때문에 참고해서 프로젝트를 공개할 경우에는 꼭 별도로 분리해야 한다.(필자는 json 파일로 분리해서 관리한다)

이제 개발자 문서에서 소개하는 결제 프로세스를 프로젝트에 적용한 코드와 함께 단계별로 살펴보겠다.

(필자가 소개하는 프로세스는 모바일 환경에서 진행됨을 유의하자)

Step 1. 결제 준비 단계

102번의 유저 9번의 포스트를 구매하는 시나리오를 예로 들어보겠다.

필자는 KakaoPayClient라는 Class를 만들어 준비(ready), 취소(cancel), 승인(approve) 로직을 수행하는 코드를 내부 메소드로 각각 구현해서 한꺼번에 관리했다.

아래는 준비 단계를 호출하는 API며, URL Parameter로 UserPost의 pk를 전달받아 KakaoPayClient의 ready 메소드를 호출한다.

아래의 API와 연결된 URL http://domain.com/user-post/9/payment-ready 이 실행된다.

https://github.com/TourKakao-Carping/Carping-Backend/blob/main/posts/views.py#L851

결제준비 API를 호출하는 실제 로직은 KakayPayClient 오브젝트에 구현되어있기 때문에 부가설명 없이 바로 넘어가도록 하겠다. 실제 구현된 메소드를 살펴보자.

단계별로 풀어보자면

- #31 포스트에 책정되어 있는 가격과 포스트 PK, 유저 PK를 연결하여 히스토리 레코드 생성- #37 ~ #53 상품정보를 포함하여 카카오페이 결제준비 API에 요청- #54 ~ #71 200을 받게 되면, 히스토리 레코드에 tid(결제 고유번호) 및 승인시간 저장
및 필요정보 응답 (TID는 전달시에 보안상 꼭 빼줘야함!)
* 200을 못받게 되거나 레코드 생성 시 오류가 발생 -> except 처리되면서 테이블 Rollback

여기서 중요한 점은, #31 ~ #71까지의 과정이 일괄적으로 이루어져야 한다. 즉 #53까지 성공했지만 #54단계에서 실패했다면 실패 전에 생성된 테이블도 없었던 일(Rollback)로 되어야 한다.

이러한 다수의 액션(쿼리)을 하나의 단위로 처리하기 위해 사용되는 것이 Tranasction이며, 위와 같이 일괄적으로 적용(Commit)되거나 없었던 일(Rollback)로 되는 특징을 원자성(Atomicity)라고 한다.

Django에서는 django.db.transactions에서with transaction.atomic() 을 사용하여 적용할 수 있다.

상품정보를 포함할 때 성공(approval), 취소(cancel), 실패(fail)에 대한 Callback URL을 설정해 줬는데, 각 Callback에서 히스토리 레코드의 PK를 전달하여 각 결제에 관한 정보를 인식하도록 했다. 이에 관한 내용은 아래에서 설명하겠다. ready 메소드가 전부 문제없이 실행되었다면, APIView에서 True를 전달받아 아래와 같은 필요 정보들을 리턴할 것이다.

tms_result는 카카오톡 결제 메세지 관련 값인 것 같은데, 자세히는 모르겠다.

DB에도 기록되었는지 체크도 한번 해보자.

왼쪽부터 TID, vat_amount(세금), user_id, userpost_id, ready_requested_at(결제 준비 요청시간)이다.

완료가 문제없이 잘 끝났다면, 전달받은 URL, Scheme으로 모바일/웹에서 결제수단 선택 창으로 Redirect될 것이다.

STEP 2–1. 콜백 함수 작성 및 결제 승인

테스트 결제이기 때문에 비밀번호를 입력하거나, 생체인증을 하는 과정이 따로 없다. 실제 제휴코드를 받아 진행하게 되면 결제 단계에 인증 플로우가 추가될 것임을 알아두자.

사용자가 결제 수단을 선택하고 테스트 결제하기 버튼을 누르면, 결제승인 API를 호출하게 되어 최종적으로 결제 완료 처리를 하는 단계이다.

이말인 즉슨, 결제하기 버튼을 누르면 카카오 서버에서 알아서 처리해 주는 것이 아닌 서버에서 승인 API를 따로 호출해야 하는 것이다. 이때 필요한 것이 상품 정보를 작성할 때 같이 작성한 Callback URL이다.

위에서 작성했던 Callback URL이다.

각 Callback URL에서는 서버에서 저장된 TID(결제 고유번호)를 사용해 해당 결제를 처리해야 하기 때문에, 어떻게 TID를 전달해 줄지 고민을 좀 했었던 것 같다. 그래서 저렇게 각 TID가 저장되어 있는 결제요청 테이블의 레코드를 처음 요청할 때 전달해 주고, 아래와 같이 URL Parameter를 함께 선언하여 콜백 함수에서도 인식할 수 있게 하였다.

주의해야 할 점은, Callback url에서 요청하는 도메인이 프로젝트에서 등록된 웹 플랫폼과 일치해야 한다는 점이다. 이 프로젝트에서는 테스트 하는 것이니, localhost:8000로 설정하였다.

성공 URL과 연결되어 있는 View를 살펴보자.

성공 Callback 함수에서 중요한 것은, 결제완료 시에 pg_token이란 값을 query parameter로 전달해준다. pg_token결제 요청을 하기위한 인증 토큰이며, 결제인증 API 호출 시에 넣어줘야 하는 값이기 때문에 꼭 체크해줘야 한다.

전달받은 결제요청 레코드 pkpg_tokenKakaoPayClientapprove 메소드에 전달하게 된다.

approve 메소드에서 결제승인 API도 호출되고, 결제요청 레코드의 상태값이 업데이트되며 결제승인 테이블의 레코드 또한 생성된다. 아래 코드를 살펴보자.

- #23 ~ #34 요청에 필요한 값들을 저장하며 결제요청 API 호출
- #36 ~ #67
요청이 잘 왔을 경우, 결제 관련 정보를 가져와서 결제승인 레코드 생성
결제요청 레코드의 status(성공, 실패, 취소) 업데이트
유저의 접근권한을 획득하기 위해 user와 userpost M:N
레코드 생성

결제가 완료되면 카카오톡 알림이 가고, 앱 내에서는 사용자가 권한을 획득하여 포스트를 볼 수 있게 된다!

STEP 2–2. 결제 취소 및 실패

사용자가 결제 도중 취소를 하거나, 예기치 않은 상황으로 결제 실패가 될 수 있다. 그럴 때에는 카카오 서버에서 위에서 설정했던 cancel_url, fail_url로 호출된다.

실패 시에는 간단하게 결제 요청 레코드의 status를 실패인 상태로 바꾸어 주었다.

취소 시에는 KakaoPayClient에 선언된 cancel 메소드를 사용했는데, 카카오 서버에서 주문 조회 API를 요청하여 TID의 상태를 조회하고 status를 취소인 상태로 바꾸어 주는 메소드이다.

카카오 공식 문서에서 말하길, 취소 시에는 보안상 주문 상세조회 API를 호출하여 상태를 확인하고 결제를 중단해야 한다고 한다.

조회 API가 정상적으로 호출되면, 응답값으로 오는 status의 상태가 QUIT_PAYMENT 인지를 체크하여 결제 요청 레코드의 status값을 변경한다.

사실 취소, 에러 시에는 크게 대응할 것이 없기 때문에 코드가 상대적으로 작지만, 여기까지 왔으면 끝이다. 필자가 생각지도 못한 Exception 처리가 있을지도 모르니, 감안하고 봤으면 좋겠다.

실제 결제모듈을 연결하는 것은 이번이 처음이기 때문에, 어떤 식으로 테이블을 설계하고 전달할지 고민이 많이 되었다.

카카오 개발자문서가 잘 되어있긴 하지만, 실제 DB와 연결하여 진행하는 튜토리얼 같은 경우는 찾을 수 없었기 때문에 필자가 한번 작성해 보았다.

사용하는 Framework가 Django가 아니여도, 필요한 테이블이나 플로우를 파악하는 것에서는 크게 문제가 없을 것이다.

글이 생각보다 많았고 회사도 다니면서 작성하느라 계속 작성기간이 늘어졌다(사실 핑계). 글 개연성이 많이 부족할지도 모르지만, 도움이 되었으면 좋겠다.

사실 무엇보다 좋았던 것은 내가 원하는 플로우에 맞춰서 결제기능을 추가한 것이다. 실제 앱 내에서 잘 작동되었던 것을 보니 뿌듯하고 재밌었다.

--

--