Published on

12. 결제 시스템

Authors
  • avatar
    Name
    이건창
    Twitter

1 단계 : 문제 이해 및 설계 범위 확장

  • 기능 요구사항
    • 대금 수신(pay-in) 흐름 : 결제 시스템이 판매자를 대신해 고객 대금을 수령한다.
    • 대금 정산(pay-out) 흐름 : 결제 시스템이 전 세계 판매자에게 제품 판매 대금을 수령한다.
  • 비기능 요구사항
    • 신뢰성 및 내결함성 : 결제 실패는 신중하게 처리한다.
    • 내부 서비스(결제 시스템, 회계 시스템)와 외부 서비스(결제 서비스 제공업체) 간 조정 프로세스 : 시스템 간 경제 정보 일치 여부는 비동기 방식으로 확인한다.
  • 개략적인 규모 추정
    • 하루 100만 건 트랜잭션을 처리하면 초당 10건 트랜잭션을 처리해야 하고 처리 대역폭 대신 트랜잭션이 잘 처리되는지 여부에 초점을 맞춰야 한다.

2단계 : 개략적 설계안 제시 및 동의 구하기

결제 흐름은 자금 흐름을 반영하면 대금 수신 흐름, 대금 정산 흐름 두 단계로 세분화한다.

대금 수신 흐름

그림 1
  • 결제 서비스
    • 사용자로부터 결제 이벤트를 수락하고 결제 프로세스를 조율한다. AML/CFT 규정을 준수하는지, 범죄 행위 증거가 있는지 평가하는 risk check를 먼저 진행하며 보통 제 3자 제공업체를 이요한다.
  • 결제 실행자
    • 결제 서비스 공급자(PSP)를 통해 결제 주문을 실행한다. 하나의 결제 이벤트는 여러 결제 주문이 포함된다.
  • 결제 서비스 공급자
    • 결제 서비스 공급자(PSP)는 돈을 옮긴다.
  • 카드 유형
    • 카드사는 신용 카드 업무를 척리한다.
  • 원장
    • 결제 트랜잭션에 대한 금융 기록이며 원장 시스템으로 전자상거래 총 수익을 계산하거나 향후 수익을 예측하는 등, 결제 후 분석(post-payment analysis)을 진행한다.
  • 지갑
    • 지갑에는 판매자의 계정 잔액을 기록한다.

대금 정산 흐름

  • 정산 흐름은 타사 정산 서비스를 사용해 전자상거래 은행 계좌에서 판매자 은행 계좌로 돈을 이체한다. 일반 결제 시스템은 대금 정산을 위해 티팔티(Tipalti)와 같은 외상 매입금(accounts payable) 지금 서비스 제공업체를 이용한다. 대금 정산에도 다양한 부기 및 규제 요구사항이 있다.

결제 서비스 데이터 모델

결제 서비스는 결제 이벤트결제 주문 테이블이 필요하다. 결제 시스템 저장소 솔루션에서 성능을 중요한 고려사항이 아니고 다음 중점을 고려한다.

  • 안정성이 검증되어 있는가? 대형 금융사에서 긍정적인 피드백을 받고 있는가?
  • 모니터링 및 데이터 탐사(investigation)에 필요한 도구가 풍부한가?
  • 데이터베이스 관리자(DBA) 채용 시장이 성숙한가?

일반적으로 ACID 트랜잭션을 지원하는 전통적인 관계형 데이터베이스를 선호하게 된다.

결제 이벤트는 결제 행위로 만들어진다. 이벤트에 필요한 정보가 저장되게 된다. 결제 주문 테이블은 **결제 주문의 실행 상태**와 지갑 전송 상태를 포함해서 저장한다. 결제 주문의 실행 상태 NOT_STARTED, EXECUTING, SUCCESS, FAILED 과정을 거치며 지갑 전송 상태TRUE, FALSE로 정의한다.

  • 결제 주문의 실행 상태 NOT_STARTED지갑 전송 상태FALSE로 결제 주문이 생성된다.
  • 결제 실행자에 주문을 전송하면 EXECUTING으로 변경한다.
  • 결제 처리자 응답에 따라 값을 SUCCESS, FAIL로 변경한다.
  • SUCCESS로 결정되면 지갑 서비스를 호출해 잔액을 업데이트하고 지갑 전송 상태TRUE로 변경한다.
