Architecture(2) - 결제 시스템 설계



결제 시스템은 금전적 가치 이전을 통해 금융 거래를 정산하는 시스템이다.


1. 문제 이해 및 설계 범위 확정

결제 시스템에 대한 생각은 사람마다 다를 수 있다.
애플페이나 구글페이처럼 디지털 지갑(Digital Wallet)을 생각할 수도 있고, 페이팔(Paypal)이나 스트라이프(Stripe)같은 결제 처리 백엔드 시스템을 생각할 수도 있다.
따라서 정확한 요구 사항을 파악해야 한다.

  • 시스템 유형
    • Q: 어떤 결제 시스템을 만들어야 하는가?
    • A: 아마존닷컴과 같은 전자상거래 애플리케이션을 위한 결제 백엔드이다. 고객이 주문을 진행할 때 발생하는 모든 돈의 흐름을 처리한다.
  • 결제 수단
    • Q: 어떤 결제 방법을 지원해야 하는가?
    • A: 실생활의 다양한 옵션 중 신용카드 결제만 처리 대상으로 제한한다.
  • 결제 처리 주체
    • Q: 신용카드 결제 처리를 직접 수행해야 하는가?
    • A: 직접 처리하지 않으며, Stripe, 브레인트리(Braintree), 스퀘어(Square) 같은 전문 결제 서비스 공급자(PSP, Payment Service Provider)를 활용한다.
  • 카드 정보 저장
    • Q: 신용카드 데이터를 내부 시스템에 직접 저장해야 하는가?
    • A: 저장하지 않는다. 보안 규정(PCI DSS 등) 준수 요건이 매우 까다로우므로 민감한 카드 데이터는 결제 처리 업체에 의존한다.
  • 통화 및 지역
    • Q: 전 세계 대상의 다양한 통화 및 국제 결제를 지원해야 하는가?
    • A: 여기서는 하나의 통화만 사용한다고 가정한다.
  • 트랜잭션 규모
    • Q: 하루에 몇 건의 결제가 이루어지는가?
    • A: 하루 100만 건의 거래가 발생한다.
  • 대금 정산
    • Q: 판매자에 대한 대금 지급(정산) 절차를 지원해야 하는가?
    • A: 그렇다. 매월 판매자에게 대금을 지급하는 정산 절차를 지원해야 한다.
  • 장애 대응 및 상태 관리
    • Q: 연동 시 주의할 점은 무엇인가?
    • A: 결제 시스템은 다양한 내부 서비스(계정, 분석 등) 및 외부 PSP와 연동하므로 한 곳에 장애가 발생하면 상태가 불일치할 수 있다. 이를 바로잡기 위한 조정(Reconciliation) 프로세스가 필수적이다.

1.1. 기능 및 비기능 요구사항

  • 기능 요구사항
    • 대금 수신(Pay-in) 흐름
      • 결제 시스템이 판매자를 대신하여 고객으로부터 대금을 수령한다.
    • 대금 정산(Pay-out) 흐름
      • 결제 시스템이 전 세계 판매자에게 제품 판매 대금을 송금한다.
  • 비기능 요구사항
    • 신뢰성 및 내결함성
      • 네트워크 오류 및 결제 실패 상황을 신중하고 안전하게 처리해야 한다.
    • 조정(Reconciliation) 프로세스
      • 내부 서비스(결제, 회계)와 외부 서비스(PSP) 간 결제 데이터가 일치하는지 비동기적으로 검증하고 교정한다.

1.2. 개략적인 규모 추정

  • 일일 트랜잭션
    • 하루 1,000,000건
  • 평균 TPS 계산
    • 하루 = \(86,400\text{초} \approx 10^5\text{초}\)
    • \[\text{평균 TPS} = \frac{1,000,000 \text{건}}{100,000 \text{초}} = 10 \text{ TPS}\]
  • 설계 초점
    • 10 TPS는 일반적인 단일 RDBMS로도 충분히 처리 가능한 트랜잭션 양이다.
    • 따라서 여기서는 처리 대역폭 확장보다는 결제 트랜잭션의 정확한 처리, 데이터 일관성 보장, 오류 복구 메커니즘에 초점을 맞춘다.

2. 개략적 설계안

결제 시스템 아키텍처는 자금의 실제 이동 방향에 따라 크게 대금 수신(Pay-in)과 대금 정산(Pay-out)이라는 두 가지 핵심 프로세스로 나뉜다.

전자상거래 플랫폼(예: 아마존)을 예로 들면, 구매자가 결제를 완료했을 때 자금이 플랫폼의 법인 계좌로 입금되는 과정이 대금 수신 흐름이다.
플랫폼은 이 자금을 즉시 소유하는 것이 아니라 수수료를 징수하는 에스크로(자금 관리자) 역할을 수행한다.
이후 상품 배송이 완료되는 등 정산 조건이 충족되면, 플랫폼 계좌에 보관되어 있는 자금에서 수수료를 차감한 후 판매자의 실제 은행 계좌로 송금하는 과정이 대금 정산 흐름이다.

간소화된 수신 및 정산 흐름


