이전에 LLM 기반 결제 시스템 설계: 토큰 계산과 동시성 제어 전략 ⭐ 에서 보상 트랜잭션 패턴을 적용한 사례를 살펴보겠습니다.
결제 시스템에서 가장 중요한 것은 금전 거래의 일관성입니다.
<aside> 💡
"결제는 승인되었는데 구독은 생성되지 않았다." "돈은 빠져나갔는데 토큰은 지급되지 않았다."
</aside>
이런 상황은 절대 발생해서는 안 됩니다. 하지만 100% 완벽한 시스템은 존재하지 않습니다. 네트워크는 끊길 수 있고, 데이터베이스는 타임아웃될 수 있으며, 외부 API는 예기치 않게 실패할 수 있습니다.
이 글에서는 결제 승인 후 내부 비즈니스 로직이 실패했을 때, 어떻게 사용자의 금전적 피해를 방지하고 시스템의 일관성을 보장했는지 공유합니다.

@Transactional(timeout = 15)
public KcpCardReturnResponse returnPaymentTest(KcpCardReturnCommand command) {
String orderNo = command.getOrderNo();
// 1. 빠른 존재 유무 체크 (2차 방어)
if (isAlreadyProcessed(orderNo)) {
return buildResponse(orderNo);
}
// 2. 콜백 데이터 검증
AiAdverSubscriptionHist pendingHist = getPendingSubscriptionHist(orderNo);
// 3. 적절한 처리기 선택 (신규 구독/구독 변경)
PaymentProcessor processor = processorFactory.getProcessor(orderNo);
// 4. 가격 검증
processor.validatePaymentAmount(pendingHist, command.getAmount());
// 5. 결제 정보 저장 (공통 정보 + 카드 상세 정보)
processor.savePaymentInfo(command);
// 6 ~ 10. 별도 저장 처리
paymentTransactionService.processPaymentAndSubscription(pendingHist, processor);
return buildResponse(orderNo);
}
모든 로직이 하나의 트랜잭션에 묶여 있습니다. 이것이 왜 문제가 되는지 하나씩 살펴 보겠습니다.
이를 해결 하기 위해 읽기 전용 트랜잭션으로 분리하면 변경 감지가 비활성화되고, MVCC 기반 스냅샷만 참조하여 동시성을 향상 시킬수 있습니다.
외부 PG사 API 호출이 트랜잭션 내부에 있습니다.