본문 바로가기

Project

결제 시나리오 2. 중복 결제 방지

배경

중복 결제에 대해 고민을 하게된 이유는 처음 결제 시스템을 개발할 때 중복 결제가 실제로 발생할 가능성이 있을까? 라는 질문에서 시작했습니다. 아직 서비스가 운영되지 않았기 때문에 실제 사례는 없었지만 한 번이라도 중복 결제가 발생하면 사용자 신뢰도에 치명적인 영향을 미칠 수 있다 생각했습니다.

결제는 단순한 API 호출이 아니라 사용자의 돈이 직접적으로 움직이는 중요한 프로세스 입니다. 따라서 이런 문제가 발생할 수도 있다는 가정만으로도 사전 대비가 필요했다고 판단했습니다.

특히 토스페이먼츠를 연동하는 과정에서 짧은 시간 안에 동일한 결제 요청이 여러 번 들어올 경우 어떻게 처리될까? 라는 의문이 들었습니다.

  • 사용자가 실수로 결제 버튼을 여러 번 클릭하면?
  • 네트워크 지연으로 인해 서버가 응답을 받지 못하고 동일 요청을 재전송하면?

이런 상황에서 토스페이먼츠의 PaymentKey 중복 방어 기능이 충분한가? 아니면 추가적인 대비책이 필요할까?
추가적인 대비책으로는 멱등키를 결제 승인 요청에도 적용해야 할까? 라는 고민에서부터 이번 의사 결정이 시작되었습니다.


선택지

  1. PaymentKey를 활용한 중복 방지
    • 토스 페이먼츠의 PaymentKey는 결제 인증 단계에서 중복 요청을 방지하는 기능을 기본적으로 제공합니다.
    • 동일한 PaymentKey로 승인 요청이 들어오면, PG에서 자체적으로 방어하여 이미 승인된 결제라고 응답이 옵니다.
  2. 클라이언트 레벨에서 중복 요청 방지
    • 결제 버튼을 여러 번 클릭하는 경우를 방지하기 위해, 결제 버튼을 비활성화하는 등의 UX 개선
    • 네트워크 장애로 인해 동일 요청이 재전송될 경우를 대비해서 프론트엔드에서 일정 시간 동안 중복 요청을 차단하는 로직도 필요합니다.
  3. 멱등키 활용
    • 멱등키를 활용하면 같은 요청이 여러 번 들어와도 한 번만 처리됨을 보장할 수 있습니다.
    • 멱등키를 보관할 Redis 등이 필요.

현재 백엔드에서 서비스가 운영중이기 때문에 PaymentKey만을 활용하여 중복 요청을 방지하고 있는데 프론트엔드도 참여한다면 UX도 개선할 예정입니다.

그럼 멱등키는 사용해야할까?

만약 멱등키를 도입하게 된다면

  1. 결제 승인 요청마다 새로운 멱등키를 생성해야 합니다.
  2. 멱등키를 DB나 Redis에 저장하고 유효기간을 설정해야합니다.
  3. 중복 요청이 들어오면 이전에 사용된 멱등키를 조회하여 동일 요청인지 확인을 합니다.

하지만 고민이 있었습니다.

  1. 이미 PaymentKey와 클라이언트에서 중복 방어 기능을 제공하고 있는데 추가적인 멱등키가 필요할까?
  2. 멱등키 도입을 위해 Redis를 추가하는 것이 적절한가?
  3. 삼중 보호 장치가 필요할 정도로 중보 결제 요청이 빈번한가?

현재 구조에서는 토스페이먼츠 PG에서 제공하는 PaymentKey 자체가 멱등성을 보장해줍니다. 동일한 PaymentKey로 승인 요청이 들어오면 페이먼츠 측에서 이를 감지하여 중복처리를 막아줍니다. 따라서 결제 승인 단계에서는 추가적인 멱등키 없이도 충분히 중복 결제를 방지할 수 있습니다. 추가로 클라이언트에서도 결제 버튼을 한 번 누르면 즉시 비활성화 시키는 것을 추가하면 사용자의 실수로 인한 중복 요청을 차단합니다.

이러한 방어 로직이 이미 2개나 존재하므로 멱등키를 추가로 도입하는 것은 불필요한 복잡성을 초래로 오히려 오버 엔지니어링이 될 가능성이 크다고 판단했습니다.


결론 및 추후 고려 사항

(1) 멀티 PG(Payment Gateway) 지원

  • 현재는 토스페이먼츠만 사용하지만, 향후 여러 PG를 도입할 경우 다른 PG가 PaymentKey를 제공하지 않는다면 멱등키가 필요할 가능성이 있습니다.

(2) 부분 취소(Partial Refund) 도입 시 중복 요청 문제

  • 현재는 전체 취소만 지원하므로 PaymentKey만으로도 충분합니다.
  • 하지만 부분 취소가 가능해지면, 클라이언트의 재시도 로직 때문에 중복 취소가 발생할 위험이 있음.
  • EX
    1. 사용자 결제 완료 (100,000원)
    2. 사용자 A 상품(30,000원) 부분 취소 요청
    3. PG에서 취소 성공 → 하지만 네트워크 오류로 클라이언트가 실패 응답을 받음
    4. 클라이언트에서 재시도 로직 실행 → 다시 30,000원 취소 요청
    5. 결국 60,000원이 취소되는 문제 발생
    • 따라서 부분 취소를 지원할 경우, 반드시 멱등키를 도입하여 중복 요청을 방지해야 합니다.