2.1. 대금 수신 흐름

대금 수신 흐름은 고객의 결제 요청부터 내부 장부 기록까지 다수의 내부 서비스와 외부 금융망이 유기적으로 연동되는 구조를 가진다.

아래는 대금 수신의 개략적인 흐름이다.

대금 수신 흐름


주요 구성 요소

  • 결제 서비스(Payment Service)
  • 결제 실행자(Payment Executor)
    • 단일 결제 주문 건을 받아 외부 PSP를 통해 실질적인 결제를 수행한다.
    • 하나의 결제 이벤트에는 여러 결제 주문이 포함될 수 있다.
  • 결제 서비스 공급자(PSP, Payment Service Provider)
    • 구매자의 카드 계좌에서 대금을 인출하여 플랫폼 계좌로 이체하는 금융 중개 서비스를 제공한다.
    • 예: Stripe, PayPal
  • 카드 유형
    • 비자(VISA), 마스터카드(MasterCard) 등 신용카드 거래 정산을 처리하는 결제 네트워크 조직이다.
  • 원장(Ledger)
    • 결제 트랜잭션에 대한 최종 재무 기록을 남기는 불변(Immutable) 시스템이다.
  • 지갑(Wallet)
    • 플랫폼 내 판매자별(Merchant) 가용 잔액 상태를 관리한다.

대금 수신 처리 순서

  • ① 구매자가 ‘주문하기’ 버튼을 클릭하면 결제 이벤트가 생성되어 결제 서비스로 전달된다.
  • ② 결제 서비스는 결제 이벤트를 자체 데이터베이스에 저장한다.
    • 결제 서비스 DB
      • 결제 이벤트 원본을 그대로 저장한다.
      • 전체 거래를 식별하는 checkout_id(PK), 구매자 ID(buyer_info), 판매자 ID 목록(seller_info), 토큰화된 카드 식별자(credit_card_info), 결제 완료 여부(is_payment_done), 생성 시각 등이 저장된다.
  • ③ 하나의 결제 이벤트는 여러 판매자의 상품이 포함될 수 있으므로, 결제 서비스는 이를 단일 결제 주문들로 분할하고 각 주문마다 결제 실행자를 호출한다.
  • ④ 결제 실행자는 결제 주문 정보를 자체 데이터베이스에 저장한다.
    • 결제 실행자 DB
      • 개별 주문 단위의 실행 상태를 기록한다.
      • payment_order_id(PK), checkout_id(FK), 승인 금액(amount), 통화(currency), 결제 진행 상태(payment_order_status), PSP에 전달할 멱등키 등이 저장된다.
  • ⑤ 결제 실행자가 외부 PSP API를 호출하여 신용카드 결제를 요청한다.
  • ⑥ PSP 결제가 성공적으로 완료되면 결제 서비스는 지갑 서비스를 호출하여 판매자의 잔액을 적립한다.
  • ⑦ 지갑 서비스는 갱신된 잔액 정보를 데이터베이스에 저장한다.
    • 지갑 서비스 DB
      • 판매자의 계정 잔액 변동 트랜잭션을 기록한다.
      • 판매자 계정 ID(seller_account), 변동 금액, 거래 직후 누적 가용 잔액, 연관 거래 ID(payment_order_id), 변경 시각 등이 저장된다.
  • ⑧ 지갑 적립이 완료되면 결제 서비스는 원장 서비스를 호출한다.
  • ⑨ 원장 서비스는 거래 내역을 복식부기 원장 데이터베이스에 기록한다.
    • 원장 서비스 DB
      • 재무제표의 기초가 되는 회계 차/대변 엔트리를 기록한다.
      • 원장 기록 ID(ledger_entry_id), payment_order_id, 차변 계정 ID, 대변 계정 ID, 금액, 계인 시각 등 수정 불가능한 형태로 저장된다.

계인 시각

거래 내역이 원장에 최종 기입되어 수정 불가능한 상태로 확정/기록된 시각을 의미함

  • 결제/주문 시각
    • 사용자가 구매 버튼을 누르거나 결제를 요청한 시점
  • 계인 시각
    • 원장 시스템에 ledger_entry_id와 함께 차변/대변 금액 계산이 끝나고 거래 내역이 장부에 ‘도장이 찍히듯’ 최종 저장을 완료한 시점

2.2. 결제 서비스 API

POST /v1/payments

해당 엔드포인트는 클라이언트의 결제 이벤트를 수신하여 처리한다.

Request Body

필드설명자료형
buyer_info구매자 식별 정보json
checkout_id결제 이벤트를 식별하는 전역 고유 IDstring
credit_card_info암호화된 신용 카드 정보 또는 PSP 전용 결제 토큰json
payment_orders이벤트 내 포함된 하위 결제 주문 목록list

payment_orders 배열 내부 개별 주문 객체 구조:

필드설명자료형
seller_account대금을 수령할 판매자 계정 식별자string
amount주문 결제 금액string
currency결제 통화 코드 (ISO 4217)string
payment_order_id주문을 식별하는 전역 고유 ID (PSP 멱등키로 활용)string

