재시도와 중복 방지가 있는 서버 측 결제 흐름.
1. ID와 접근 권한 준비하기
활성 스토어와 테스트한 결제 수단으로 시작하세요. 다음에서: 설정 → API 접근필요한 프로젝트로 제한된 읽기/쓰기 자격 증명을 만드세요. 토큰은 백엔드에 보관하고 브라우저 코드나 공개 저장소에 넣지 마세요.
복사하세요: 프로젝트 API ID 및 스토어 API ID 스토어의 다음 위치에서: 기본 → API ID 상자. 이 값은 UUID이며 읽기 쉬운 프로젝트 식별자나 주문 번호가 아니에요. 자체 API 호스트명을 사용하세요.
다음에서: 스토어 → IPN에서 서명 비밀값을 먼저 만든 뒤 다음을 제공하세요: ipn_url판매자 VPS에서 HTTPS 수신기에 접근할 수 있어야 해요.
2. 청구서 만들기
자리표시자를 바꾸고 백엔드에서 이 요청을 보내세요. 금액은 부동소수점 계산이 아닌 십진 문자열이에요.
curl --fail-with-body --request POST \
'https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices' \
--header 'Authorization: Bearer YOUR_MERCHANT_API_TOKEN' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: order-1042-attempt-1' \
--data '{
"amount": "10.00",
"currency": "EUR",
"order_id": "order-1042",
"description": "Example order",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
}
}'저장하세요: data.invoice_id 을 주문에 저장한 뒤 고객을 다음으로 리디렉션하세요: links.checkout새 결제 시도에는 고유 멱등 키를 쓰세요. 시간 초과 시 다음으로 재시도하세요: 같은 자격 증명, 키, 정확히 같은 본문 바이트.
생략하면: payment_methods 스토어의 허용 수단을 사용해요. 체인 슬러그와 티커로 청구서별 범위를 좁힐 수 있지만 허용하지 않은 자산을 켜지는 않아요. 모든 요청 필드와 응답 예시 →
3. IPN, 웹훅 또는 둘 다 선택하기
IPN 는 청구서 수명 주기를 따라요. 스토어 기본 IPN URL을 설정하거나 다음으로 재정의하세요: ipn_url 해당 청구서에 적용해요. 웹훅 은 엔드포인트가 선택한 이벤트를 구독하게 해요. 예: invoice.settled.
둘은 같은 서명 형식을 쓰지만 비밀값은 달라요IPN은 스토어 IPN 비밀값을, 각 웹훅 엔드포인트는 자체 비밀값을 사용해요. 어느 쪽도 API Bearer 토큰으로 서명하지 않아요.
둘 다 앱으로 전달하면 알림이 겹칠 수 있어요. 주문을 두 번 반영하지 마세요.
4. 알림 검증 및 저장하기
- JSON 파싱 전에 정확한 원시 요청 본문을 읽으세요. 다음을 검증하세요:
Wholly-Signature일치하는 비밀값과 타임스탬프·재전송 검사를 사용하세요. 공식 SDK가 검증기를 제공해요. - 서명된 본문의 프로젝트, 스토어, 청구서, 이벤트 식별자를 검증하세요. 서명되지 않은 전달 헤더는 인증 근거가 아니에요.
- 고유한 다음 값으로 이벤트를 영속 저장하세요:
event_id그런 다음 HTTP 2xx를 신속히 반환하세요. 주문은 백그라운드 워커에서 처리하세요. - 요청에 담긴 임의 호스트가 아니라 설정된 API 호스트에서 현재 청구서를 조회하세요. 저장한 프로젝트, 스토어, 금액, 통화, 주문 참조를 비교하세요.
5. 정산 후 한 번만 이행하기
다음 방식에서는: 이벤트 기반 수신기가 처리할 항목: event_type = invoice.settled그리고 현재 다음을 검증하세요: status = settled 과 예외 정책을 확인하세요. 데이터베이스 트랜잭션·고유 주문 제약으로 한 번만 이행하세요.
빠르게 확정되는 체인은 다음 둘을 모두 보낼 수 있어요: payment.received 및 invoice.settled 및 status = settled다른 체인에서는 payment.received가 아직 processing일 수 있어요. 둘 다 오류가 아니에요.
다음으로 이벤트 중복을 제거하세요: event_id순번만 쓰지 마세요. 다른 이벤트 유형이 같은 순번을 공유할 수 있어요. 상태 기반 SDK 처리기는 대신 청구서 리비전을 합치고 이벤트 유형과 무관하게 상태를 확인해요. 이 합치기와 이벤트 유형 필터를 함께 쓰지 마세요. 두 방식 모두 주문 수준 중복 방지가 필요해요.
확인하세요: requires_review 과 수동 해결 결과를 이행 전에 확인하세요. amount_status = paid 만으로 확인이 입증되지는 않아요. 최상위 결제 자산 필드는 정산을 요약하고 payment_info 에는 자세한 수신·견적 데이터가 담겨요. 모든 상태, 이벤트, 예외 규칙 →
6. 재시도와 복구 테스트하기
소액 결제, 중복 전달, 만료 청구서, 일시적으로 사용할 수 없는 수신기를 테스트하세요. 이벤트 재실행이 주문을 두 번 반영하면 안 돼요. 순서가 바뀐 이벤트가 더 최신 상태를 덮어쓰지 않게 처리하세요.
확인하세요: 스토어 → IPN / 웹훅 → 기록 → 상세또는 청구서 상세의 전달 영역을 확인하세요. 재전송은 기록된 이벤트를 재사용하며 새 정산이 아니에요.
처리 크레딧이 부족하면 IPN/웹훅은 멈추지만 결제는 계속돼요. API로 미완료 주문을 대사하고 복구 후 보존된 전달을 처리하세요. 브라우저 리디렉션이나 고객 스크린샷으로 주문을 이행하지 마세요.