2021년의 결제대행 API 연동을 2026년의 질문으로 다시 보기
2021년 여름, 더파이러츠에서 넉 달 동안 백엔드 개발자로 일했다. 맡은 일 중 하나가 제휴 상점의 자동 결제 시스템을 만들고 결제 대행업체 API를 연동하는 것이었다. 이력서에는 오래 “PG 모듈 연동”이라고 적혀 있었는데, 정확한 표현은 아니다. PG사와 직접 계약하고 연동한 것이 아니라, API 호출만으로 결제를 처리할 수 있는 중간 결제대행업체(효성FMS)의 API를 호출하는 수준이었다.
이 글은 그 작업을 자랑하는 글이 아니다. 5년 뒤 ParityPay에서 결제 시스템의 실패 경로를 하나씩 실험하고 나서 그때를 돌아보니, 당시의 내 지도에 없던 질문이 몇 개 보였다. 그 질문들을 적어두는 것이 목적이다.
그때 내 지도에 있던 것
2021년의 나에게 결제 연동은 이런 일이었다.
- 대행사 API 문서를 읽고 요청 파라미터를 맞춘다.
- 응답 코드를 보고 성공이면 성공으로, 실패면 실패로 저장한다.
- 자동 결제니까 스케줄러가 정해진 시각에 대상 상점을 순회하며 호출한다.
- 실패한 건은 로그를 남기고, 운영자가 확인한다.
이 목록은 틀리지 않았다. 다만 “정상 요청이 정상 응답을 받는 경우”만 다루고 있다. 그때는 그것이 결제 연동의 전부라고 생각했고, 실제로 그 범위에서는 동작했다.
지금 내 지도에 있는 것
ParityPay를 만들면서 결제 시스템에 던지게 된 질문은 다섯 개다. 각각에 대해 2021년의 시스템이 어떻게 답하고 있었는지를 적어야 하는데, 솔직하게 말하면 대부분 기억나지 않는다. 그리고 기억나지 않는다는 사실 자체가 답이다. 알고 처리했다면 기억에 남았을 것이다. 몰라서 묻지 않은 것은 기억에 남지 않는다.
1. 타임아웃은 성공인가 실패인가
대행사 API를 호출했는데 응답이 안 왔다. 그때의 코드는 아마 예외를 잡아 실패로 기록했을 것이다. 그게 자연스러운 코드다. 그런데 대행사가 결제를 처리한 뒤에 응답만 유실됐다면, 우리는 실패로 기록하고 고객 카드에서는 돈이 빠져 있다. 다음 스케줄에 다시 시도하면 두 번 빠진다.
ParityPay에서는 이 상태를 UNKNOWN으로 보존하고 조회로 확정한다. 2021년에는 “타임아웃이면 어느 쪽인가”라는 질문 자체가 없었다. 대행사에 상태 조회 API가 있었는지, 있었다면 썼는지 기억나지 않는다.
2. 같은 결제를 두 번 보내면 대행사는 어떻게 하는가
스케줄러가 어떤 이유로 같은 상점을 두 번 순회하면, 또는 서버가 두 대로 늘어나 같은 스케줄이 두 번 돌면, 같은 결제 요청이 두 번 나간다. 대행사가 요청에 실린 주문번호로 중복을 걸러주는지, 아니면 두 번 결제하는지는 대행사 API 계약에 달려 있다.
ParityPay에서는 이것을 우리 쪽 멱등 키로 막는다. 요청마다 키를 부여하고, 같은 키의 재시도는 최초 결과를 돌려준다. 2021년에는 외부에 보내는 요청에 우리가 만든 멱등 키가 있었는지, 대행사가 그것을 존중했는지 확인하지 않았다. 스케줄러가 한 대였으니 문제가 나지 않았을 뿐이다.
3. 우리 기록과 대행사 기록이 다르면 누가 아는가
결제가 성공했다고 우리가 기록했는데 대행사 쪽에는 없거나, 반대로 대행사에는 있는데 우리 쪽에 없는 경우가 있을 수 있다. 1번의 타임아웃이 정확히 이 상황을 만든다. 이걸 발견하려면 주기적으로 양쪽 기록을 대조해야 한다.
ParityPay에는 대사(reconciliation) 작업이 있고, 기관 명세를 받지 못한 날은 대사를 멈춘다. 2021년에는 대사가 없었거나, 있었다면 운영자가 대행사 관리 화면과 우리 DB를 눈으로 비교했을 것이다. 이것도 기억나지 않는다.
4. 결제 결과는 어디에 기록되는가
성공하면 “결제 완료” 플래그를 세우고 금액을 저장했을 것이다. 취소가 생기면 그 행을 고쳤거나 취소 플래그를 추가로 세웠을 것이다. 나중에 “이 상점의 이번 달 결제 총액이 왜 이 숫자인가”를 설명해야 할 때, 플래그와 금액 컬럼만으로는 어렵다.
ParityPay는 모든 금융 효과를 이중부기 원장에 기록하고, 확정 원장은 수정하지 않는다. 취소는 반대 분개다. 이 구조에서는 어떤 시점의 잔액도 원장에서 재구축할 수 있다. 2021년에는 “나중에 설명할 수 있는가”를 묻지 않았다.
5. 자동 결제는 실패하면 어떻게 되는가
정기 자동 결제에서 특정 상점의 결제가 실패했을 때, 다음 주기에 다시 시도하는지, 몇 번까지 시도하는지, 계속 실패하면 상점 상태를 바꾸는지가 정책으로 정해져 있어야 한다. 그리고 그 재시도가 1번, 2번과 만나면 이중 결제 경로가 된다.
이것은 기술이 아니라 정책의 문제이고, 2021년에는 아마 “실패하면 로그 남기고 운영자가 본다”가 정책이었을 것이다. 넉 달짜리 초기 구축에서 그 이상을 정하기 어려웠을 수도 있다.
그때 알았다면
다섯 질문을 2021년에 알았다면 무엇이 달라졌을까. 시스템 전체를 다르게 만들지는 못했을 것이다. 넉 달이었고, 결제는 맡은 일 중 하나였다. 하지만 세 가지는 할 수 있었다.
- 대행사 API 문서에서 상태 조회 API와 중복 요청 처리 규칙을 먼저 찾았을 것이다. 있으면 쓰고, 없으면 그 사실을 문서에 남겼을 것이다.
- 타임아웃을 실패로 저장하지 않고 “확인 필요” 상태로 저장했을 것이다. 상태 하나를 추가하는 것은 큰 작업이 아니다.
- 운영자가 대행사 화면과 우리 기록을 대조하는 절차를 최소한 문서로 적었을 것이다. 자동 대사는 못 만들어도 “누가 언제 무엇을 비교하는가”는 정할 수 있다.
왜 이 글을 쓰는가
이력서에 “결제 대행업체 API 연동”이 한 줄 있으면, 결제 도메인 회사의 면접관은 “타임아웃은 어떻게 처리했나”를 물을 것이다. 그 질문에 “그때는 그 질문을 몰랐다”라고 답하는 것이 “기억나지 않는다”보다 정확하고, “이렇게 했다”고 지어내는 것보다 낫다.
그리고 그 답 뒤에 이어질 말이 있어야 한다. 그 질문을 언제 알게 됐고, 알고 나서 무엇을 만들어봤는지. ParityPay는 그 뒷말이다. 2021년의 나는 정상 경로를 만들 줄 알았고, 2026년의 나는 정상 경로가 전체의 절반이라는 것을 안다. 나머지 절반은 응답이 사라지고, 프로세스가 죽고, 같은 요청이 두 번 오는 경로이고, 결제 시스템의 정확성은 그 절반에서 결정된다.
댓글
아직 댓글이 없습니다