<설계 핵심 포인트>

  • payment_order_id의 멱등키 활용
    • 결제 실행자가 타사 PSP에 결제를 요청할 때 이 ID를 멱등키(Idempotency Key)로 전달하여 중복 결제를 방지한다.
  • amount 필드의 문자열(string) 처리 이유
    • 부동 소수점(double, float)은 프로토콜, 하드웨어, 프로그래밍 언어 간 직렬화/역직렬화 과정에서 반올림 오차를 발생시킨다.
    • 자금 단위의 정밀도를 보장하기 위해 전송 및 저장 시에는 문자열로 다루고, 연산이 필요한 시점에만 고정소수점(Fixed-point) 객체로 변환한다.

2.3. 결제 서비스 데이터 모델

결제 시스템의 DB 선정 기준은 TPS보다 ACID 트랜잭션의 엄격한 보장, 모니터링 도구의 풍부함, 숙련된 DBA 인력 확보 수월성이다.
따라서 NoSQL이나 NewSQL 보다는 수년간 검증된 전통적인 RDBMS(PostgreSQL, MySQL 등)를 선호한다.


💡NewSQL 의 개념과 RDBMS 선호 이유는?

NewSQL은 NoSQL의 수평 확장성과 RDBMS의 ACID 트랜잭션 보장 특성을 모두 갖춘 분산 관계형 데이터베이스이다.
예) CockroachDB, YugabyteDB, TiDB 등

그럼에도 일반 RDBMS를 선호하는 이유는 본 시스템의 예상 트랜잭션 스케일이 10 TPS 수준이기 때문이다.
단일 RDBMS 인스턴스로도 여유 있게 처리가 가능한 수치이므로, 분산 노드 간 합의 알고리즘(Raft/Paxos) 오버헤드가 존재하는 NewSQL을 굳이 도입하기보다 운영 및 검증이 완벽히 끝난 RDBMS를 선택하는 것이 시스템의 복잡성과 리스크를 낮추는 길이다.


데이터베이스 스키마 설계

결제 이벤트 서비스(Payment Event)

컬럼명자료형제약조건 / 설명
checkout_idstringPK, 결제 이벤트 고유 식별자
buyer_infostring구매자 정보
seller_infostring판매자 정보
credit_card_infostring카드사/PSP 토큰 정보
is_payment_doneboolean이벤트 하위 주문의 전체 완료 여부

결제 주문 테이블(Payment Order)

컬럼명자료형제약조건 / 설명
payment_order_idstringPK, 결제 주문 고유 식별자
buyer_accountstring구매자 계정
amountstring결제 금액
currencystring통화 단위
checkout_idstringFK, 상위 결제 이벤트 ID
payment_order_statusstring주문 처리 상태
ledger_updatedboolean원장 반영 완료 여부
wallet_updatedboolean지갑 반영 완료 여부

<상태 변경 및 트랜잭션 흐름>

  • payment_order_statusNOT_STARTED로 생성된다.
  • 결제 실행자에 전송되면 EXECUTING으로 변경된다.
  • PSP 응답 결과에 따라 SUCCESS, FAIL 로 최종 변경된다.
  • 상태가 SUCCESS가 되면 지갑 서비스를 호출해 잔액을 올리고 wallet_updatedTRUE로 변경한다.
  • 이후 원장 서비스를 호출해 회계 장부를 작성하고 ledger_updatedTRUE로 변경한다.
  • 동일한 checkout_id를 가진 모든 주문이 성공하면 결제 이벤트 테이블의 is_payment_doneTRUE로 변경한다.
  • 완료되지 않고 미결 상태로 남아있는 주문을 탐지하기 위해 주기적인 스케줄러 작업을 실행하고, 일정 임계 시간을 초과하면 엔지니어에게 알림을 보낸다.

💡지갑(Wallet) 서비스의 정확한 역할과 잔액 동작 방식은?

지갑 서비스는 플랫폼 내부에서 판매자가 보유한 가용 잔액을 관리하는 장부 역할을 한다.
구매자가 결제한 대금은 우선 플랫폼의 통장으로 들어온다.
이 때 지갑 서비스는 판매자의 잔액을 차감하는 것이 아니라, 결제된 금액(수수료 제외)만큼 잔액을 증가(+) 적립시킨다.

이후 대금 정산(Pay-out) 단계가 되었을 때 지갑 잔액을 차감(-)하면서 실제 판매자 은행 계좌로 돈을 이체하게 된다.


2.4. 복식부기(Double-entry) 원장 시스템

모든 금융 시스템의 핵심 기록 원칙은 복식부기(Double-entry Bookkeeping)이다.
모든 거래는 서로 다른 두 개의 원장 계좌에 동일한 금액으로 차변(Debit)과 대변(Credit)에 동시에 기록된다.

계정(Account)차변(Debit)대변(Credit)
구매자 계정$1 
판매자 계정 $1

즉, 돈이 빠져나가는 계정은 차변(Debit)에, 돈이 들어오는 계정은 대변(Credit)에 같은 금액으로 기록한다.

