개요
주문형 구독을 사용하면 고객의 결제 수단을 한 번 승인한 후, 고정된 일정 대신 필요할 때마다 변동 금액을 청구할 수 있습니다. 이 기능은 모든 계정에서 사용할 수 있으며 별도의 승인이 필요하지 않습니다. 이 가이드를 사용하여 다음을 수행할 수 있습니다:- 주문형 구독 생성(선택적 초기 가격과 함께 mandate 승인)
- 사용자 지정 금액으로 후속 청구 실행
- webhook을 사용하여 결과 추적
사전 요구 사항
- Dodo Payments merchant 계정 및 API key
- webhook secret이 구성되어 있고 이벤트를 수신할 endpoint
- 카탈로그에 등록된 구독 상품
주문형 작동 방식
on_demand객체를 사용하여 구독을 생성하고 결제 수단을 승인하며, 선택적으로 초기 청구 금액을 수금합니다.- 이후 전용 charge endpoint를 사용하여 사용자 지정 금액으로 해당 구독에 대한 청구를 생성합니다.
- webhook(예:
payment.succeeded,payment.failed)을 수신하여 시스템을 업데이트합니다.
주문형 구독 생성
Endpoint: POST /checkouts 주요 request fields (body):Create Checkout Session에서 확인하세요.
주문형 구독 생성
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
주문형 구독 청구
mandate가 승인되면 필요에 따라 청구를 생성합니다. Endpoint: POST /subscriptions/{subscription_id}/charge 주요 request fields (body):Charge request body parameters
Charge request body parameters
integer
필수
청구할 금액(최소 통화 단위). 예를 들어 $25.00을 청구하려면
2500을 전달합니다.string
청구에 사용할 선택적 통화 재정의입니다.
string
이 청구에 사용할 선택적 설명 재정의입니다.
boolean
true인 경우
product_price에 Adaptive Currency 수수료가 포함됩니다. false인 경우 수수료가 별도로 추가됩니다.object
이 청구를 정산하는 데 고객의 wallet balance가 어떻게 사용되는지 지정합니다.
object
결제에 대한 추가 metadata입니다. 생략하면 subscription metadata가 사용됩니다.
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
실패한 청구 처리
온디맨드 subscription에 대한 청구가 실패하면 다음에 수행할 작업을 직접 결정합니다. 예약된 subscription에서는 갱신 실패 시 이후 자동 billing이 중단되지만, 온디맨드 subscription은 실패 후에도 청구할 수 있습니다. 자체 retry 로직의 일부로 charge endpoint를 다시 호출할 수 있습니다.실패 시 발생하는 일
1
Charge attempt fails
POST /subscriptions/{subscription_id}/charge 요청은 error response를 반환하거나 비동기적으로 완료되며, 거절 사유가 포함된 payment.failed webhook을 발생시킵니다.2
Subscription may transition to on_hold
subscription이
on_hold 상태로 이동하고 subscription.on_hold webhook을 발생시킬 수 있습니다(Subscription States → On Hold 참조). 이는 신호일 뿐 잠금이 아닙니다. 온디맨드 subscription에서는 on_hold이 다시 청구하는 것을 방지하지 않습니다.3
Retry the charge (your call)
온디맨드 flow에서는 Dodo가 자동 retry를 수행하지 않습니다. 언제든
POST /subscriptions/{subscription_id}/charge을 다시 호출하여 retry할 수 있습니다. 아래의 safe retry policy를 적용하세요. exponential backoff를 사용하고, hard decline은 건너뛰며, burst pattern을 피해야 fraud 및 risk system에서 retry를 플래그하지 않습니다.4
Optionally, ask the customer for a new payment method
결제 수단 자체의 문제(만료된 카드, 폐쇄된 계정 등)로 retry가 계속 실패하면
POST /subscriptions/{subscription_id}/update-payment-method을 사용하여 고객으로부터 새 결제 수단을 수집하세요. 성공하면 subscription이 active으로 돌아가고, payment.succeeded webhook과 subscription.active webhook이 차례로 발생합니다.온디맨드와 예약 방식 비교: 예약된 subscription에서는 Dodo가 자체 renewal retry와 dunning을 수행합니다. 온디맨드 subscription에서는 다음 청구가 언제 이루어져야 하는지 아는 주체가 여러분뿐이므로 retry policy를 직접 관리합니다(달력이 아니라 usage event에 의해 결정됨).
실패한 온디맨드 청구의 Webhook sequence
Event 3과 4는 후속 청구가 성공한 경우에만 발생합니다.
Retry 책임
Subscription Dunning(기본 제공 email recovery sequence)은 예약된 subscription의 실패한 renewal payment와 고객이 시작한 cancellation에 적용됩니다. 온디맨드 청구 실패를 위한 기능은 아닙니다. 결제 수단을 업데이트해야 한다고 판단되면 고객에게 직접(예: transactional email 또는 인앱 prompt) 안내하세요.Payment retry
fraud detection system은 공격적인 retry pattern을 차단할 수 있으며, 이를 잠재적인 card testing으로 플래그할 수도 있습니다. safe retry policy를 따르세요.Safe retry policy의 원칙
- Backoff mechanism: retry 사이에 exponential backoff를 사용합니다.
- Retry limits: 전체 retry 횟수를 제한합니다(최대 3~4회 시도).
- Intelligent filtering: retry 가능한 실패(예: network/issuer error, insufficient funds)에만 retry하고, hard decline은 절대 retry하지 않습니다.
- Card testing 방지:
DO_NOT_HONOR,STOLEN_CARD,LOST_CARD,PICKUP_CARD,FRAUDULENT,AUTHENTICATION_FAILURE과 같은 실패에는 retry하지 않습니다. - Metadata 변경(선택 사항): 자체 retry system을 운영하는 경우 metadata를 사용하여 retry를 구분합니다(예:
retry_attempt).
권장 retry schedule (subscription)
- 1차 시도: charge 생성 시 즉시
- 2차 시도: 3일 후
- 3차 시도: 추가 7일 후(총 10일)
- 4차 시도(최종): 다시 7일 후(총 17일)
Burst retry를 피하고 authorization 시간에 맞추기
- portfolio 전체에서 “burst” 동작을 피하려면 retry를 최초 authorization timestamp에 맞춥니다.
- 예: 고객이 오늘 오후 1시 10분에 trial 또는 mandate를 시작했다면 backoff에 따라 이후 날짜의 오후 1시 10분에 후속 retry를 예약합니다(예: +3일 → 오후 1시 10분, +7일 → 오후 1시 10분).
- 또는 마지막 성공 payment time을
T에 저장한다면, 시간대 정렬을 유지하도록 다음 시도를T + X days에 예약합니다.
Time zone 및 DST: schedule에 일관된 time standard를 사용하고, 간격을 유지하기 위해 표시할 때만 변환합니다.
Retry하지 않아야 하는 Decline code
STOLEN_CARDDO_NOT_HONORFRAUDULENTPICKUP_CARDAUTHENTICATION_FAILURELOST_CARD
전체 decline reason 목록과 사용자가 수정할 수 있는지 여부는
Transaction Failures 문서를 참조하세요.
Implementation guideline (code 없음)
- 정확한 timestamp를 유지하는 scheduler/queue를 사용하고, 정확한 시간대 offset에 다음 시도를 계산합니다(예: 같은 HH:MM의
T + 3 days). - 마지막 성공 payment timestamp
T을 유지하고 참조하여 다음 시도를 계산합니다. 여러 subscription을 동일한 시각에 몰아서 처리하지 마세요. - 항상 마지막 decline reason을 평가하고, 위 skip list의 hard decline에 대해서는 retry를 중단합니다.
- 우발적인 surge를 방지하기 위해 고객별 및 account별 동시 retry 수를 제한합니다.
- 선제적으로 안내합니다. 다음 예정된 시도 전에 고객에게 email/SMS를 보내 결제 수단을 업데이트하도록 합니다.
- metadata는 observability 용도로만 사용합니다(예:
retry_attempt). 중요하지 않은 field를 변경하여 fraud/risk system을 “회피”하려고 해서는 안 됩니다.
Cancellation
온디맨드 subscription은 즉시 종료 날짜를 정할 기준이 되는 고정 billing cycle이 없으므로 예약된 subscription과 다른 cancellation flow를 따릅니다.Customer portal 동작
고객이 Customer Portal에서 온디맨드 subscription을 취소하면 기본적으로 다음 billing date에 cancellation이 예약됩니다. 온디맨드 subscription에는 Cancel Now option이 의도적으로 표시되지 않습니다. 그 이유는 온디맨드 subscription에는 예측 가능한 반복 renewal date가 없고, 다음 charge time이 전적으로 usage event에 의해 결정되기 때문입니다. 다음 billing date에 cancellation을 예약하면 period boundary까지 mandate가 활성 상태로 유지되어 진행 중인 usage도 계속 청구할 수 있으며, 이후 subscription이 정상적으로 종료됩니다. 고객이 cancellation을 확인한 후:- subscription은
active상태를 유지하며, 예약된 cancellation date까지POST /subscriptions/{id}/charge를 통해 계속 청구할 수 있습니다. - subscription의
cancel_at_next_billing_date이true로 설정됩니다. - cancellation이 적용되면
subscription.cancelledwebhook이 발생합니다.
subscription을 즉시 종료해야 하는 경우(예: refund 또는 support request에 대한 대응), customer portal flow에 의존하지 말고 API를 통해 programmatically 취소하세요.
Programmatically 취소
언제든 API를 통해 온디맨드 subscription을 취소할 수 있습니다. cancellation을 즉시 적용할지 예약할지 직접 제어합니다. Endpoint: PATCH /subscriptions/{subscription_id}- Cancel immediately
- Cancel at next billing date
subscription의
status을 cancelled로 설정하면 즉시 종료됩니다. mandate가 취소되고 더 이상 charge를 생성할 수 없습니다.cURL
Cancellation 시 Webhook
Webhook으로 결과 추적
고객 여정을 추적할 수 있도록 webhook handling을 구현하세요. Implementing Webhooks을 참조하세요.- subscription.active: Mandate가 authorization되고 subscription이 활성화됨
- subscription.failed: 생성 실패(예: mandate failure)
- subscription.on_hold: subscription이 보류 상태가 됨(예: unpaid state)
- subscription.cancelled: subscription이 완전히 취소됨(Cancellation 참조)
- payment.succeeded: charge 성공
- payment.failed: charge 실패
Testing 및 다음 단계
1
Create in test mode
test API key를 사용하여 subscription을 생성한 다음 반환된
checkout_url을 열고 mandate를 완료하세요.2
Trigger a charge
작은
product_price(예: 100)로 charge endpoint를 호출하고 payment.succeeded을 수신하는지 확인하세요.3
Go live
Event와 내부 state update를 검증한 후 live API key로 전환하세요.
Troubleshooting
- 422 Invalid Request:
on_demand.mandate_only이 생성 시 제공되고,product_price가 charge에 제공되는지 확인하세요. - Currency error:
product_currency을 override하는 경우 account와 customer에 지원되는 currency인지 확인하세요. - Webhook을 수신하지 못함: webhook URL과 signature secret configuration을 확인하세요.