인턴 퇴사후 백수 2주차, 슬슬 뭐라도 해야겠다 마음을 굳히며 원티드를 기웃거리던 도중 "기술과제 합격률을 높이는 예외처리와 테스트 전략" 이라는 백엔드 챌린지를 진행 중인걸 발견했습니다.
결제 시스템이라니... 뭔가 이전 회사가 떠오르는 재밌어 보이는 주제 + 코틀린만 쓰다보니 자바가 생각이 안 나서 공부해야 함+ 포폴갱신
라는 이유로, 이미 기간은 지났지만 혼자서 과제를 만들어 보았습니다.
GitHub - hyerijang/simple-payment-system: 결제 시스템
결제 시스템. Contribute to hyerijang/simple-payment-system development by creating an account on GitHub.
github.com
요구 사항 분석 1
우선 주어진 요구사항을 간단히 정리하면 다음과 같습니다.
결제 플랫폼, 포트원(구 아임포트)을 사용해서 결제 프로세스를 구현합니다.
시간적 여유가 된다면 지난 과제를 진행해주세요.
0-1. 지난 과제를 진행했다면, 용품을 구매할 때 결제가 진행되도록 해주세요.
0-2. 지난 과제를 진행하지 않았다면, 바로 결제를 위한 기능을 구현해주세요.
사용자간 거래에서 결제플랫폼을 통해 결제를 진행합니다.
결제진행, 가상계좌, 결제취소 기능을 개발해주세요. (변경: 계좌이체 -> 가상계좌)
3-1. 이 과정에서 결제 대행사는 임의로 설정하셔도 괜찮습니다.
결제연동문서를 자세히 보고 단순 기능 추가가 아닌 실제 결제가 진행된다는 관점에서 기능을 구현해주세요.
위에서 말하는 지난 과제는 당근 마켓처럼 유저가 물건을 등록하고 거래할 수 있는 전자 상거래 서비스 API입니다. 제대로 구현하려면 꽤 시간이 걸릴 것 같아서 (인증 구현, 연관관계 복잡..) 우선 결제 서비스 구현에만 집중해 보기로 했습니다.
그래서 결제 시스템이 뭐야?
처음에는 패기롭게 설계부터 시작했는데요, 이것도 넣고 저것도 넣고 하다 보니 어느새 마켓 API + 결제 API 가 합쳐진 거대한 무언가가 되어있었습니다. 😅
여기서 발견한 문제점 : 나 결제시스템이 뭔지 정확히 모르는구나!
포트원 개발자센터api 문서를 읽어봐도.. 솔직히 "결제 프로세스"라는 게 정확히 뭔지, 어디서 부터 어디까지 구현해야하는지 감이 잘 안 오더군요 (그래서 엔티티에 뭘 넣어야 된다고?). 그래서 우선 결제 시스템이 뭔지 감을 잡아야겠다는 생각이 들었습니다.
뭔가 참고할만한 레퍼런스가 없나 하다가, 얼마 전에 산 "가상 면접 사례로 배우는 대규모 시스템 설계 기초" 1,2, 권중 하나에는 분명 있을 것 같다는 생각이 들어 찾아봤는데 다행히도 있었습니다 (2권 11장 결제시스템)
(저한테 필요한 부분만 정리했기 때문에 책에서 생략된 부분이 많습니다 + 책에 없는 내용도 있음)
결제 시스템이란?
[기능 요구사항]
- 결제 시스템은 크게 두 가지 플로우로 구성된다
- 대금 수신 : 판매자를 대신하여 고객으로부터 대금을 수신한다
- 대금 정산 : 전 세계의 판매자에게 제품 판매 대금을 송금한다
- (즉, 쿠팡에서 물건을 사면 바로 판매자 계좌로 입금하는 게 아니라 우선은 쿠팡이 돈을 맡아뒀다가 주기적으로 정산한다.)
[비기능 요구 사항]
- 신뢰성 및 내결함성 : 결제 서비스는 신중하게 처리되어야 한다
- 내부 서비스 (결제 시스템, 회계 시스템)과 외부 서비스 (결제 서비스 제공업체 (여기서는 포트원)) 간의 조정 프로세스
- 해당 시스템 간 결제 정보가 일치하는지 비동기적으로 확인해야 한다
[구성 요소]
- 결제 서비스
- 주요 업무
- 결제 이벤트를 수신
- 결제 프로세스 조율
- 위험 확인 (자금 세탁/ 테러자금 조달 같은 범죄 행위의 증거가 있는지 평가)
- 위험 확인은 매우 복잡하고 전문화되어있기 때문에 제 3자 제공업체 사용
- 주요 업무
- 결제 실행자
- 결제 서비스 공급자를 통해 지급지시서(PaymentOrder) 하나를 실행
- 하나의 결제 이벤트에는 여러 결제 주문이 포함될 수 있다
- 결제 주문을 여러 개로 나누는 이유: 한 번의 결제로 여러 판매자의 제품을 처리하는 경우를 고려해야 하기 때문
- 결제 서비스 공급자 (PSP, 국내에서는 보통 PG사라고 지칭)
- 포트원 / 페이팔 등등
- A 계좌-> B 계좌로 돈을 옮기는 역할
- 원장
- 결제 트랜젝션에 대한 금융 기록
- 복식 부기 원장으로 설계해야 함
- 지갑
- 판매자의 계정 잔액 기록
[일반적인 결제 흐름]