복식부기 추상화 모델에서 모든 거래 항목의 합계는 반드시 0이어야 한다.
자금의 출처와 흐름이 정밀하게 추적되므로, 장부의 일관성을 완벽히 검증할 수 있다.


💡복식부기 기록의 주체와 시기는?

  • 기록 주체
    • 단일 내부 서비스인 원장 서비스(Ledger Service)가 전담한다.
  • 기록 시점
    • PSP 결제 성공을 확인하고 지갑 서비스 잔액 갱신이 완료된 직후(또는 결제 성공 이벤트 수신 시점)에 기록한다.
  • 동시 기록 이유
    • 차변과 대변의 기록 주체나 시점을 따로 나누면 중간에 네트워크 장애가 발생했을 때 차변만 기록되고 대변이 누락되는 등 ‘합계 = 0’ 이라는 원장 불변성이 깨진다.
    • 따라서 원장 서비스가 단일 데이터베이스 트랜잭션 내에서 차변 엔트리와 대변 엔트리를 동시에 생성한다.

차변과 대변, 물리적 테이블(장부)은 1개지만, 그 안에 기록되는 사람이나 목적(계정)은 수만개가 될 수 있다.
복식부기는 돈의 흐름을 기록하는 것이므로, 반드시 ‘돈이 빠져나가는 계정’ 1개와 ‘돈이 들어오는 계정’ 1개, 즉 최소 2개의 별도 계정이 필요하다.

사용자 A가 가맹점 B에게 10,000을 결제했다고 가정해보자.
이 때 원장 서비스에는 다음과 같이 두 개의 별도 계정(Account)이 등장한다.

  • 계정 1 (Account ID: USER_A_WALLET): 사용자 A의 지불용 선불충전금 계정
  • 계정 2 (Account ID: MERCHANT_B_ESCROW): 가맹점 B에게 정산해 줄 대기금 계정

테이블(ledger_entry)에는 이 두 계정의 내역이 아래와 같이 2줄로 기록된다.

ledger_entry_id계정 ID (Account)구분 (Type)금액설명
1001USER_A_WALLET (계정 1)차변 (DEBIT)10,000사용자 A의 잔액 감소
1002MERCHANT_B_ESCROW (계정 2)대변 (CREDIT)10,000가맹점 B의 정산 대기금 증가

복식부기 시스템을 구현하는 방법에 대한 내용은 Books, an immutable double-entry accounting database service를 참고하길 바란다.


2.5. 외부 결제 페이지

기업이 카드 정보를 자체 데이터베이스에 저장하려면 미국의 PCI DSS(Payment Card Industry Data Security Standard)와 같은 엄격한 보안 표준을 준수해야 한다.

따라서 대부분의 기업은 카드 정보를 직접 다루지 않고, PSP가 제공하는 iframe, 위젯 또는 모바일 SDK 형태의 외부 결제 페이지를 이용한다.
카드 정보 수집부터 검증까지의 과정을 PSP에 위임함으로써 플랫폼 서버에는 보안 민감 데이터가 전혀 유입되지 않도록 설계한다.

중요한 점은 결제 서비스가 아니라 PSP가 제공하는 외부 결제 페이지가 직접 고객 카드 정보를 수집한다는 것이다.


2.6. 대금 정산(Pay-out) 흐름

대금 정산 흐름은 플랫폼 법인 계좌에서 판매자의 외부 은행 계좌로 자금을 이체하는 과정이다.

대금 정산 역시 복잡한 세무, 회계 및 국가별 금융 규제가 수반되므로 티팔티(Tipalti)와 같은 전문 외상 매입금(Accounts Payable) 지급 서비스 제공업체를 연동하여 구축한다.

외상 매입금(AP, Accounts Payable)

기업이 상품이나 서비스를 제공받았으나, 아직 실제 대금을 지급하지 않은 채무(부채)를 의미한다.

결제 시스템 관점에서 고객이 결제한 대금은 플랫폼 계좌에 들어왔지만, 이는 향후 판매자에게 지급해야 할 돈이다.
따라서 회계 장부상 정산 대금은 플랫폼의 부채인 ‘외상 매입금’으로 계상되며, 정산(Pay-out) 프로세스는 이 외상 매입금 채무를 실제 송금을 통해 변제하는 과정이다.

계상

회계 장부나 예산안 등에 특정 금액이나 항목을 공식적으로 계산하여 기입하는 행위를 의미한다.
쉽게 말해 ‘장부에 올려 반영한다’라는 의미이다.

예) ‘서버 증설 비용을 내년 예산안에 계상했다.’ (= 예산 항목으로 올려 잡았다.)


3. 상세 설계: 신뢰성 확보

분산 결제 시스템에서는 네트워크 지연, 서버 장애, 외부 결제대행사(PSP)의 타임아웃 등 다양한 예외 상황이 발생한다.
성공적인 결제 아키텍처는 오류 발생 시 이중 청구를 방지하고, 시스템 간 데이터 불일치를 완벽히 복구할 수 있도록 설계되어야 한다.


3.1. PSP 연동

