경력 목록

Payment System

GuideME 및 Purple Academy 플랫폼에서 결제부터 예약·정산까지 이어지는 주문 및 결제 라이프사이클 설계/구현

소속
(주)가이드미 · (주)퍼플아카데미
역할
백엔드 엔지니어
기술
PostgreSQL · Toss Payments · NestJS
이 프로젝트에 대해 AI에게 묻기

예약 동시성 제어와 주문·결제 멱등성

출처: 포트폴리오

문제

동일 요청 재시도 시 주문·결제 데이터가 중복 생성되거나 비동기 업데이트가 중복 실행될 위험이 있었고, 테스트 환경에서 같은 시간대에 한 가이드에게 2건의 예약이 동시에 결제 완료되는 문제가 발견되었습니다. 애플리케이션 단의 사전 조회 후 검증만으로는 동시 진입 요청을 완전히 막을 수 없었습니다.

해결

주문 ID 및 결제 ID를 고유 식별자로 삼아 비즈니스 로직에서 1차 검증하고, DB Unique Constraint를 함께 적용해 애플리케이션과 DB 2단계로 멱등성을 보장했습니다. 조회 시점과 저장 시점 사이의 시차(TOCTOU)로 생기는 예약 충돌은 PostgreSQL GiST 인덱스 기반 범위 제약조건(Exclusion Constraint)으로 동일 가이드의 시간 범위(tstzrange)가 겹치는 예약 생성을 DB 차원에서 차단했습니다.

결과

동일 결제 건의 중복 생성 및 재처리를 방지하는 멱등성 구조를 구축했고, 동시 요청 상황에서도 애플리케이션 레이어를 넘어 DB 레벨에서 중복 예약을 100% 차단했습니다.

정산 배치 파이프라인 구축

출처: 포트폴리오

문제

거래 생명주기가 결제 순간에 끝나지 않고 예약 → 서비스 진행 → 정산으로 길게 이어져, 결제 완료 건을 일률 정산하면 이용 취소·환불·미완료 서비스 건까지 잘못 정산될 수 있었습니다.

해결

결제 처리와 정산 시점을 분리하고 거래 라이프사이클을 상태 머신(State Machine)으로 관리했습니다. 매일 새벽 실행되는 배치 작업에서 거래 및 서비스 진행 상태를 검증하고, 결제 완료 + 서비스 정상 완료 + 환불 건 없음 등 복합 조건에 맞는 데이터만 선별해 트랜잭션 단위로 정산했습니다.

결과

예약 상태 변화에 연동되어 정산 적격 데이터만 처리하는 자동화된 정산 배치 시스템을 구축했고, 결제부터 최종 정산까지 전체 거래 흐름을 상태 기반으로 관리해 정산 오류 및 중복/부정 정산 리스크를 제거했습니다.

6개 Toss 상점 분기 로직의 Resolver / Gateway 패턴 유연화

출처: 포트폴리오

문제

퍼플아카데미에서 과세/면세 여부, 일반결제/BrandPay, 상품 카테고리 조합에 따라 총 6개의 Toss 상점 Key를 분기해야 했습니다. Service 레이어에서 if/else로 분기하면 상점이나 결제 수단이 추가될 때 비즈니스 코드와 PG 연동 코드가 얽혀 유연성이 떨어지고 테스트가 어려워집니다.

해결

'어떤 상점을 쓸 것인가(비즈니스 조건)'와 '해당 상점으로 어떻게 결제할 것인가(PG 연동)'의 책임을 분리했습니다. TossStoreResolver가 과세 여부, 결제 수단, 상품 조건을 평가해 storeKey를 도출하고, PgGatewayResolver가 storeKey에 매핑된 TossPaymentGateway 또는 BrandPayGateway 구현체를 반환합니다. 공통 승인/취소 메서드를 인터페이스로 추상화해 PaymentService가 상점의 인증 정보나 PG 구현체를 몰라도 결제를 요청할 수 있게 하여 개방-폐쇄 원칙(OCP)을 지켰습니다.

결과

신규 상점/결제 방식 추가 시 기존 비즈니스 로직 수정 없이 Resolver/Gateway 확장만으로 대응할 수 있는 구조를 확보했습니다.