그림 2

여기에서 볼 수 있는 모습은 트랜잭션 범위를 최소화하기 위해 상태 변화 과정을 세 단계로 분할했다. **외부 인프라가 존재한다면 단계를 분할해 트랜잭션의 범위를 최소화 할 수 있겠다**는 결론에 다다를 수 있었다.

책에서 결과 기준으로 트랜잭션 범위를 분할하며 작업 간 일관성을 보장했다. 그럼 **공유 자원으로 결과를 어떻게 관리할지**도 관건이었다.

외부 API를 호출하거나 호출받는 흐름은 어떤 트랜잭션에 포함되어야 할지도 고민이었다. 외부 API 호출이 실패하면 롤백할지 여부를 결정해야 하는데 난관이었다.

복식부기 원장 시스템

복식 부기는 결제 시스템에 필수 요소이며 정확한 기록을 남기는 데 핵심적인 역할을 수행한다. 복식부기는 모든 거래 항목 합계가 0이어야 한다. 복식부기 특징을 활용하면 자금 흐름을 추적할 수 있고 결제 기록을 일관성을 보장할 수 있다.

복식 부기 시스템에 관심있다면 다음 글을 참고하자.

https://developer.squareup.com/blog/books-an-immutable-double-entry-accounting-database-service/

3 단계 : 상세 설계

조율과 조정

결제 서비스가 결제 프로세스를 조율하며 ACID를 만족하게 된다. 그러나 PSP 처럼 외부 시스템과의 통신에서 통신 장애가 발생할 경우 주기적으로 비교하는 조정을 통해 문제를 해결한다.

트랜잭션 보장을 위해 결제 서비스에서 조율하고 마지막 확인을 위해 주기적으로 비교하는 조정을 진행한다.

결제 서비스는 결제 프로세스를 다음처럼 결과로 조율하게 된다.

그림 3

조정은 결제 시스템에서 PSP(결제 서비스 공급자)에서 제공하는 정산 파일로 진행된다.

그림 4

조정 중 발견된 문제 해결 방법은 주어진 재료를 이용해 세 가지 방식으로 분류할 수 있으며 어떻게 대응할 수 있을지 정의할 수 있다.

  • 문제 유형을 파악하고 프로세스를 자동화 할 수 있는 경우 : 자동화 진행
  • 문제 유형을 파악했지만 프로세스를 자동화 할 수 없는 경우 : 재무팀에서 처리
  • 문제 유형을 분류할 수 없는 경우 : 재무팀에서 조사

서비스 간 커뮤니케이션

통신에는 동기식과 비동기식 두 가지 패턴이 있다. 동기 통신은 소규모 시스템에서는 잘 동작하지만 규모가 커지면 처리할 양이 많아지고 지연시간이 길어지게 된다. 대표적인 단점은 다음과 같다.

  • 성능저하 : 서비스 하나가 전체 시스템에 영향을 끼친다.
  • 장애 격리 곤란 : PSP에서 장애가 발생하면 클라이언트는 더 이상 응답을 받지 못한다.
  • 높은 결함도 : 요청 발신자는 수신자를 알아야 한다.
  • 낮은 확장성 : 변화하는 트래픽 대응이 어렵다.

동기식 통신은 설계가 쉽지만 서비스 자율성을 높이기에는 적합하지 않다. 의존성이 높아질 수록 전반적인 성능은 떨어지게 된다. 비동기 통신은 설계의 단순성과 데이터 일관성을 시스템 확장성 및 장애 감내 능력과 맞바꾼 결과다. 비즈니스 로직이 복잡하고 타사 서비스 의존성이 높은 대규모 결제 시스템에는 비동기 통신이 나은 선택이다.

결제 실패 처리