대부분의 전자상거래 플랫폼은 보안 및 PCI DSS 규정 준수 비용을 절감하기 위해 민감한 카드 정보를 서버에 직접 수집하지 않는다.
대신 PSP가 제공하는 외부 결제 페이지(Hosted Checkout Page, iframe, SDK)를 연동하는 방식을 택한다.

간결한 설명을 위해 결제 실행자, 원장, 지갑 등은 생략했다.

외부 결제 페이지 이용 흐름

  • ① 결제 요청
    • 사용자가 ‘결제하기’ 버튼을 클릭하면 클라이언트는 결제 주문 정보를 담아 결제 서비스를 호출한다.
  • ② 결제 등록 요청
    • 결제 서비스는 결제 금액, 통화, 결제 요청 만료일, 리디렉션 URL, 비중복 난수(Nonce, Number used Only Once)를 결제 등록 요청에 담아 PSP로 전송한다.
      • 결제 등록 시 설정된 만료 시간까지 결제가 완료되지 않으면 PSP 단에서 해당 토큰은 자동으로 무효화처리 된다.
        • 사용자가 뒤늦게 결제 승인을 시도하더라도 PSP가 요청을 거부하며, 결제 서비스 역시 스케줄러를 통해 일정 시간이 지난 미완료 결제 주문을 EXPIRED 또는 CANCELLED 상태로 전환하고 주문 재시도를 유도한다.
  • ③ 토큰 발급
    • PSP는 해당 결제 요청을 유일하게 식별하는 UUID 기반 토큰을 발급하여 결제 서비스에 반환한다.
  • ④ 토큰 저장
    • 결제 서비스는 전달받은 PSP 토큰을 데이터베이스에 저장한다.
  • ⑤ 결제 페이지 표시
    • 클라이언트는 토큰을 이용하여 PSP가 제공하는 외부 결제 페이지(또는 SDK UI)를 화면에 노출한다.
    • 이 때 결제가 완료되면 호출될 웹페이지의 URL인 리디렉션 URL이 필요하다.
  • ⑥ 결제 정보 입력 및 실행
    • 사용자는 PSP 결제 페이지에 카드 번호, CVC, 유효기간 등을 입력하고 결제를 진행한다.
  • ⑦ PSP 결제 승인
    • PSP가 카드 네트워크를 통해 결제를 처리하고 결과를 반환한다.
  • ⑧ 클라이언트 리디렉션
    • 결제가 완료되면 브라우저는 사전에 등록된 리디렉션 URL(결제 완료 결과 페이지)로 이동한다.
    • 이 때 보통 7단계에서 수신된 결제 상태가 URL에 추가된다.
  • ⑨ 웹훅 처리
    • PSP는 서버 간 통신인 웹훅을 통해 결제 최종 상태를 결제 서비스로 비동기 전송한다.
    • 결제 서비스는 이를 통해 데이터베이스의 payment_order_status를 최신 상태로 업데이트한다.

PSP가 결제 결과를 리디렉션 페이지(8단계)와 웹훅(9단계) 두 곳으로 나눠서 보내는 이유는 신뢰성 보장때문이다.
브라우저 리디렉션(8단계)은 클라이언트 네트워크 단절, 브라우저 탭 강제 종료, 모바일 앱 이탈 등으로 인해 서버에 승인 결과가 전달되지 않을 가능성이 있다.

반면 웹훅(9단계)은 PSP 서버가 플랫폼 백엔드로 직접 HTTP POST 요청을 보내는 Server-to-Server 통신이다.
응답이 200 OK로 확인될 때까지 재시도 메커니즘이 작동하므로, 시스템의 최종 결제 상태 업데이트는 반드시 웹훅을 기준으로 처리해야 안전하다.

웹훅(Webhook)

특정 이벤트가 발생했을 때 한 서버가 다른 서버로 실시간 HTTP POST 요청을 보내 데이터를 전달하는 역방향 API(Reverse API) / 커스텀 HTTP 콜백 메커니즘이다.
polling 처럼 주기를 두고 계속 물어볼 필요 없이, 이벤트 발생 즉시 비동기로 결과를 통보받을 수 있어 결제 상태 동기화에 필수적이다.


3.2. 조정(Reconciliation)

분산 환경에서는 네트워크 오류나 서비스 장애로 인해 내부 원장, 지갑, 외부 PSP 간의 결제 상태 데이터가 불일치할 수 있다.
조정(Reconciliation)은 이러한 차이를 주기적으로 비교 검증하여 일관성을 맞추는 결제 시스템의 최종 방어선이다.

조정

<조정 동작 방식>

  • 매일 밤 PSP나 은행은 가맹점(전자상거래 플랫폼, 예: 아마존)에 하루 동안 발생한 모든 거래 내역과 잔액이 기록된 정산 파일(Settlement File/Discrepancy File)을 제공한다.
  • 조정 시스템은 이 정산 파일을 파싱하여 내부 원장(Ledger) 데이터베이스의 거래 내역과 비동기로 1:1 비교를 수행한다.
  • 불일치건이 발견되면 재무팀에게 전달하여 수동 조치를 진행하거나, 자동 교정 파이프라인을 통해 정정한다.

3.3. 결제 지연 처리

