[시스템 디자인] 11. 결제 시스템
[시스템 디자인] 11. 결제 시스템
시스템 디자인 시리즈의 글입니다.
금전적 가치의 이전을 통해 금융거래를 정산하는데 사용되는 모든 시스템
0. 내설계
- 일단 무조건 메세지큐는 써야할것같음. (비동기처리해서 부하방지. retry)
- DB는 무조건 RDB사용 요기요
- 동기식 구조로 되어있음
- 부분취소는 너무 어렵다..
- 디지털지갑이 PG(=PSP)쓰는것보다 훨씬 이득이다 추가키워드
- ochestration saga pattern (중재자가 있음)
- choreography saga pattern
1. 문제 이해
기능 요구사항
- 대금 수신 (pay-in) : 결제 시스템이 고객으로부터 대금을 수령
- 대금 정산 (pay-out) : 결제 시스템이 판매자에게 판매 대금을 송금
비기능 요구사항
- 신뢰성
- 내결함성
- 내부시스템(결제/회계)과 외부 서비스(결제서비스제공업체) 간의 조정 프로세스
개략적인 규모 추정
- 하루 100만건(1M) 거래
- 10\^6 / 10\^ 5 = 10 TPS
- 처리 대역폭보다는 정확한 처리에 초점을 맞춰야함
2. 개략적 설계
대금 수신 흐름
구성요소
- 결제 서비스 (payment service)
- 사용자로부터 결제 이벤트를 수락하고 결제 프로세스를 조율
- AML/CFT 규정 준수
- 위험점검을 통과한 결제만 처리
- 결제 실행자 (payment executor)
- PSP를 통해 결제주문 하나를 실행
- 결제 서비스 공급자 (payment service provider)
- A계정에서 B계정으로 돈을 옮기는 역할
- 카드 유형
- 신용카드 업무를 처리하는 조직
- visa, mastercard..
- 원장 (ledger)
- 결제 트랜잭션에 대한 금융 기록
- 결제 후분석에서 매우 중요한 역할
- 총 수익 계산
- 향후 수익 예측 등..
- 지갑 (wallet)
- 판매자의 계정 잔액 기록
- 특정 사용자가 결제한 총 금액 기록 플로우 1. 주문하기누름 2. 결제이벤트생성 3. 결제서비스 이벤트 DB 저장 4. 결제실행자 결제주문을 DB저장 5. 결제실행자 PSP 호출 6. 결제서비스 지갑 갱신 7. 지갑서버 잔고 DB저장 8. 지갑서비스 원장 호출 9. 원장서비스 원장 DB저장
결제 서비스 API 설계
- POST /v1/payments
- 결제이밴트 실행
- 하나의 결제이벤트에 여러 결제 주문이 포함될수있음
- pyment_order_id = globally unique
- amount는 double이 아닌 string 이어야함. (직렬화/ 역직렬화시 정밀도차이로인한 오류방지)
- GET /v1/payments/{:id}
- payment_order_id가 가리키는 단일 결제 주문의 실행상태 반환
결제 서비스 데이터 모델
테이블
- 결제 이벤트
- 결제주문 저장소 솔루션 고려할점
- 안정성 검증
- 모니터링 및 데이터탐사(ingestigation)
- DBA 채용시장의 성숙도
- 성능은 최우선 고려사항은 아님. 안정성이 최우선.
- ⇒ 전통적인 RDB 가 선호됨 데이터모델
- 결제이벤트 테이블 : 결제이벤트정보 저장
- 결제주문 테이블 : 주문의 실행상태 저장
복식부기 원장 시스템
- 결제시스템 필수요소.
- 정확한 기록을 남기는 핵심적 역할
- 한쪽에서는 차감기록 / 한쪽에서는 입금기록
- 모든 거래 항목의 합계는 0
- 자금의 흐름을 추적 가능.
- 결제주기 전반에 걸쳐 일관성 유지 가능
외부 결제 페이지
- 신용 카드 정부를 내부에 저장하려면 복잡한 규정 준수
- 귀찮으면 그냥 PSP에서 제공하는 외부 widget이나 iframe 사용..
대금 정산 흐름
- 대금 수신 흐름과 유사
- 타사 정산서비스를 사용하여 플랫폼 계좌에서 판매자 계좌로 돈을 입금
3. 상세 설계
PSP 연동
연동방법
- 회사가 민감한 결제정보를 안전하게 저장할 수 있다면 , API 통해 PSP와 연동
- 없다면 PSP가 제공하는 외부결제페이지 이용. PSP가 보안처리는 다 해놓음.
플로우
- 결제버튼클릭
- 결제서비스는 결제등록요청을 PSP에 요청. 이때 멱등성키(주문키)로 UUID활용
- PSP는 토큰(UUID)반환.
- 결제서비스는 DB에 토큰저장
- 외부 결제 페이지 표시
- 토큰을 사용하여 결제요청
- PSP 결제 상태 반환
- 사용자 리다이렉트
- 비동기적으로 PSP 결제상태를 결제서비스에 연동 (webhook, polling..)
조정
- 비동기적 통신의 경우 응답 반환에대한 보장이 없음
- 정확성 보장을 위해 ‘조정’ 프로세스가 필요
- PSP, 은행은 고객에게 정산(settlement)파일 전송
- 조정시스템은 정산파일을 읽어 원장시스템과 비교
- 내부 일관성을 확인 불일치 해결방안
- 어떤유형문제인지 알고 해결방법 자동화 가능 : 자동화하면됨
- 어떤문제인지 아는데 자동화 불가능 : 작업큐에넣고 재무팅 수동작업
- 어떤문제인지 모름 : 특별작업큐에 넣고 재무팀 조사
결제 지연 처리
여러 이유로 결제가 지연될수있음
- PSP가 결제 위험성을 감지하고 담당자 검토를 요구
- 신용카드사가 구매확인용도로 카드소유자의 추가정보 요구 PSP는 다음과 같이 처리
- 결제 pending상태를 클라에게 반환
- 클라는 사용자에게 상태표시
- 결제상태가 변경되면 polling이나 webhook으로 결제서비스에 알림
내부 서비스 간 커뮤니케이션
동기식통신
- 성능저하
- 장애 격리 곤란
- 높은 결합도
- 낮은 확장성 ⇒ 버려야됨 비동기 통신
- 단일수신자 : 한 메세지를 하나의 수신자가 처리
- 다중수신자 : 한 메세지를 여러 수신자가 처리 ⇒ 결제서비스에서는 동일한 메세지로 여러작업을 해야하기때문에 비동기 + 다중수신자 구성이 적합함
결제 실패 처리
- 결제 상태 추적 : 실패시 재시도 / 환불이 필요한지 결정이 필요
- 재시도 큐 : 일반적 오류같은 재시도는 재시도큐에 보관
- 실패 메세지 큐 : 반복적으로 실패하면 여기로 옴. 메세지를 디버깅하고 격리하여 검사함 (=dead leter queue)
정확히 한 번 전달 {toggle=”true”}

