자동화 slecs

결제 콜백 자동수신으로 수동 입금 매칭 루프 제거

목차

결제대행사에서 보내는 입금 결과를 사람이 하루 한 번 손으로 매칭하던 구조였다. 대시보드 열고, 거래 목록 내려받고, 주문 테이블이랑 하나씩 비교하는 반복 작업. 평소엔 그냥 굴러갔는데 누락 건이 하나 생기면 타이밍이 묘하게 나빴다. 정산 마감 직전에 발견되면 새벽에 다시 들어와 메우는 일이 몇 번 반복됐다. 어떤 날은 자정 넘어서 VPN 붙이고 있었다.

자동화하기로 결정한 건 사실 오래된 숙제였는데, 결제대행사 쪽 신규 API가 안정화됐다는 확인이 들어오면서 타이밍이 맞았다. 이번에 콜백을 받는 엔드포인트를 붙여서 그 수동 루프를 끊었다.

엔드포인트 설계에서 챙긴 것들

외부에서 HTTP로 날아오는 결제 콜백은 내부 API와 결이 다르다. 호출자를 우리가 통제할 수 없고, 네트워크 오류로 같은 요청이 두 번 들어오는 게 표준 동작이다. 대행사 입장에서는 200 응답을 받기 전까지 계속 재시도한다. 설계 초반에 이 전제를 잡고 들어가지 않으면 나중에 중복 입금 반영이 생기고, 그걸 수습하는 비용이 자동화 이득을 다 먹어버린다.

챙긴 포인트는 세 가지였다.

  • 멱등성: 같은 거래번호로 두 번, 세 번 들어와도 반영은 한 번만. 요청이 들어오면 멱등키부터 조회하고, 이미 처리된 건이면 기존 결과를 그대로 돌려준다.
  • 시그니처 검증: 외부망에서 바로 인입되는 트래픽이라 위변조 차단을 1차에 걸었다. 검증 실패는 422가 아니라 401로 내렸다. 422는 "요청은 정상인데 처리 못 한다"는 의미라 보안 거절이랑 맞지 않는다.
  • 원본 페이로드 보관: 들어온 JSON을 파싱 결과가 아니라 원문 그대로 저장한다. 분쟁이 났을 때 우리 파싱 로직을 끼고 설명하는 게 아니라 원본을 들고 회의 들어갈 수 있어야 한다.

흐름을 단순하게 그리면 이렇다.

요청 수신
 → 시그니처 검증
     └ 실패 → 401 반환
 → 원본 페이로드 저장
 → 멱등키 조회
     ├ hit  → 200 OK (기존 처리 결과 반환)
     └ miss → 결과 반영 → 멱등키 저장 → 200 OK

멱등키는 거래번호를 그대로 쓰지 않고 {provider}:{txid} 형태로 네임스페이스를 붙였다. 나중에 대행사를 추가하거나 교체할 때 같은 거래번호 충돌이 생기는 걸 미리 막는 것이다. 실제로 이런 상황이 올지 모르겠지만, 키 설계를 바꾸는 건 데이터가 쌓인 뒤엔 비용이 커서 처음에 여유 있게 잡아두는 편이 낫다.

에러 처리도 의도적으로 단순하게 뒀다. 반영 로직에서 예외가 터지면 500을 내리는데, 대행사가 이걸 받으면 자동 재시도한다. 멱등성이 있으니 재시도는 안전하고, 우리 쪽 장애가 해소되면 자연히 처리된다. 알림 큐를 따로 두는 방식도 고려했는데, 이 구조에선 오버엔지니어링이라 판단했다.

스타일 수정이 같은 커밋에 들어간 이유

콜백을 받기 시작하면 파트너 화면에 노출되는 상태 뱃지 종류가 늘어난다. "대기", "처리중", "완료", "실패" 정도의 구분인데, 기존 CSS가 두 파일에서 색을 따로 정의하고 있었다. 한쪽은 #555, 다른 쪽은 #999. 같은 "처리중" 상태인데 페이지를 이동하면 색이 달라 보이는 상황이었다.

PR을 쪼갤지 잠깐 고민했다. 원칙적으로는 API 변경과 스타일 수정은 별도 PR이 맞는다. 그런데 이번엔 같은 사용자 시나리오 안에서 발생하는 문제였고, 리뷰어 입장에서 "API 붙이고 그 결과가 어디 보이는지"를 한 흐름으로 따라가는 게 맥락이 분명하다고 봤다. 스타일 파일만 따로 나가면 왜 이 시점에 이 색을 바꿨는지 히스토리에서 파악하기 어려워진다.

다만 이번엔 색 값을 직접 통일하는 방식으로 처리했다. 본래는 디자인 토큰으로 추출해서 CSS 변수로 관리하는 게 맞는데, 그건 범위가 더 넓어지기 때문에 이번 커밋 목적에서 벗어난다. 일단 값을 맞추고, 토큰화는 다음 번 UI 정리 때 하기로 했다.

회고

잘한 것 아쉬운 것
멱등성을 초기 설계에 박음 테스트가 정상 흐름 위주, 재시도 시나리오 부족
원본 페이로드 통째 적재 시그니처 키가 단일 키, 로테이션 없음
API와 UI 일관성을 한 커밋에 묶음 CSS를 토큰화 않고 값만 맞춤
네임스페이스 있는 멱등키 설계 에러 알림 채널 따로 없음

시그니처 키 로테이션은 다음에 꼭 짚어야 할 지점이다. 지금은 키가 한 개고, 만료나 교체 절차가 없다. 외부 콜백을 받는 엔드포인트가 늘어나면 이 약점이 공유된다. 로테이션 자체가 복잡한 기능은 아닌데, 나중에 키를 바꿔야 하는 상황이 오면 엔드포인트를 잠깐 다운시키거나 대행사 쪽과 전환 시간을 조율해야 해서 운영 비용이 생긴다. 설계를 조금 더 일찍 잡아뒀어야 했다는 생각이 남는다.

테스트 커버리지도 정상 흐름 위주로 짰다. 멱등키가 이미 있을 때, 시그니처가 깨졌을 때, 대행사가 같은 요청을 연속으로 보낼 때 같은 엣지 케이스는 수동으로 한 번씩 확인했는데 자동화된 케이스로는 남겨두지 못했다. 콜백 엔드포인트는 특성상 실제 트래픽이 들어오기 전까지 문제가 안 보이는 구조라, 재시도 시나리오를 테스트로 못 박아두지 않은 게 지금은 살짝 불안하다. 다음 스프린트 때 보완할 생각이다.

댓글 0

첫 댓글 달아줘.