Skip to content

아름의 고민과 해결

aoooec edited this page Jun 4, 2025 · 4 revisions

결제 시스템 구현: PG 선택부터 데이터 저장 전략까지

🚩 배경

핵심 서비스인 유료 챌린지를 위해 결제 기능을 구현해야 했다. 이 과정에서 어떤 PG 솔루션을 선택해야 하는지, 그리고 결제 기능 구현시 임시 데이터와 결제 상태를 어떻게 관리해야 하는지에 대한 고민이 있었다.

🚩 PG 솔루션 선택

PG 솔루션 옵션을 선택하는 것에는 두 가지 주안점이 있었다.

다중 PG 솔루션 연동 여부

사용자 입장에서는 다양한 결제 옵션을 제공하는 것이 좋기 때문에 다중 PG 솔루션을 제공할지, 단일 PG 솔루션을 사용할지부터 고민해야 했다. 다중 PG 솔루션을 연동한다면 직접 PG 솔루션을 하나하나 학습하고 연결하는 방법과 통합 결제 솔루션인 PortOne을 사용하는 방법이 있었다.

PG_솔루션_비교

직접 연동하는 방법은 각각의 PG 솔루션 API/SDK를 학습하고 모든 예외 케이스를 처리해야 하므로, PortOne을 사용하는 것이 초기 리소스 측면에서 더 나은 선택처럼 보였다. 다양한 PG 솔루션을 한 번에, 그리고 쉽게 연동할 수 있도록 서비스하기 때문에 개발 가이드와 샌드박스도 아주 잘 제공되어 있었다. 그러나 실제 서비스 흐름과 요구사항을 고려했을 때, 최종적으로는 PortOne 대신 단일 PG 연동 방식을 선택했다.

결제 승인 + 환급 로직

결제_플로우_검증을_위한_토스페이먼츠_선택

우리 서비스는 단순히 결제 완료로 끝나는 것이 아니라 사용자가 유료 챌린지를 성공했을 경우 참가비를 환급(부분 환불)해주어야 한다. 그래서 결제 → 챌린지 성공 → 부분 환불 로직에 대한 전체 플로우 검증이 중요했다.

따라서 핵심 플로우를 검증하고 안정적으로 운영하기 위해 복잡도를 낮출 수 있는 단일 PG 솔루션 연동을 선택했다. 그 중에서도 개발 가이드 및 샌드박스 제공이 잘 되어있어 테스트 API Key 하나로 프로덕션 환경처럼 검증할 수 있는 Toss Payments를 최종 선택하게 되었다.

🚩 임시 주문서 저장 전략

우리는 유료 챌린지 결제를 위해 외부 결제 시스템인 Toss Payments를 연동해 사용하고 있다. 사용자가 결제를 시도할 경우 고유한 orderId를 생성하고, 이 값을 기반으로 결제 완료 및 데이터 정합성 검증을 수행한다.

저장해야 하는 데이터 vs 저장하면 안되는 데이터

결제_승인_플로우

orderId는 결제 요청 시 생성하여, 서버 입장에서는 검증 단계에서 단기적으로 사용하는 데이터로 결제 흐름이 종료되면 삭제해도 되는 일시적 식별자에 해당한다. 그러나 정합성 검증 및 처리에 필요하기 때문에 서버가 이 값을 저장하지 않고 언제든지 데이터 변경이 가능한 프론트 단에서만 전달받아 사용한다면 문제가 발생할 수 있다.

예를 들어 Toss 결제는 성공했지만, 금액(amount)이 조작된 채 우리 서버에 요청이 들어올 수 있다. 따라서 orderId를 비롯해 정합성 검증에 필요한 임시 주문서를 결제 시도 시점에 저장해야 한다는 결론에 도달했다.

어디에, 어떻게?

임시 주문서 데이터는 정합성 검증을 위해 반드시 필요하지만, 동시에 검증이 끝나면 불필요해지는 일시적 데이터이다. 만약 사용자가 결제 진행 중 실제 결제까지는 진행하지 않고 이탈한다면 실제 주문서 데이터도 생성되지 않기 때문에 완벽한 고아 데이터가 되어버리고 만다.

더불어 외부 결제 API를 연동하여 사용하기 때문에 지연 허용 범위가 짧으므로 데이터를 가져오는 응답 속도도 중요했다.

따라서 아래와 같이 임시 주문서 데이터 저장 방식 세 가지를 고려했다.

저장_전략_비교

우선 순수 RDBMS는 영속성·정합성 측면에서 우수하지만 임시 데이터 저장이라는 목적에는 적합하지 않았다. 누적 데이터 정리가 어렵고 디스크 기반 저장이므로 응답 속도가 상대적으로 느리기 때문이다.

다음으로 서버 세션은 자동적으로 데이터가 휘발되고 구현이 간단하지만 확장성이 낮고 서버 재시작시 데이터가 유실된다는 단점이 있어 추후 서비스 확장을 고려하여 부적합하다고 판단했다.

마지막으로 Redis는 운영 복잡도는 증가할 수 있지만 인메모리 기반 key-value 형태를 가진 데이터베이스이기 때문에 응답 속도가 빠르고 TTL 지정이 가능하므로 임시 데이터를 저장하기에 적합하다고 판단했다.

최종적으로 임시 주문서는 Redis에 TTL 15분으로 저장하여 빠른 응답성을 확보하고 고아 데이터를 방지하며, 모든 결제 프로세스가 완료된 경우 RDB에 실제 주문서 데이터를 영속 저장하여 데이터 무결성을 보장하는 하이브리드 방식을 채택하게 되었다.

💪 앞으로의 과제

위와 같이 임시 주문서 저장 전략을 Redis 기반으로 수립하였으나, 이 전략만으로는 결제 실패 복구나 CS 측면에서 충분히 대응할 수 없다는 문제가 발생했다. 따라서 결제 전체 흐름을 통제하기 위해서는 전체 결제 과정 상태를 저장하는 별도의 영속 데이터가 필요하다는 결론에 이르게 되었다.

만약 Toss 결제 성공 → 주문서 DB 저장 실패 → 보상 트랜잭션 흐름으로 결제 실패가 된 경우는 ROLLBACK으로 인해 주문서 데이터가 존재하지 않으므로 이후 보상 트랜잭션을 실행할 근거가 없어 실질적 장애로 이어질 수 있기 때문이다. 실제 금전이 오간 기록이 부재하다면 회사 측면에서도, 그리고 고객 CS 대응에 있어서도 큰 문제가 발생할 수 있다.

이와 같은 문제를 방지하기 위해 Redis를 통해 임시 주문서를 저장하고 결제 흐름을 통제하며, 추가적으로 영속 결제(상태) 데이터를 저장하여 장애 복구 및 추적 가능성을 확보할 예정이다.

Oneul

📔 개발 위키

Clone this wiki locally