결제시스템에서는 매우매우 중요한 이슈.
- 최소 한 번은 실행
- 최대 한 번 실행 ⇒ 두 요건이 충족되면 exactly once 를 보장할수잇다
보증하는 방법
- 재시도
- 실패시 재시도를 통해 최소 한 번은 실행되도록함
- 재시도 간격을 정해야하는데, 일반적으로 exponential backoff(지수적) 사용
- 멱등성
- 최대 한번을 위한 핵심 개념
- 고유한값을 멱등키로 사용하여 동일 키가 들어와도 한번만 처리하고 동일한 결과를 리턴하도록함.
- https://docs.tosspayments.com/blog/what-is-idempotency
일관성
결제실행시 상태정보 유지관리하는 여러 서비스가 호출됨 이때 서비스간 통신 실패로 데이터 불일치가 발생하지 않도록 여러 기법을 사용
- 멱등성
- 조정프로세스
- 데이터 다중화 : 복제지연이 있을수있음.
결제 보안
매우 중요..
- 요청/응답 도청 : https사용
- 데이터 변조 : 암호화 / 무결성 강화 모니터링
- 중간자 공격 : 인증서 고정 + ssl사용
- 데이터 손실 : 데이터 복제 및 스냅샷
- 카드도난 : 토큰화. 실제 카드번호가 아닌 토큰을 사용해 저장하고 결제에 활용
- PCI규정준수 : 정보보안 표준 준수
- 사기주소확인, 카드확인번호, 사용자 행동분석 등..
4. 마무리
다뤄볼만한 주제들
- 모니터링
- 경보
- 디버깅도구
- 환율
- 현금결제
- 구글/애플페이 연동..