01
멀티 벤더 결제 시스템 및 미수 복구 안정화
서비스 초기 설계부터 구현까지 전담
결제 서비스 초기 설계부터 구현까지 전담하며, 단일 결제 구조를 멀티 벤더사를 수용하는 모듈 구조로 확장하고, DLQ 기반 미수 이벤트 처리와 자동 복구 체계를 구성했습니다. 이후 DLQ retry stuck 방지, PG not-found 후속 재결제, 취소 알림톡 분기까지 운영 안정화 영역을 보강했습니다.
설계 배경
단일 결제 중심 구조에서 복수 벤더를 같은 방식으로 수용하고, 이후 새로운 벤더가 늘어나더라도 같은 확장 지점으로 붙일 수 있는 구조가 필요했습니다.
강조 포인트
PG 확장과 미수 이벤트 재처리 기준을 함께 설명할 수 있는 프로젝트입니다.
핵심 구현
- 추상 클래스 기반 벤더 전략 패턴으로 PG사 통합 아키텍처를 구성했습니다.
- GCP Pub/Sub 기반 DLQ 패턴을 구현하고 실패 이벤트를 별도 큐로 격리해 미수 처리 대상이 추적 가능하도록 구성했습니다.
- 재시도, DLQ, NACK 기반 자동 복구 경로를 구현해 미수 이벤트가 결제 보완 처리 흐름으로 이어지도록 구성했습니다.
- FAILOVER 상태 전이와 retry timestamp 기록을 보강해 dead-letter 재시도가 stuck 되지 않도록 만들고, PG not-found 응답에서도 미수 재결제 플로우가 끊기지 않도록 수정했습니다.
- 전액 취소, 부분 취소, 로밍 결제 취소 알림톡 context를 분리하고 금액 포맷팅을 정리해 사용자 커뮤니케이션 정확도를 높였습니다.
엔지니어링 관점
- 결제 요청, 포인트 hold/confirm, 성공·실패 업데이트를 PaymentProcessor 중심으로 묶어 상태 전이를 한 곳에서 추적할 수 있게 설계했습니다.
- 같은 사용자의 중복 결제 시도가 포인트 hold를 동시에 건드리지 않도록, 유저 단위 락 안에서 hold와 payment READY 생성을 함께 처리했습니다.
- PG 네트워크 불확실성은 repair, 컨슈머 재시도, failover 배치로 층을 나눠 즉시 복구와 운영 개입 경계를 분리했습니다.
- 재시도 자체도 운영 관찰 대상이라고 보고, 실패 이벤트가 어디에서 멈췄는지 latestRetriedAt과 상태 전이로 남겨 후속 보정 판단이 가능하게 했습니다.
Mermaid로 보는 핵심 구조
Mermaid View
멀티 벤더 결제 오케스트레이션: 락, hold, 상태 전이
PaymentProcessor 기준으로, 요청 데이터와 락 키, 포인트 hold, payment READY, `PG Vendor` 내부 승인 단계, 성공/실패 상태 전이가 한 장 안에서 자연스럽게 이어지도록 정리했습니다.
flowchart TD
Req["pay / rePay<br/>order=ORD-240915-001<br/>user=421 method=17 point=2000"] --> Lock["distributed lock<br/>payment-user-process:421"]
Lock --> Hold["PointUpdater.hold()<br/>wallet -> HOLD 2000P"]
Hold --> Ready["createWithReady()<br/>payment READY"]
Ready --> Sub["resolve subscription<br/>methodId=17 or primary"]
Sub --> Vendor["PG Vendor Router<br/>supports: KakaoPay / TossPayments / KakaoT<br/>selected: KakaoT"]
subgraph PGV["PG Vendor Layer"]
direction TD
Vendor --> Keys["read vendor keys<br/>pgPayKey + token"]
Keys --> Api["vendor client.pay(...)"]
Api --> Tx["save pgTransactionId<br/>paymentId / tid / paymentKey"]
end
Tx --> Result{"approval result"}
Result -->|success| Done["updateSuccess<br/>payment PAID<br/>point HOLD->CONFIRM"]
Result -->|fail| Fail["updateFailed<br/>releaseHold(order)"]
Fail --> Recovery["repair / retry / failover"]
classDef vendor fill:#dff2ff,stroke:#0f4c81,stroke-width:2px,color:#0f172a;
class Vendor,Keys,Api,Tx vendor;
style PGV fill:#eef7fb,stroke:#0f4c81,stroke-width:2px,color:#0f172a; Mermaid View
멀티 Vendor 시스템: 필수 계약과 선택 확장
`VendorType` 확장 지점 위에 `VendorChecker.select()`를 두고, 모든 벤더에 필요한 계약과 특정 벤더에만 필요한 기능을 분리했습니다. `@RequiredVendor`와 `VendorRequirementsValidator`가 앱 시작 시 필수 구현 누락을 막고, `VendorPaymentOnceProcessor(KakaoPay)`나 repair 서비스는 필요한 벤더에만 붙도록 구성했습니다.
flowchart TD
Vendors["VendorType<br/>KakaoPay / TossPayments / KakaoT"] --> Select["VendorChecker.select(vendorType)"]
Select --> Required["Required on all vendors<br/>VendorPaymentProcessor<br/>VendorMethodProcessor"]
Select --> Partial["Required on some vendors<br/>VendorPaymentOnceProcessor<br/>(KakaoPay only)"]
Select --> Optional["Optional extensions<br/>RepairService / vendor hooks"]
Required --> Validate["@RequiredVendor<br/>+ VendorRequirementsValidator"]
Partial --> Validate
Validate --> Boot{"startup validation"}
Boot -->|missing| Error["application start fail"]
Boot -->|ok| Route["route to concrete impl"]
classDef core fill:#dff2ff,stroke:#0f4c81,stroke-width:2px,color:#0f172a;
classDef optional fill:#edf9f3,stroke:#2f6f57,stroke-width:2px,color:#0f172a;
classDef error fill:#fff1f2,stroke:#be123c,stroke-width:2px,color:#0f172a;
class Vendors,Select,Required,Partial,Validate,Route core;
class Optional optional;
class Error error; 운영 결과
- 복수 PG를 단일 인터페이스로 통합하고, 새로운 벤더가 추가돼도 같은 구조로 확장 가능한 결제 아키텍처를 만들었습니다.
- DLQ 기반 미수 이벤트 처리와 자동 복구 경로를 운영에 적용했습니다.
- 외부 PG 응답 예외와 취소 알림 분기를 보강해 미수 복구 흐름과 사용자 안내 메시지의 신뢰도를 높였습니다.