웹 개발
웹훅 재시도와 사용자 재클릭을 구분하지 못한 주문 처리 실수 정리
같은 주문이 두 번 처리된 원인을 웹훅 재전송과 사용자 재클릭으로 나눠 식별자를 분리한 기록이다.
중복 주문은 한 가지 재현으로 설명되지 않았다
테스트 결제에서 같은 주문 번호가 두 번 처리된 로그를 발견했다. 처음에는 결제 제공업체의 웹훅 재전송으로 결론 내렸지만, 시간순으로 펼쳐 보니 결제 완료 화면 새로고침 요청도 섞여 있었다. 둘 다 중복이지만 막아야 할 지점은 달랐다.
이벤트 ID와 작업 키를 따로 저장했다
웹훅은 제공업체의 이벤트 ID로 같은 이벤트를 한 번만 반영한다. 사용자 요청은 클라이언트가 만든 Idempotency-Key로 재시도 결과를 재사용한다. 주문 번호만 유일하게 만들면 금액이 다른 새 결제까지 막을 수 있었다.
const eventId = request.headers.get('x-provider-event-id')
const operationKey = request.headers.get('idempotency-key')
if (!eventId || !operationKey) {
return Response.json({error: '식별자가 필요합니다.'}, {status: 400})
}
const seen = await db.webhookEvent.findUnique({where: {eventId}})
if (seen) return Response.json({ok: true, duplicate: true})
같은 작업 키에 다른 주문 ID나 금액이 들어오면 409로 거절하도록 저장된 요청 요약도 비교했다. 로그에는 이벤트 ID, 작업 키, 주문 ID, 금액, 처리 결과만 남기고 서명 값과 카드 정보는 기록하지 않았다.
재시도 테스트 목록
- 동일 웹훅 이벤트를 두 번 보내도 상태 변경이 한 번인가
- 타임아웃 직후 같은 작업 키로 다시 보내도 결과가 재사용되는가
- 같은 키에 다른 금액을 넣으면 거절되는가
- 다른 작업 키의 정상 재결제는 막히지 않는가
- 트랜잭션 커밋 전 장애 시 재처리가 가능한가
중복을 막는다는 말만으로는 부족하다. 공급자 이벤트와 사용자의 네트워크 재시도를 같은 키로 뭉뚱그리지 않아야 정상 재시도는 살리고 실제 이중 처리는 차단할 수 있다.