대규모 시스템 설계 기초 2 (11장)
결제 시스템
기능 요구사항은 다음과 같다.
- 대금 수신 흐름 : 결제 시스템이 판매자를 대신하여 고객으로부터 대금을 수령한다.
- 대금 정산 흐름 : 결제 시스템이 전 세계의 판매자에게 제품 판매 대금을 송금한다.
대금 수신 시스템 구성 요소
결제 서비스
결제 서비스는 사용자로부터 결제 이벤트를 수락하고 결제 프로세스를 조율한다.
결제 실행자
결제 실행자는 결제 서비스 공급자를 통해 결제 주문 하나를 실행한다.
결제 서비스 공급자
결제 서비스 공급자(PSP)는 A 계정에서 B 계정으로 돈을 옮기는 역할을 담당한다.
원장
원장은 결제 트랜잭션에 대한 금융 기록으로, 결제 후 분석을 위해서 사용한다.
지갑
지갑에는 판매자의 계정 잔액을 기록할 것이다.
결제 흐름
- 사용자가 주문하기 버튼을 클릭하면, 결제 이벤트가 결제 서비스로 전송된다.
- 결제 서비스는 결제 이벤트를 데이터베이스에 저장한다.
- 단일 결제 이벤트에 여러 결제 주문이 포함될 수 있는데, 실제 서비스는 결제 주문마다 결제 실행자를 호출한다.
- 결제 실행자는 결제 주문을 DB 에 저장한다.
- 결제 실행자는 외부 PSP 를 호출하여 결제를 처리한다.
- PSP 에 의해서 성공적으로 결제가 처리되면, 결제 서비스는 지갑을 갱신하여 판매자의 잔고를 기록한다.
- 지갑 서버는 갱신된 잔고를 DB 에 저장한다.
- 지갑 서비스가 판매자 잔고를 갱신하면, 결재 서비스는 원장을 호출한다.
- 원장 서비스는 새 원장 정보를 DB 에 추가한다.
API 설계 시, 주의 사항
- 프로토콜, 소프트웨어, 하드웨어에 따라 직렬화/역직렬화에 사용하는 숫자 정밀도가 다를 수 있다. 따라서 숫자는 문자열로 보관되어야 한다.
- 일반적으로 결재 시스템에서는 ACID 트랜잭션을 지원하는 전통적인 RDB 를 선호한다.
원장 시스템
원장 시스템에는 복식부가리는 원칙이 있는데, 쉽게 말해서 모든 거래 기록을 남기고, 거래 항목의 합계는 0 이어야 한다는 것이다. 자금의 흐름을 추적하여 결제 주기 전반의 일관성을 보장할 수 있다.
외부 결제 페이지
대부분의 경우는 신용 카드 정보를 직접 받아서 저장해서 처리하지 않는다. 이는 복잡한 법적 규정과 관련이 있기 때문이다. 따라서 PSP 에서 제공하는 외부 신용 카드 페이지를 사용한다.
(PSP 가 포함된) 전체 결제 프로세스
- 사용자가 클라이언트에서 결제 버튼을 클릭하고, 결제 주문 정보가 결제 서비스로 전송된다.
- 결제 서비스는 결제 등록 요청을 PSP 로 전송한다. 이 요청에는 결제 금액, 통화, 만료일, 리디렉션 URL 등의 정보가 포함되며, 결제 주문을 정확히 한 번 수행하기 위한 UUID 필드가 있다.
- PSP 는 결제 서비스에 토큰을 반환하는데, 토큰은 등록된 결제 정보를 유일하게 식별할 수 있는 UUID 이다. 이 토큰을 사용하면 결제 등록 및 결제 실행 상태를 확인할 수 있다.
- 결제 서비스는 PSP 가 제공하는 외부 결제 페이지를 호출하기 전에 토큰을 DB 에 저장한다.
- 토큰을 저장하면, 클라이언트는 PSP 가 제공하는 외부 결제 페이지를 표시한다. 이 페이지는 토큰을 필요로 하며, PSP 의 페이지는 토큰을 사용하여 결제 요청에 대한 정보를 확인한다.
- 사용자가 PSP 의 웹 페이지에 결제 새부 정보를 입력하면 PSP 가 결제 처리를 시작한다.
- PSP 가 결제 상태를 반환한다.
- 사용자는 리디렉션 URL 이 가리키는 웹 페이지로 보내진다.
- 비동기적으로 PSP 는 웹훅을 통해 결제 상태와 함께 결제 서비스를 호출한다. 결제 서비스는 결제 처리 결과를 받아서, 주문 및 결제의 상태를 업데이트한다.
조정
결제 시스템의 구성 요소가 비동기 시스템으로 통신하기에 항상 응답이 반환된다는 보장이 없다. 정확성을 보장하기 위해서 조정 프로세스를 사용한다.
조정 프로세스 상으로, 매일 밤에 PSP 나 은행은 고객(여기서는 상거래 운영자)에게 정산 파일을 보낸다. 정산 파일에는 하루 동안 발생한 모든 거래 내역이 있다. 조정 시스템은 파일의 세부 정보를 읽어서 자체 보유한 원장과 비교한다.
결제 지연 처리
PSP 는 결제의 진행 상황을 추적하고, 상태가 바뀌면 웹훅을 통해서 결제 서비스에 알린다. 결제 서비스는 내부 시스템에 기록된 정보를 적절하게 업데이트하며, 결제가 완료되면 고객에게 상품을 배송한다.
결제 실패 처리
모든 결제 시스템은 실패한 결제를 적절히 처리할 수 있어야 한다.
결제 상태 추적
결제 주기의 모든 단계를 정확히 추적해야 한다. 실패가 일어나면 재시도 또는 환불을 수행해야 한다.
재시도 큐 및 실패 메시지 큐
- 재시도 큐 : 일시적 오류 같은 재시도 가능 오류는 재시도 큐에 보낸다.
- 실패 매시지 큐 : 반복적으로 처리에 실패한 메시지는 결국에 실패 메시지 큐로 보낸다.
정확히 한 번 전달
수학적으로, 다음의 요건이 충족된다면 정확히 한 번 전달된다고 여겨진다.
- 최소 한 번 실행
- 최대 한 번 실행
재시도를 통해 최소 한 번 실행을 달성할 수 있고, 멱등성 검사를 통해 최대 한 번 실행을 보증할 수 있다.
재시도
재시도 메커니즘을 통해서 결재 거래를 다시 시도할 수 있다. 재시도 메커니즘을 도입할 때는 간격을 정하는 것이 중요하다. 즉시 재시도, 고정 간격, 증분 간격, 지수적 백오프, 취소 방법이 있다.
상황에 따라 적절한 재시도 전략을 선택해야 한다. 네트워크 문제를 고려하면 일반적으로 지수적 백오프를 통해서 컴퓨팅 자원을 아끼는 것이 좋을 것이다.
멱등성
API 관점에서 보자면 멱등성은 클라이언트가 같은 API 호출을 여러 번 반복해도 항상 동일한 결과가 나온다는 뜻이다. UUID 를 멱등 키로 사용하자. 결제 주문의 결제 ID 를 db 의 PK 로 설정함으로서 멱등성을 보장할 수 있을 것이다.