1. 사용자가 주문하기 버튼을 클릭하면 결제 이벤트 생성, 결제 서비스로 전송
2. 결제 서비스는 결제 이벤트를 DB에 저장
3. 때로는 단일 결제 이벤트에 여러 결제 주문이 포함될 수도 있다. (여러개 상품을 구매하는 경우), 이 경우 결제 주문마다 결제 실행자를 호출한다.
4. 결제 실행자는 결제 주문을 DB에 저장
5. 결제 실행자가 외부 PSP를 호출하여 신용카드 결제 처리
(아래부터는 ' 대금 정산'을 위한 단계임. 판매자에게 제품 판매 대금을 송금하는 경우에만 필요! )
(지갑, 원장 서비스와 내부 통신 )
6. 결제 실행자가 결제를 성공적으로 처리하고 나면 결제 서비스는 지갑을 갱신하여, 특정 판매자의 잔고 기록
7. 지갑 서버는 갱신된 잔고 정보를 데이터베이스에 저장
8. 지갑 서비스가 판매자 잔고를 성공적으로 갱신하면 결제 서비스는 원장을 호출
9. 원장 서비스는 새 원자 정보를 데이터베이스에 추가
[왜 하나의 결제 이벤트를 여러 결제 주문으로 쪼갤까?]
- 판매자가 여러명인 경우 : A에게 만원 정산, B에게 2만원 정산... 이런식으로 처리하려면 어차피 물품별로 구분하긴 해야함.
- 이렇게 쪼개면 한 주문에 대해서 일부 물품만 취소 (부분 환불)하는 것도 가능해질 것 같다.