대부분의 결제 승인은 몇 초 내에 처리되지만, 일부 결제 요청은 완료되거나 거절되기까지 수일이 소요되는 지연 결제 상태에 빠질 수 있다.

<지연 결제 발생 주요 원인>

  • 위험 검토
    • PSP 내부 이상 거래 탐지 시스템(FDS)이 해당 결제를 고위험군으로 판단하여 담당자의 수동 검토가 필요한 경우
  • 3D 보안 인증
    • 신용카드 승인 시 추가적인 본인 인증 과정이 요구되는 경우
    • 3D Secure는 카드 결제 시 단순 카드 정보 입력 외에, 카드 발급사 앱 인증, SMS OTP, 공인인증서 등을 통해 소유자 본인임을 한 번 더 검증하는 보안 프로토콜이다.
    • 이 과정에서 사용자 개입과 카드사 인증 서버의 비동기 승인 절차가 추가되므로 결제 처리 시간이 동기식 API 호출 범위를 넘어 지연될 수 있다.

<지연 결제 처리 메커니즘>

  • 외부 PSP는 결제 승인 대신 대기 상태(PENDING) 응답을 반환한다.
  • 클라이언트는 사용자에게 ‘결제 진행 중/검토 중’ 안내 화면을 노출한다.
  • PSP는 내부 추적을 계속 진행하며, 최종 승인/거절이 확정되면 웹훅을 통해 결제 시스템으로 이벤트를 발행한다.
    • 웹훅을 지원하지 않는 PSP의 경우 스케줄러를 통한 상태 폴링 방식 사용
  • 결제 시스템은 웹훅 수신 시 결제 주문 상태를 업데이트하고 상품 배송 프로세스를 시작한다.

3.4. 내부 서비스 간 커뮤니케이션

결제 아키텍처 내 서비스 간 통신 방식은 동기식과 비동기식으로 나뉘며, 시스템 스케일과 내결함성 요구사항에 따라 선택해야 한다.


3.4.1. 동기식 통신(HTTP/gRPC)

동기식 통신은 구조가 직관적이지만 시스템 규모가 커질수록 다음과 같은 문제가 발생한다.

  • 성능 저하
    • 체인 구조로 연결된 서비스 중 하나만 병목이 발생해도 전체 응답 시간이 늘어난다.
  • 장애 전파
    • 외부 PSP나 내부 서비스에 장애가 발생하면 요청을 보낸 클라이언트까지 즉시 영향을 받는다.
  • 강한 결합도
    • 호출자가 수신자의 네트워크 위치와 API 명세를 명확히 알아야 한다.
  • 확장성 한계
    • 갑작스러운 트래픽 폭주 시 메시지 버퍼링이 불가능하여 서버 과부하로 이어진다.

3.4.2. 비동기 통신(Message Queue/Event Streaming)

비동기 메시징 아키텍처는 시스템 간 결합도를 낮추고 장애 격리 성능을 극대화한다.

  • 단일 수신자 모델(Point-to-Point Queue)
    • 하나의 메시지는 하나의 Consumer만 처리한다.
    • 작업이 완료되면 메시지는 큐에서 제거된다.

메시지 큐와 메시지마다 하나의 수신자가 있는 경우

  • 다중 수신자 모델(Publish-Subscribe/Kafka)
    • 하나의 이벤트 메시지를 여러 수신자 서비스가 동시에 구독하여 각자의 로직을 수행한다.
    • 카프카와 같은 이벤트 스트림은 메시지가 수신 후 삭제되지 않으므로, 결제 성공 이벤트 하나를 기반으로 푸시 알림 전송, 재무 분석 반영, 정산 장부 업데이트 등을 독립적으로 병렬 처리하기에 적합하다.

동일한 메시지에 여러 수신자


3.5. 결제 실패 처리

분산 결제 시스템에서는 외부 금융망 단절, 타임아웃, 잔액 부족 등 다양한 이유로 결제 실패가 발생한다.
실패 상황을 신속히 감지하고 우아하게 처리(Graceful Degradation)하는 메커니즘이 필수적이다.


3.5.1. 결제 상태 추적

결제 수명주기의 모든 단계에서 결제 상태 변경 이력을 정밀하게 기록한다.

오류 발생 시 현재 상태가 어느 단계였는지 파악해야 자동 재시도 대상인지, 환불 처리 대상인지 판단할 수 있다.
상태 데이터는 기존 레코드를 수정하는 대신 새로운 상태 이력을 계속 추가하는 Append-Only 방식의 데이터베이스 테이블에 보관하여 감사 추적이 가능하도록 한다.


3.5.2. 재시도 큐 및 실패 메시지 큐

시스템 결함이나 일시적 네트워크 장애로 인한 실패를 복구하기 위해 재시도 큐(Retry Queue)와 실패 메시지 큐(DLQ, Dead Letter Queue)를 도입한다.

