Cheondi
개발 · API

결제 요청 하나가 다섯 저장소를 지나갔다

결제 기능을 따라가며 계약·외부 연동·업무 API·사용자 화면·운영 화면의 책임을 연결한 기록입니다.

  • #payment
  • #api
  • #architecture

결제 수단 하나를 추가하는 작업은 사용자 화면에 버튼과 입력창을 붙이는 일처럼 시작했다. 코드를 따라가 보니 공통 계약, 외부 결제 연결, 내부 거래 생성, 사용자 화면, 운영 화면까지 다섯 저장소가 같은 거래를 서로 다른 관점으로 다뤘다. 한 저장소만 보고 끝내면 상태나 필드가 중간에서 끊겼다.

실제 회사명과 공급자명, 주소, 테이블은 공개 글에서 모두 빼고 역할만 남겼다.

요청이 지나가는 경로를 먼저 그렸다

구현 전에 거래 한 건의 흐름을 일반적인 역할로 나눴다.

client
 -> service API: 사용자·계좌·금액 검증, 내부 거래 생성
 -> gateway: 외부 요청 서명과 공급자 호출
 -> provider: 결제 처리와 callback
 -> service API: 내부 상태 정규화
 -> admin/client: 결과 조회와 후속 행동

contract repo: 위 계층이 공유하는 요청·응답 타입

어느 계층이 외부 비밀키를 가지고 어느 계층이 사용자 권한을 확인하는지도 함께 표시했다.

입금과 출금을 같은 흐름으로 보지 않았다

둘 다 돈이 움직이지만 필요한 입력, 승인 주체, 상태 전이가 달랐다. 입금은 사용자 요청 뒤 외부 완료 콜백을 기다릴 수 있고, 출금은 운영 승인과 거절·재시도 정책이 필요할 수 있었다.

입금 예시: Created -> PendingProvider -> Completed | Failed
출금 예시: Requested -> Reviewing -> Processing -> Completed | Rejected

실제 상태 이름을 복사하지 않고 개념만 일반화했다. 공급자 상태를 내부 상태와 바로 동일시하지 않았다.

화면 필드 하나도 끝까지 따라갔다

사용자가 입력한 값이 클라이언트 검증만 통과하고 서버에서 다시 확인되지 않으면 직접 API 호출로 우회될 수 있었다. 요청 DTO, 서버 검증, 외부 payload, 운영 상세 표시까지 같은 의미가 이어지는지 봤다.

공급자 도메인과 실제 계정 값 대신 https://api.example.com/payment과 테스트용 구조를 사용했다. 비밀키, 서명 원문, 실제 거래 데이터는 기록하지 않았다.

저장소 경계가 책임을 설명했다

처음에는 저장소가 많아서 복잡하다고만 느꼈다. 경로를 그리고 나니 외부 통신, 내부 거래 규칙, 사용자 경험, 운영 증거가 분리된 이유가 보였다.

이 경험 뒤 여러 저장소를 수정하는 기능은 파일 목록부터 만들지 않는다. 한 요청이 어디서 만들어지고 검증되고 저장되고 관찰되는지를 먼저 추적한다. 구현 범위는 그 흐름에서 끊긴 계약을 따라 자연스럽게 정해졌다.