토스페이먼츠를 활용한 포인트 충전 시스템 구축기 - fitpassTeam/fitpass GitHub Wiki

토스페이먼츠를 활용한 포인트 충전 시스템 구축기

문제 : 포인트 충전을 관리자가 수동으로 충전해주는 시스템을 토스페이먼츠로 자동화하기

토스페이먼츠 도입 배경

  • 포인트를 관리자가 직접 충전해주는 수동 시스템에서 자동으로 해주는 토스페이먼츠를 연동함.
  • 사용자가 포인트를 충전하려면 실제 돈으로 결제해야 하기에, 이를 처리하기 위해서 신뢰된 PG사 연동을 해야하기에 도입을 결정
  • Toss Payments는 금융 규제를 준수하는 합법적인 결제 처리 시스템을 제공하고, 테스트 환경을 구축해주기에 결정.
  • 개발자 친화적 API : 직관적인 RESTful API와 상세한 문서 제공
  • 간편한 연동 : Payment Widget을 통한 빠른 프론트엔드 구현

아키텍처 설계

계층형 아키텍처

Controller → Service → Repository → Entity
     ↓
Payment Client (토스페이먼츠 API)

도메인 분리

  • payment : 결제 관련 로직 (토스페이먼츠 연동)
  • point : 포인트 관리 로직 (기존 시스템)분리를 통해서 결제 로직과 포인트 로직의 책임을 명확히 구분하고, 향후 다른 결제 수단 추가시 확장성을 확보했다.

의사결정 배경 및 요구사항

비즈니스 요구사항

  • 결제의 신뢰성 확보

    실제 결제가 이루어진 경우에만 포인트가 정확히 적립되어야 하며, 이중 적립은 절대 불가

  • 사용자 경험 향상

    결제 실패, 취소, 지연 등 모든 상황에 대해 친절하고 명확한 피드백 제공

  • 결제 흐름의 간결성

    사용자 관점에서 복잡하지 않은 결제 절차를 제공하여 전환율(결제 성공률)을 높임

기술적 요구사항

  • 확장성 (멀티 서버 환경 대응)

    여러 서버에서 동시에 결제 callback을 수신하더라도 orderId 기준으로 중복 처리 방지(idempotency)

  • 결제-포인트 트랜잭션 일관성 확보

    Toss 결제 상태와 우리 시스템의 포인트 적립이 반드시 동일한 시점에서 처리되어야 함

    (예: DB 트랜잭션 + Toss confirm API 응답 처리 순서 중요)

  • 장애 대비 안전성 확보

    Toss 결제는 완료됐지만 서버 처리 실패한 경우를 고려해 후속 보정처리 가능하도록 설계

  • 보안 및 위변조 방지

    사용자가 조작한 금액을 그대로 포인트로 적립하는 일이 없도록 Toss의 결제 결과(amount)를 서버에서 항상 검증

  • 유지보수성과 추적성

    각 결제 요청 및 응답, 포인트 적립/환불 기록은 로그로 남기고, 관리자가 추적 가능해야 함


결제 플로우

image.png

토스페이먼츠 API 클라이언트 구현

@Component
@RequiredArgsConstructor
@Slf4j
public class TossPaymentClient {
    
    private final TossPaymentConfig config;
    private final WebClient webClient = WebClient.builder().build();
    private final ObjectMapper objectMapper = new ObjectMapper()
        .registerModule(new JavaTimeModule());
    
    public PaymentResponseDto confirmPayment(PaymentConfirmRequestDto request) {
        try {
            String auth = encodeSecretKey(config.getSecretKey());
            
            String response = webClient.post()
                .uri(TossPaymentConfig.TOSS_CONFIRM_URL)
                .header(HttpHeaders.AUTHORIZATION, "Basic " + auth)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .bodyValue(request)
                .retrieve()
                .bodyToMono(String.class)
                .block();
            
            return objectMapper.readValue(response, PaymentResponseDto.class);
            
        } catch (Exception e) {
            log.error("토스페이먼츠 결제 승인 실패", e);
            throw new RuntimeException("결제 승인 처리 중 오류가 발생했습니다", e);
        }
    }
    
    private String encodeSecretKey(String secretKey) {
        return Base64.getEncoder()
            .encodeToString((secretKey + ":").getBytes(StandardCharsets.UTF_8));
    }
}

장점:

  • 멀티 서버 대응: orderId 기준 중복 결제 방지
  • 세밀한 트랜잭션 처리: DB 트랜잭션과 Toss 응답을 묶어 일관성 확보
  • 친절한 사용자 메시지 제공 가능
  • 보안과 신뢰성 강화: 금액 위변조 방지, 응답 검증

토스페이먼츠를 활용한 포인트 충전 시스템 구축기