결제 서비스에 대한 안정성과 결함 내성을 얻기 위해선 실패한 결제를 적절히 처리할 수 있어야 한다. 책에서는 결제 상태를 추적할 수 있는 환경과 재시도 및 결제 실패 이력 관리로 내결함성을 유지했다.

  • 결제 상태 추적 : 결제 실패할 경우 재시도 또는 환불 여부를 결정할 수 있어야 한다. 결제 상태는 추가만 가능하고 수정이 불가능한 테이블에 보관해야 한다.
  • 재시도 및 결제 실패 관리 : 일시적인 오류에 대비해 재시도 큐(retry queue)에 보내고 반복 처리 실패 메시지는 실패 메시지 큐(dead letter queue)로 보낸다.

정확히 한 번 전달

결제 시스템에서 가장 큰 문제는 이중으로 청구되는 일이기 때문에 정확히 한 번만 실행되도록 구현해야 한다. 메시지를 한 번만 전달하는 건 어려워보이지만 최소 한 번, 최대 한 번 만 실행되도록 검증해나가면 된다.

최소 한 번 실행

요청을 보내게되면서 최소 한 번 실행되게 되는데 통신 장애로 인해 요청을 다시 시도해야 하는 경우가 있다. 재시도 메커니즘을 도입할 때 얼마나 간격을 두고 재시도할지 정하는게 중요한데 사용되는 전략은 다음과 같다.

  • 즉시 재시도 : 즉시 요청을 다시 보낸다.
  • 고정 간격 : 고정된 시간 동안 기다린 후 요청을 다시 보낸다.
  • 증분 간격 : 기다리는 시간을 점진적으로 늘려 나가며 요청을 다시 보낸다.
  • 지수 백오프 : 기다리는 시간을 직전 대비 두 배씩 늘려간다.
  • 취소 : 요청을 철회한다.

어떤 재시도 전략을 사용할지 결정하기 어렵지만 네트워크 문제가 단시간 내 해결되지 않다면 지수 백오프를 사용하면 좋다.

최대 한 번 실행

최대 한 번 실행을 보장하기 위해서 멱등성을 유지하는 게 좋다.

멱등성은 연산을 여러 번 실행해도 최초 실행 결과가 그대로 보존되는 특성을 의미한다.

멱등성을 고려할 케이스는 다음과 같다.

  • 고객이 결제 버튼을 빠르게 두 번 클릭한 경우 : 요청에 포함된 멱등키를 받았기 때문에 멱등키로 요청한 이전 결과를 반환한다.
  • PSP 결제를 성공적으로 처리했지만 네트워크 오류로 응답이 결제 시스템에 전달되지 못해 사용자가 결제 버튼을 다시 클릭한 경우 : PSP는 비중복 난수를 전송하므로 난수로 식별한다.

일관성

결제 실행 과정에서 상태 정보를 유지하기 위해 여러 서비스를 호출한다.

  1. 결제 서비스는 비중복 난수, 토큰, 결제 주문, 실행, 살태 등 결제 관련 데이터를 유지한다.
  2. 원장은 모든 회계 데이터를 보관한다.
  3. 지갑은 판매자의 계정 잔액을 유지한다.
  4. PSP는 결제 실행 상태를 유지한다.
  5. 데이터는 안정성을 높이기 위해 여러 데이터베이스 사본에 복제된다.

분산 환경에서는 서비스 간 통신 실패호 데이터 불일치가 발생할 수 있는데, 이런 일관성 문제를 해결할 수 있어야 한다. 우선 내부 서비스 간 데이터 일관성을 유지하기 위해서는 **요청이 정확히 한 번만 처리될 수 있도록 보장**해야 한다. 외부 서비스와 데이터 일관성을 유지하기 위해서는 **멱등성**과 **조정 프로세스**를 활용한다.

데이터 다중화 시 복제 지연으로 인해 기본 데이터베이스와 사본 데이터가 불일치하는 일이 발생할 수 있다. 이런 경우 **(1)마스터에서만 쓰기와 읽기 연산을 처리**하거나 **(2)모든 사본이 항상 동기화되도록 구성**해야 한다.