- 위 그림에서 '가맹점'은 쇼핑몰의 웹페이지를, '결제창'은 왼쪽 그림의 각 PG사가 제공하는 결제창 (오른쪽 그림 참조)를 의미
-
- 서버(백엔드)에서 작업이 필요한 부분은 4번 ~ 9번 단계
- PG사로부터 전달받은 결제 키를 저장
- 유저가 입력한 결제 정보를 토대로 결제 처리후 결과 응답
- PG사에 결제 요청 및 결과 (결제 실패 , 완료, 취소)를 처리
- (필요한 경우) 원장, 지갑 처리
- 서버(백엔드)에서 작업이 필요한 부분은 4번 ~ 9번 단계
- 구현 시 고려해야 할 점
- 내부 시스템 간 통신은 비동기 통신 + 메세지 큐 (카프카 등) 로 구현하는 게 일반적
- 특히, 대규모 결제 시스템 (비즈니스 로직이 높고 타사 서비스 의존성이 높은) 에서는 비동기 통신을 선택하는 것이 좋다.
- 동기로 구현시 설계하기는 쉽지만, 아래와 같은 문제가 발생하기 때문.
- 성능 저하 : 요청 처리에 관련된 여러 서비스에 성능 문제가 발생시, 전체 시스템 성능에 영향
- 장애 격리 곤란 : PSP 등의 서비스에 장애 발생시 클라이언트는 응답 받지 못함
- 높은 결합도 : 요청 발신자가 수신자를 알아야함
- 낮은 확장성 : 큐를 버퍼로 사용하지 않음 -> 갑작스러운 트래픽 증가에 대응할 수 있도록 시스템을 확장하기 어려움
- 결제 처리는 PSP와 신용 카드사에서 아주 오래 지연(pending) 될 수 있음
- 여러 컴포넌트와 외부 서비스를 낌 + 위험 관리 (자금세탁) 체크 과정에서 하루 넘게 지연되기도 함
- 결제 처리 결과를 알기 위해 웹훅을 사용하거나 / 주기적으로 확인(polling)해야 함 (포트원의 경우 웹훅 방식 제공)
- 결제 처리 실패 시 재시도 필요
- 재시도 큐 (카프카)
- 정확히 한번 처리를 보장해야 함
- 이를 위해 전체 플로우(주문, 결제, 재고관리, 배송... )에서 공통된 하나의 키를 사용해야 함
- 이 키는 멱등키이다 (같은 요청이 여러 번 일어나도 항상 첫 번째 요청의 결과 리턴)
- 보안
- 내부 시스템 간 통신은 비동기 통신 + 메세지 큐 (카프카 등) 로 구현하는 게 일반적
요구사항 분석_최종
책에서 공부한 내용을 바탕으로 제 나름 요구 사항을 다시 분석해 보았습니다
[요구사항 분석]
- 요구사항에는 결제 서비스 중 "대금 수신" 부분만 있음 (대금 정산은 구현할 필요 없음)
- 원장/ 지갑 / 결제 재시도 : 요구사항에도 없고 구현하면 너무 복잡해지니 생략
- 결제 실행자는 멱등성을 보장해야 함
- 단일스레드
- 멀티스레드 : DB 키 저장해서 중복 체크 / ConcurrentHashmap/ Redis / 공유락
- 뭐가 더 나을지는 상황에 따라 고려. ( 성능, 복잡도 vs 정확한 결과 보장 )
- 카프카 : 주문 서비스 등에서 "이벤트"를 발행하면 결제 서비스, 재고 관리 서비스, 정산 ... 등등이 각자 이를 수신해서 각자 처리함
- 멱등성을 위해, kafka producer 구현시에는 key을 활용 -> 특정 유저의 주문서는 특정 파티션에만 적재되도록 한다.
[어떤 방식으로 구현할까?]
- 간단한 구현을 위해 일단 단일 스레드 / 동기식으로 구현
- 단일 서비스(결제 서비스)만 구현 -> 내부 서비스간 통신이 없으므로 일단 카프카는 생략
- 지난 과제에 따르면, 우리 서비스는 당근마켓 같은 서비스 물품 거래 서비스
- 한 번에 한 번의 물건만 결제/ 환불할 것이므로 지급지시서에 하나의 주문만 들어간다고 가정하겠음
참고자료
포트원 결제 연동 Doc
포트원 결제 연동 가이드입니다. 빠른 시간 안에 결제를 연동할 수 있게 도와드립니다.
developers.portone.io
가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 | 알렉스 쉬 - 교보문고
가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 | 구글 맵, 지메일, 아마존 S3 같은 대규모 시스템은 어떻게 설계할까? 글로벌 기업 시스템 설계에 몸담은 핵심 인물들의 노하우를 집대성했
product.kyobobook.co.kr
'프로젝트 > 간단 결제 시스템 (개인)' 카테고리의 다른 글
| 간단 결제 시스템 구현 : (2) Mono 제대로 써보기 (0) | 2025.03.12 |
|---|