환불 복구
환불 권위는 refund_attempts(마이그레이션 019)예요. 시도 레코드가 PG 호출보다 먼저 기록돼요 — 미기록 외부 환불은 존재할 수 없어요(REFUND-1). 잔여 한도의 권위는 비-failed 시도의 SUM(amount_krw)예요.
시도 수명주기
attempted → confirmed (PG 취소 확정 + provider_ref) → succeeded (내부 효과 적용)
→ needs_review (불확실/미귀속 — 재조회로만 해소, 추측 확정 금지)
→ failed (종단)1
2
3
2
3
idempotency_key— 같은 키 + 같은 요청 지문은 기록된 결과 재생, 다른 지문은409 IDEMPOTENCY_CONFLICT.provider_ref— UNIQUE. reconcile이 PG 취소 거래를 시도별로 귀속해요.- 비종단 시도가 있으면 같은 주문의 새 시도는
409 REFUND_IN_PROGRESS— 이중 환불 방어선이에요.
복구 경로
| 상황 | 조치 |
|---|---|
attempted 5분+ 정체(비-toss) | 다음 환불/reconcile이 자동 failed 정리 — 외부 호출이 없었으므로 안전 |
attempted/needs_review(toss) | RECHECK_PROVIDER_REFUND op — GET /v1/payments/{key}만. 금액 매칭 transactionKey로 재귀속, 귀속 불가는 needs_review 유지 |
confirmed + 내부 효과 미적용 | APPLY_CONFIRMED_REFUND_INTERNAL op — apply_refund_effects 재실행(원장 idem_key가 이중 적용 불가 보장) |
| 웹훅 취소 선도착 | apply_provider_cancel이 confirmed 시도 선기록 후 내부 적용 |
타임아웃 후 환불을 맹목적으로 재시도하지 마세요 — PG 취소가 이미 성공했을 수 있어요(이중 환불). refund.attempt/refund.apply/license.revoke 증적은 체인 봉인돼요 (audit-evidence).