실패한 결제의 처리

  • 재시도 큐(Retry Queue)
    • 네트워크 순단, PSP 일시 장애 등 재시도 가능한 일시적 오류(Transient Error)가 발생한 메시지를 격리하여 재처리한다.
  • 실패 메시지 큐(DLQ)
    • 지정된 최대 재시도 횟수를 초과했거나 카드 정보 오류, 한도 초과 등 재시도가 불가능한 영구적 오류(Non-retryable Error) 발생 메시지를 보관한다.
    • 엔지니어는 DLQ의 메시지를 분석하여 디버깅하거나 문제 원인을 파악한다.

Uber는 Apache Kafka 기반의 multi-tier 재시도 큐(Retry & DLQ) 아키텍처를 도입하여 결제 시스템의 내결함성과 최종 일관성을 보장한다.


3.6. 정확히 한 번 전달(Exactly-Once Delivery)

결제 시스템에서 발생할 수 있는 가장 치명적인 문제는 이중 청구이다.
결제 주문은 어떤 장애 상황에서도 정확히 한 번만 실행되어야 한다.

Chain Services with Exactly-Once Guarantees 참고


3.6.1. 재시도(Retry)

네트워크 시간 초과나 응답 지연이 발생할 때 결제 요청을 재시도하여 최소 한 번은 성공을 보장한다.

sequenceDiagram
    participant 클라이언트
    participant 결제 시스템

    클라이언트 ->> 결제 시스템: $10 결제
    결제 시스템 --x 클라이언트: 결제 실패

    Note left of 클라이언트: 재시도
    클라이언트 ->> 결제 시스템: $10 결제
    결제 시스템 --x 클라이언트: 결제 실패

    Note left of 클라이언트: 재시도
    클라이언트 ->> 결제 시스템: $10 결제
    결제 시스템 --x 클라이언트: 결제 실패

    Note left of 클라이언트: 재시도
    클라이언트 ->> 결제 시스템: $10 결제
    결제 시스템 -->> 클라이언트: 결제 성공

<재시도 전략 종류>

  • 즉시 재시도(Immediate Retry): 실패 즉시 요청을 다시 전송한다.
  • 고정 간격(Fixed Interval): 일정 시간(예: 3초) 대기 후 재시도한다.
  • 증분 간격(Incremental Interval): 대기 시간을 일정 양만큼 늘려가며 재시도한다.
  • 지수적 백오프(Exponential Backoff): 재시도 대기 시간을 직전 대비 2배씩 지수적으로 늘려 전송한다.(예: 1초 → 2초 → 4초 → 8초)
  • 취소(Cancel): 요청을 철회하고 실패 상태로 확정한다.

서버 과부하 시 집단 재시도가 몰려 스스로 서비스를 마비시키는 자기 유발 서비스 거부(Self-inflicted DoS) 및 스로틀링(Throttling) 현상을 방지하기 위해 지수적 백오프에 지터(Jitter, 임의의 무작위 지연 시간)를 추가하는 것이 바람직하다.


💡Retry-After 헤더의 표준성 및 활용 근거는?

Retry-After 헤더는 RFC 9110(HTTP Semantics)에 정의된 공식 HTTP 응답 헤더이다.
서버가 429 Too Many Requests나 503 Service Unavailable 응답을 반환할 때, 클라이언트에게 다음 재시도까지 대기해야 하는 시간(초 단위 또는 HTTP Date)을 명시적으로 전달한다.

무분별한 즉시 재시도로 인한 백엔드 및 외부 PSP의 가용성 마비를 방지하고, 클라이언트 애플리케이션이 표준화된 대기 로직을 구현할 수 있도록 돕는다.


3.6.2. 멱등성(Idempotency)

멱등성이란 동일한 연산을 여러 번 수행하더라도 결과가 최초 1회 실행된 상태와 동일하게 유지되는 특성을 의미한다.
이중 결제를 방지하는 핵심 열쇠이다.

클라이언트는 결제 요청 시 HTTP 헤더에 UUID 기반의 멱등키를 전송한다.(Idempotency-Key: <UUID>)


💡멱등키에 TTL이 필요한 이유는?

멱등키가 고유한 식별자임에도 불구하고 TTL이 설정되어야 하는 이유는 두 가지이다.

  • 메모리 및 저장 공간 관리
    • 모든 거래의 멱등키를 영구 보관하면 DB/Redis의 인덱스 용량이 무한히 증가한다.
  • 비즈니스 상태 변화 차단
    • 오래 전 장바구니에 담아둔 결제 시도가 수개월 뒤 동일한 멱등키로 재요청될 경우, 변동된 상품 가격이나 재고 상태를 무시하고 옛 결제가 처리되는 부작용을 막기 위함이다.
    • 보통 Stripe 등 상용 PSP에서는 24시간~48시간의 TTL을 권장한다.

3.6.2.1. 시나리오 1: 고객이 ‘결제’ 버튼을 빠르게 두 번 클릭하는 경우

전자상거래 웹사이트에서 멱등키는 일반적으로 결제가 이루어지기 직전의 장바구니 ID이다.

