결제 및 정산 시스템 기능 요구사항 분석
결제 및 정산 시스템의 기능 요구사항을 분석하고 정의해보자.
Micro Service 환경에서 하나의 서비스를 구성하는 상황으로 가정한다.
JPA Deep Dive 시리즈를 진행하면서 토스 페이먼츠와 같은 결제 & 정산 시스템 을 구축하는 것을 목표로 한다.
우리가 만든 결제 시스템을 사용하는 클라이언트들이 어떤 주문을 어떻게 하는지는 알 필요가 없다.
단순히 어떤 결제 수단, 결제 금액 등 필수 사항들에 대한 정보만 필요하다.
필수 사항을 토대로 API 요청이 우리 측에 들어오면 우리는 정상적으로 응답하는 것이 목표이다.
핵심적인 기능을 정리를 해보자면,
주문 시 결제가 되었는가?
결제 실패시 어떻게 처리할 것인가?
결제 후 정산은 어떻게 진행이 될 것인가?
시스템 흐름 파악
하나의 시스템에 대한 설계가 필요한 경우에 잘 만들어진 서비스의 API를 분석해보고, 내부적인 구현은 어떻게 되어있을지 유추하며 전체적인 흐름을 이해하는 것도 좋은 설계 방법이라고 생각한다.
1. 결제 시스템
코어 API 정보와 아래의 결제 흐름도를 통해 페이먼츠 시스템의 흐름을 정리해보자.
흐름도에서 '결제 시스템'과 관련된 부분은 주황색(토스페이먼츠) 부분이다. 단순하게 1. 결제 생성, 2. 결제 승인 에 대한 API만 호출하면 된다고 하는데, 내부적으로 어떤 정보가 필요할까? 이를 기반으로 ERD 설계가 필요할 것이다.
결제 데이터 검증 측면
결제 시스템에서 데이터 검증은 매우 중요하다. 중간에 '결제 금액'을 바꾼다거나(데이터 무결성), 이미 결제된 건에 대한 중복 결제 등 고려해야할 사항들이 많다. 이렇듯 돈과 관련된 시스템의 경우 문제가 발생한다는 것은 있어서는 안된다.
이러한 측면을 고려했을 때 아래와 같은 정보가 필요하다고 판단했다.
주문 정보 (Order)
여기서의 주문 정보는 전체 주문 정보가 아닌, 결제에 필요한 주문 정보를 의미한다.
실제 주문 정보보다 더 적은 양의 데이터일 가능성이 크다.
결제 키 (Payment Key)
결제 금액 (Payment Amount)
또한, 결제 키(Payment Key) 같은 경우 결제를 식별하는 역할로 결제 승인, 결제 조회, 결제 취소 API 에서도 사용이 될 것이다.
결제 상태 측면
실제 서비스라면 다양한 상황을 고려해야하지만, 일단 성공 케이스만 분석을 해보자. 성공 케이스의 경우 3가지 상태의 흐름으로 이어진다.
READY -> IN_PROGRESS -> DONE
READY: 최초 결제 생성시
IN PROGRESS: 카드 정보 입력 & 카드 소유자 인증 + 내부 로직 처리가 정상적으로 처리됨
DONE: 결제 승인 완료
# 여기서 잠깐, 히스토리 데이터를 관리하기 위해 별도의 테이블이 필요할까?
위에서 나온 정보들을 기반으로 ERD를 간단하게 설계해보자.
이 테이블에서 결제 상태를 관리하는 측면에서 문제점은 무엇일까?
결제 시스템에서 '신뢰성'과 '데이터의 정확성'은 보장되어야 한다. 특히, 단일 테이블에서 상태 값만 변경하는 방식으로는 결제 처리의 전체 흐름과 변경 이력을 추적하기 어렵다.
예를 들어, 사용자는 결제가 완료되었다고 인지했지만, 내부 시스템에서 문제가 발생하여 실제 결제 상태가 DONE
이 아닌 경우, 단순히 현재 결제 테이블만으로는 어떤 과정에서 문제가 발생했는지 파악하기 어렵다.
이러한 구조에서 분쟁이 발생한다면 어떻게 대응할 수 있을까? 정확한 원인 규명이 가능할까?
결제는 '동적 변경' 데이터라는 것을 알아두자. 모든 상태 변경을 시간순으로 기록하고 추적할 수 있는 히스토리 관리 시스템이 현 상황에서는 필수적이다.
(동적 변경 데이터: 요청에 따라 정해지지 않은 상태로 변경되는 데이터를 말한다.)
위와 같은 테이블 구조를 설계를 하고 해당 테이블은 INSERT ONLY 작업만을 수행하게 한다. 이로 인해 어떤 상태에서 어떤 상태로 변경되었는지, 그리고 그 변경시킨 요인이 무엇인지에 대한 데이터를 쌓아두어 결제 시스템의 신뢰성과 데이터 정확성을 크게 향상시킬 수 있다.
히스토리 테이블에 대한 수정은 없어야 하므로, INSERT ONLY 작업만을 수행해야 하며, updated_at 컬럼 또한 존재하지 않는다.
시스템을 설계할 때 '관리 측면' 에서도 생각하는 것이 중요하다.
읽어볼거리
결제 API는 어떻게 구성될까?
위와 같이 분석한 내용을 기반으로 API를 간단하게 설계해보자.
결제 생성
Request
주문 정보와 결제 수단(카드 정보)가 넘어오게 될 것이다.
Response
생성된 결제 정보와 결제 키를 반환하게 될 것이다.
결제 승인
Request
생성된 결제에 대해서 승인 처리를 진행한다.
Response
성공 or 실패 형태의 응답
성공: 성공 데이터
실패: 에러 객체
결제 조회
결제 단건 조회
Request
결제가 완료가 되었을 때 주로 사용이 된다.
관리 목적에서도
DONE
이 아닌 경우 사용자들에게 별도의 확인이 필요한 경우도 사용 가능
결제 목록 조회
Request
groupingKey: 네이버 쇼핑 / 쿠팡 / 무신사 / 29CM 등등 어떤 스토어인지 식별하기 위한 키
현재 추가한 조건 뿐만 아니라 페이징 처리는 기본적으로 포함
2. 정산 시스템
정산 시스템은 왜 필요할까?
판매자(업체)에게 대금을 지급하기 위함임은 당연하게 이해하고 있을 것이다.
그렇다면, 결제 시스템과의 차이점은 무엇일까? 가장 큰 특징은 결제는 실시간 처리로 이루어지지만 정산은 특정 주기(일, 주, 월)마다 정리가 되어야 한다.
여기서 드는 생각, 정산을 위한 별도 테이블이 꼭 필요할까?
위에서 언급했던 결제 테이블이 존재한다고 가정했을 때, 결제 상태(payment_status) 값이 완료(DONE) 인 상태들에 대해서 조회 쿼리를 날리면 되는 것이 아닌가?
단순 금액 합계 외에도 수수료 차감, 세금 계산 등 추가 연산이 필요하다.
정산에 사용된 데이터가 나중에 변경될 수 있다. 과거 정산 내역의 정확성을 보장하기 어렵다.
이 뿐만 아니라 더 많은 문제점들이 많을 것이라고 예측이 되며, 다양한 이유로 별도의 정산 데이터 관리가 필요하다고 느껴진다.
목적
사용자의 결제를 실시간으로 처리
특정 주기에 따라 정산 대상과 금액을 집계하고 지급
주요 기능
카드, 계좌이체, 포인트 결제 처리 / 결제 승인 & 취소 / 결제 상태 관리
결제 완료 데이터를 기반으로 정산 집계 / 지급 / 오류 처리
트랜잭션 속성
실시간 트랜잭션 (ACID)
배치성 트랜잭션 (주기적 실행)
장애 영향도
장애 발생 시 서비스 이용 불가 (사용자 결제 중단)
장애 발생 시 정산 지연, 즉각적인 사용자 영향 적음
데이터 저장
트랜잭션 위주 (MySQL, PostgreSQL 등 RDB 선호)
집계 및 분석이 많음 (RDB + Redis/NoSQL 조합 가능)
Trigger / 스케줄링(배치) / 비동기 등등 기술들이 정산 시스템에 적합해보이는데 이에 대해서 학습이 필요해 보인다.
업계 사례 분석
실제 정산 시스템을 운영하고 있는 업계에서는 어떤 방식으로 시스템을 구성했는지 파악해보자.
정산 시스템은 매일 금액 변동을 관리하는 시스템이라고 보면 이해하기가 편하다. 핵심적인 기능은 다음과 같다.
매일 발생되는 주문 데이터를 저장하고 이를 기반으로 정산 금액을 계산한다.
지정된 일자에 사장님(업체)에게 정산을 지급해야 한다.
결제와 마찬가지로 돈을 다루는 도메인이다보니 '정확한 정산'을 지급하는 것을 최우선 목표로 선정해야한다.
정산 흐름 정리 및 ERD 설계
전체적인 흐름을 보면 다음과 같을 것이다.
스토어별 일간 집계
payments
테이블에 상태가 완료(DONE)인 값들에 대해 특정 시간에 집계를 수행한다.
일간 집계 작업이 필수적이진 않으나, 정산 효율성 측면과 가맹점주들에게 데이터를 보여준다는 측면을 고려했다.
주간 또는 월간 정산 실행
스토어별 정산 시기에 맞춰 주간 또는 월간 정산이 실행되어야 한다.
Trigger 방식처럼 특수 시점에 배치 작업을 통해 daily_settlement
테이블을 기반으로 처리할 것으로 예상된다.
정산 지급을 위한 은행 API 호출
정상 처리의 경우 정산 완료 상태로 업데이트가 될 것이며, 실패할 경우 재시도 및 알림을 발송할 것으로 예상된다.
위의 흐름과 분석한 내용을 기반으로 ERD 설계를 진행해보자.
정산 또한 히스토리성 테이블이 필요해보이지만, 일단 생략하고 넘어간다.
정산 API는 어떻게 구성될까?
정산을 직접적으로 요청하는 API가 존재할까?
기간 별 정산을 조회하는 기능은 무조건 있을 것
수동 정산이 필요한 케이스가 무엇인가?
매입: 가맹점(상점)이 카드사에 결제 대금을 청구하는 과정 (업체 -> 페이먼츠 -> 카드사)
일반적인 업체의 경우 카드 결제 승인 후 자동으로 매입 요청 및 정산 처리를 진행하게 된다. 또한 페이먼츠에서 결제가 승인된 다음날 일괄적으로 카드사에 매입을 요청한다.
하지만, 결제 승인과 상품/서비스 제공 사이에 시차가 있는 경우나 결제 후 취소나 변경 가능성이 있는 경우 수동 정산이 필요하게 된다.
구체적인 예시로, 시험 접수 대행 업체에서 결제 완료 후 접수건에 대해 취소 가능성이 있다. 따라서 취소된 결제건을 제외하고 유효한 결제만 정산을 받기 위해 수동 정산이 필요하다.
즉, 자동 매입을 비활성화 하며 거래 상태를 추적하는 별도의 시스템이 존재할 것으로 예측이 된다. 별도의 flag성 컬럼이 필요하지 않을까? 라는 생각이 들기도 한다.
정산 조회
정산 단건 조회
Request
특정 정산건에 대한 ID 값을 함께 요청한다.
Response
대표적으로 아래와 같은 정보가 포함이 된다.
결제 방법
정산 금액 (total - fee)
현재 정산 상태
정산 목록 조회
Request
여기서 settledAt 데이터의 범위를 startDate ~ endDate
사이로 지정해서 필터링하게 될 것이다.
Response
위의 정산 단건 조회의 응답을 리스트 형태로 응답할 것이다.
수동 정산 요청
Request
어떤 결제 건에 대해서 정산을 요청할 것인지를 명시한다.
Response
단순 정산 요청이 성공했는지 실패했는지에 대해 응답할 것이다.
결제와 정산에 대한 전체적인 흐름은 파악한 것 같으나, 여전히 명확하진 않아 꾸준히 시간이 될 때 잘 구축된 시스템들은 어떻게 설계가 되어있을지 찾아보면서 많은 고민이 필요해보인다.
또한, 이미 완성된 시스템이더라도 완벽할 수는 없다는 측면에서 비판적으로 보는 것 또한 괜찮은 자세일 것 같다.
Last updated
Was this helpful?