← 프로젝트 목록

결제(Payment)

멀티 PG 2사 × 7종 결제수단 — 이중결제 0건

PayPal APIEximbay APIPorts & AdaptersStrategy PatternPessimistic Lock

배경 및 제약조건

  • PayPal(글로벌) + Eximbay(아시아/카드/알리페이/그랩페이/클라나) 2개 PG사 연동
  • 결제 수단 7종: PayPal, Credit Card, Alipay+, GrabPay, Klarna, Apple Pay(향후), Point
  • 3가지 결제 경로: 포인트 전액(PG 불필요), BillingKey 즉시(서버→서버), 클라이언트 리다이렉트
  • 결제 전 관세/배송비/재고/국가 제한/블랙리스트 재검증 필요 (DDP 국가 대응)
  • BillingKey 기반 간편 결제(Vault) — 원클릭 결제 + 이력 관리

설계 구조

3가지 결제 경로
├── [경로 1] 포인트 전액 → PG 불필요 → completeImmediately()
├── [경로 2] BillingKey → 서버→서버 직접 결제 → ServerComplete
└── [경로 3] 클라이언트 → PG 리다이렉트 → 콜백 대기 → confirm()

PG 추상화 (Ports & Adapters)
├── PaymentGateway (Port) — initiate + confirm 2단계
├── PaymentGatewayRoutingAdapter — method의 gateway 타입으로 라우팅
├── PaypalPaymentAdapter — Orders API v2 + Capture
├── EximbayPaymentAdapter — Form params + Webhook 서명 검증
└── PaymentStatusQueryRouter — PG 결제 상태 직접 조회 (복구/보호용)

결제 검증 파이프라인 (결제 직전)
├── TaxShippingRevalidator — DDP 국가 관세/배송비 재계산
├── PaymentValidator — 금액/재고/국가 제한/블랙리스트
└── PricingConsistencyPolicy — 주문 시점과 결제 시점 가격 변동 감지

Problem & Solution

Problem

Eximbay notify_url(서버→서버)과 return_url(리다이렉트)이 거의 동시에 도착하여 결제 완료 처리 2회 실행

Solution

SELECT FOR UPDATE 비관락으로 동시 접근 직렬화. payment.isCompleted() 멱등성 체크 — 먼저 잡은 쪽만 처리, 두 번째는 즉시 성공 결과 반환

Problem

CompletePayment(결제 확정)와 cancelBeforePayment(주문 취소)가 동시에 같은 주문에 접근 — 결제됐는데 주문 취소되는 사고

Solution

두 서비스 모두 orderRepository.findByIdForUpdate()로 같은 비관락 경합. 먼저 잡은 쪽이 처리, 뒤에 온 쪽은 ApplicationConflictException. 취소 시 PaymentStatusQueryRouter로 PG 확인하여 이미 결제됐으면 취소하지 않음

Problem

주문 생성 후 결제 전까지 UPS/DHL 관세 API 응답이 달라질 수 있음 (DDP 국가) — 금액 불일치

Solution

TaxShippingRevalidator가 결제 직전에 현재 기준으로 재계산. 주문 저장값과 비교하여 허용 오차 초과 시 결제 차단, 카트로 리다이렉트

Problem

결제수단(PayPal/Card/Alipay)과 PG사(PayPal/Eximbay)가 N:M 관계 — 하드코딩 시 확장 불가

Solution

PaymentMethod = (PaymentMethodType, PaymentGatewayType) 분리. 레거시 호환 fromLegacy()/toLegacySettleCase() 양방향 변환. PaymentGatewayRoutingAdapter가 gateway 타입으로 라우팅

Problem

PG 거절(카드 한도, 잔액 부족)과 시스템 에러(네트워크, 타임아웃)가 같은 로그에 섞여 모니터링 노이즈

Solution

PaymentErrorRecord로 에러를 타입별(BEFORE_CAPTURE/AFTER_CAPTURE/PG_DECLINED/SYSTEM_ERROR) 분류. PG 거절은 별도 테이블에 기록하여 시스템 에러 로그와 분리

기술적 챌린지

결제 실패 보상 처리

CreatePayment 실패 → 프론트에 에러 → cancelBeforePayment. CompletePayment 실패 → PaymentDeclinedException → /error/{pg}. PG 응답 지연 → 20분 만료 스케줄러 (정리 전 PG 상태 확인으로 보호)

성과

  • Checkout 이탈률 84.3% 감소에 기여 — 결제 안정성 및 검증 강화
  • 이중결제 0건, 금액 불일치 0건 달성
  • 멀티 PG 2사 × 7종 결제수단 안정 운영
  • PG 추가 시 Adapter만 구현하면 되는 확장 가능 구조
  • 결제 전 다단계 검증(관세/배송비/재고/국가/블랙리스트)으로 결제 후 문제 사전 차단
  • PG 에러 분류로 모니터링 노이즈 제거
  • 간편결제 UX 최적화 (원클릭 + 최근 결제 수단 자동 선택)