멱등성

  • 고객이 결제 버튼을 연속 클릭하더라도 두 요청은 동일한 장바구니 ID 기반 멱등키를 포함한다.
  • 결제 시스템은 첫 번째 요청 수신 시 데이터베이스 유니크 제약 조건 또는 Redis distributed lock을 획득하고 결제를 진행한다.
  • 두 번째 요청이 들어오면 서버는 동일한 멱등키가 이미 처리 중이거나 완료된 것을 확인하고, 추가 결제를 수행하지 않은 채 가장 최근의 결제 처리 상태 응답을 그대로 반환한다.
  • 동시에 너무 많은 요청이 유입될 경우 429 Too Many Requests 상태 코드를 반환한다.

3.6.2.2. 시나리오 2: PSP 승인 성공 후 네트워크 오류로 응답이 유실되어 고객이 ‘결제’를 재시도하는 경우

외부 결제 페이지 이용 흐름

  • 2단계와 3단계에서 첫 번째 결제 시도 시 결제 서비스는 PSP에 비중복 난수(Nonce/UUID)를 전달하고, PSP 전용 토큰을 발급받아 결제를 승인받는다.
    • 여기서 이 난수는 결제 주문을 유일하게 식별하는 구실을 하며, 해당 토큰은 그 난수에 1:1로 대응된다.
    • 따라서 토큰 또한 결제 주문을 유일하게 식별 가능하다.
  • 카드 승인은 완료되었으나 결제 서비스로 돌아오는 네트워크 응답이 유실되면, 사용자는 결제가 실패한 것으로 판단하고 ‘결제’ 버튼을 다시 누른다.
  • 동일한 주문 건이므로 PSP로 전달되는 토큰과 멱등키는 이전과 동일하다.
  • PSP는 해당 토큰을 멱등키로 인식하여 신규 신용카드 승인을 발생시키지 않고, 이전에 승인했던 거래 결과를 그대로 반환하여 이중 청구를 차단한다.

3.7. 일관성(Consistency)

분산 환경에서 결제 서비스, 원장, 지갑, 외부 PSP 등 여러 데이터베이스 사본 간 상태 불일치가 발생할 수 있다.

  • 내부 서비스 간 일관성
    • 데이터베이스 트랜잭션과 비동기 이벤트를 정확히 한 번 처리(Exactly-Once)하도록 결합한다.
  • 내부 서비스와 외부 PSP 간 일관성
    • 멱등성을 기반으로 한 재시도 및 매일 실행되는 조정(Reconciliation) 프로세스를 통해 최종 일관성(Eventual Consistency)을 확보한다.
  • DB 복제 지연(Replication Lag) 해결 방안
    • Primary 읽기/쓰기 강제
      • Replica DB의 복제 지연으로 인한 정합성 오류를 막기 위해 결제 핵심 트랜잭션은 Primary 노드에서만 처리한다.
      • 규모 확장성이 떨어진다.
      • 사본은 데이터 안정성 보장에만 활용되고 트래픽은 처리하지 않으므로 자원이 낭비된다.
    • 분산 합의 DB 활용

3.8. 결제 보안

금융 트랜잭션을 다루는 결제 아키텍처에서 필수적으로 고려해야 하는 보안 위협 대응책이다.

위협 / 문제보안 해결책
요청/응답 도청 (Eavesdropping)전송 계층 암호화 적용 (HTTPS / TLS 1.3)
데이터 변조 (Data Tampering)요청 메시지 서명(HMAC), 암호화 저장 및 데이터 무결성 검증
중간자 공격 (MITM Attack, Man-In-The-Middle)SSL Certificate Pinning 적용으로 신뢰할 수 있는 엔드포인트만 통신
데이터 손실 (Data Loss)다중 Region 간 DB 실시간 복제 및 정기적 SnapShot 생성
DDoS 공격API Gateway 단의 처리율 제한(Rate Limiting) 및 WAF 방화벽 적용
카드 정보 도난토큰화(Tokenization) 및 PCI DSS 준수를 위해 외부 PSP 결제 창 활용
사기 및 이상 거래 (Fraud)IP 주소 검증, CVV/CVC 검증, FDS(이상거래탐지) 사용자 행동 분석

4. 마무리

안정적인 결제 시스템은 대금 수신(Pay-in)과 대금 정산(Pay-out)의 자금 흐름을 이해하고, 분산 환경에서 발생하는 네트워크 예외 상황을 멱등성과 조정 프로세스로 방어하는 것이 핵심이다.

<실무 운영 시 추가 고려요소>

  • 모니터링
    • 결제 승인 성공률, PSP별 Latency, 결제 실패 원인 코드 비율, 시스템 CPU/Memory 대시보드 구축
  • 경보
    • 평균 승인율이 특정 임계값으로 떨어지거나 DLQ에 메시지가 쌓일 경우 온콜(on-call) 엔지니어에게 Slack 등으로 비상 알림 전송
  • 디버깅 도구
    • 특정 거래 결제 ID(checkout_id, payment_order_id) 입력 시 내부 서비스 로그와 PSP 트랜잭션 이력을 추적할 수 있는 툴 구축
  • 환율 및 지역별 결제 수단
  • 디지털 지갑 연동

5. 요약

마인드맵


참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 알렉스 쉬, 산 람 저자의 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2를 기반으로 스터디하며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10