نظرة عامة
تتيح لك الاشتراكات عند الطلب تفويض وسيلة الدفع الخاصة بالعميل مرة واحدة ثم تحصيل مبالغ متغيرة كلما احتجت، بدلاً من جدول زمني ثابت. هذه الميزة متاحة لجميع الحسابات - لا حاجة لموافقة.
استخدم هذا الدليل لـ:
إنشاء اشتراك عند الطلب (تفويض تفويض مع سعر أولي اختياري)
تحفيز الرسوم اللاحقة بمبالغ مخصصة
تتبع النتائج باستخدام الويب هوكس
لإعداد اشتراك عام، راجع دليل تكامل الاشتراك .
المتطلبات الأساسية
حساب تاجر دودي بايمنتس ومفتاح API
سر الويب هوك مُعد ونقطة نهاية لاستقبال الأحداث
منتج اشتراك في كتالوجك
إذا كنت تريد من العميل الموافقة على التفويض عبر الخروج المستضاف، قم بتعيين payment_link: true وقدم return_url.
كيف تعمل الاشتراكات عند الطلب
تقوم بإنشاء اشتراك باستخدام كائن on_demand لتفويض وسيلة الدفع وجمع رسوم أولية اختيارية.
لاحقًا، تقوم بإنشاء رسوم ضد ذلك الاشتراك بمبالغ مخصصة باستخدام نقطة نهاية الرسوم المخصصة.
تستمع إلى الويب هوكس (مثل payment.succeeded، payment.failed) لتحديث نظامك.
إنشاء اشتراك عند الطلب
نقطة النهاية: POST /checkouts
حقول الطلب الرئيسية (الجسم):
يرجى العثور عليها في إنشاء جلسة الخروج
إنشاء اشتراك عند الطلب
Node.js SDK
Python SDK
Go SDK
cURL
import DodoPayments from 'dodopayments' ;
const client = new DodoPayments ({
bearerToken: process . env . DODO_PAYMENTS_API_KEY ,
environment: 'test_mode' , // defaults to 'live_mode'
});
async function main () {
const subscription = await client . checkoutSessions . create ({
product_cart: [{ product_id: 'pdt_123' , quantity: 1 }],
billing_address: { city: 'SF' , country: 'US' , state: 'CA' , street: '1 Market St' , zipcode: '94105' },
customer: { customer_id: 'cus_123' },
return_url: 'https://example.com/billing/success' ,
subscription_data: {
on_demand: {
mandate_only: true // set false to collect an initial charge
// product_price: 1000, // optional: charge $10.00 now if mandate_only is false
// product_currency: 'USD',
// product_description: 'Custom initial charge',
// adaptive_currency_fees_inclusive: false,
}
}
});
console . log ( subscription . checkout_url );
}
main (). catch ( console . error );
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token = os.environ.get( 'DODO_PAYMENTS_API_KEY' ),
environment = "test_mode" , # defaults to "live_mode"
)
checkout = client.checkout_sessions.create(
product_cart = [
{ "product_id" : "pdt_123" , "quantity" : 1 }
],
billing_address = {
"city" : "SF" ,
"country" : "US" ,
"state" : "CA" ,
"street" : "1 Market St" ,
"zipcode" : "94105" ,
},
customer = {
"customer_id" : "cus_123" ,
},
return_url = "https://example.com/billing/success" ,
subscription_data = {
"on_demand" :{
"mandate_only" : True , # set False to collect an initial charge
# "product_price": 1000, # optional: charge $10.00 now if mandate_only is false
# "product_currency": "USD",
# "product_description": "Custom initial charge",
# "adaptive_currency_fees_inclusive": False,
}
},
)
print (checkout.checkout_url)
package main
import (
" fmt "
" os "
dodo " github.com/dodopayments/dodopayments-go "
)
func main () {
client := dodo . NewClient ( dodo . Config {
BearerToken : os . Getenv ( "DODO_PAYMENTS_API_KEY" ),
Environment : dodo . TestMode , // defaults to LiveMode
})
checkout , err := client . CheckoutSessions . Create ( dodo . CheckoutSessionCreateParams {
ProductCart : [] dodo . ProductCartItem {
{
ProductID : "pdt_123" ,
Quantity : 1 ,
},
},
BillingAddress : & dodo . BillingAddress {
City : "SF" ,
Country : "US" ,
State : "CA" ,
Street : "1 Market St" ,
Zipcode : "94105" ,
},
Customer : & dodo . CustomerRef {
CustomerID : "cus_123" ,
},
ReturnURL : "https://example.com/billing/success" ,
SubscriptionData : & dodo . SubscriptionData {
OnDemand : & dodo . OnDemandSubscription {
MandateOnly : true , // set false to collect an initial charge
// ProductPrice: 1000, // optional: charge $10.00 now if mandate_only is false
// ProductCurrency: "USD",
// ProductDescription: "Custom initial charge",
// AdaptiveCurrencyFeesInclusive: false,
},
},
})
if err != nil {
panic ( err )
}
fmt . Println ( checkout . CheckoutURL )
}
curl -X POST " $DODO_API /checkouts" \
-H "Authorization: Bearer $DODO_API_KEY " \
-H "Content-Type: application/json" \
-d '{
"product_cart": [
{
"product_id": "pdt_123",
"quantity": 1
}
],
"customer": {
"customer_id": "cus_123"
},
"billing_address": {
"street": "1 Market St",
"city": "SF",
"state": "CA",
"country": "US",
"zipcode": "94105"
},
"subscription_data": {
"on_demand": {
"mandate_only": true
}
},
"return_url": "https://example.com/billing/success"
}'
{
"session_id" : "cks_123" ,
"checkout_url" : "https://test.checkout.dodopayments.com/session/cks123"
}
تحصيل اشتراك عند الطلب
بعد تفويض التفويض، قم بإنشاء رسوم حسب الحاجة.
نقطة النهاية: POST /subscriptions/{subscription_id}/charge
حقول الطلب الرئيسية (الجسم):
المبلغ المراد تحصيله (بالوحدة الأصغر للعملة). مثال: لتحصيل 25.00 دولار، مرر 2500.
تجاوز العملة الاختياري للرسوم.
تجاوز الوصف الاختياري لهذه الرسوم.
adaptive_currency_fees_inclusive
إذا كانت صحيحة، تشمل رسوم العملة التكيفية ضمن product_price. إذا كانت خاطئة، تضاف الرسوم فوق ذلك.
بيانات وصفية إضافية للدفع. إذا تم حذفها، يتم استخدام بيانات الاشتراك الوصفية.
Node.js SDK
Python SDK
Go SDK
cURL
import DodoPayments from 'dodopayments' ;
const client = new DodoPayments ({ bearerToken: process . env . DODO_PAYMENTS_API_KEY });
async function chargeNow ( subscriptionId ) {
const res = await client . subscriptions . charge ( subscriptionId , { product_price: 2500 });
console . log ( res . payment_id );
}
chargeNow ( 'sub_123' ). catch ( console . error );
import os
from dodopayments import DodoPayments
client = DodoPayments( bearer_token = os.environ.get( 'DODO_PAYMENTS_API_KEY' ))
response = client.subscriptions.charge(
subscription_id = "sub_123" ,
product_price = 2500 ,
)
print (response.payment_id)
package main
import (
" context "
" fmt "
" github.com/dodopayments/dodopayments-go "
" github.com/dodopayments/dodopayments-go/option "
)
func main () {
client := dodopayments . NewClient ( option . WithBearerToken ( "YOUR_API_KEY" ))
res , err := client . Subscriptions . Charge ( context . TODO (), "sub_123" , dodopayments . SubscriptionChargeParams {
ProductPrice : dodopayments . F ( int64 ( 2500 )),
})
if err != nil { panic ( err ) }
fmt . Println ( res . PaymentID )
}
curl -X POST " $DODO_API /subscriptions/sub_123/charge" \
-H "Authorization: Bearer $DODO_API_KEY " \
-H "Content-Type: application/json" \
-d '{
"product_price": 2500,
"product_description": "Extra usage for March"
}'
{ "payment_id" : "pay_abc123" }
قد تفشل محاولة تحصيل اشتراك ليس عند الطلب. تأكد من أن الاشتراك يحتوي على on_demand: true في تفاصيله قبل التحصيل.
محاولات الدفع
قد يقوم نظام كشف الاحتيال لدينا بحظر أنماط المحاولة العدوانية (ويمكن أن يحددها كنماذج اختبار بطاقات محتملة). اتبع سياسة إعادة المحاولة الآمنة.
يمكن أن يتم تصنيف أنماط المحاولة المتفجرة على أنها احتيالية أو اختبار بطاقات مشبوهة من قبل أنظمة المخاطر ومعالجاتنا. تجنب المحاولات المتجمعة؛ اتبع جدول التراجع وإرشادات محاذاة الوقت أدناه.
مبادئ سياسة إعادة المحاولة الآمنة
آلية التراجع : استخدم التراجع الأسي بين المحاولات.
حدود إعادة المحاولة : حدد إجمالي المحاولات (3-4 محاولات كحد أقصى).
تصفية ذكية : أعد المحاولة فقط في حالات الفشل القابلة لإعادة المحاولة (مثل أخطاء الشبكة/المصدر، نقص الأموال)؛ لا تعيد المحاولة في حالات الرفض القاسية.
منع اختبار البطاقات : لا تعيد المحاولة في حالات الفشل مثل DO_NOT_HONOR، STOLEN_CARD، LOST_CARD، PICKUP_CARD، FRAUDULENT، AUTHENTICATION_FAILURE.
تغيير البيانات الوصفية (اختياري) : إذا كنت تحتفظ بنظام إعادة المحاولة الخاص بك، قم بتمييز المحاولات عبر البيانات الوصفية (مثل retry_attempt).
جدول إعادة المحاولة المقترح (الاشتراكات)
المحاولة الأولى : فورية عند إنشاء الرسوم
المحاولة الثانية : بعد 3 أيام
المحاولة الثالثة : بعد 7 أيام أخرى (10 أيام إجمالاً)
المحاولة الرابعة (الأخيرة) : بعد 7 أيام أخرى (17 يومًا إجمالاً)
الخطوة النهائية: إذا لم يتم الدفع بعد، قم بوضع علامة على الاشتراك كغير مدفوع أو قم بإلغائه، بناءً على سياستك. قم بإخطار العميل خلال الفترة الزمنية لتحديث وسيلة الدفع الخاصة بهم.
تجنب المحاولات المتفجرة؛ التوافق مع وقت التفويض
قم بتثبيت المحاولات على الطابع الزمني الأصلي للتفويض لتجنب سلوك “المتفجرات” عبر محفظتك.
مثال: إذا بدأ العميل تجربة أو تفويض في الساعة 1:10 مساءً اليوم، قم بجدولة المحاولات المتابعة في الساعة 1:10 مساءً في الأيام التالية وفقًا لجدول التراجع الخاص بك (مثل +3 أيام → 1:10 مساءً، +7 أيام → 1:10 مساءً).
بدلاً من ذلك، إذا كنت تخزن وقت الدفع الناجح الأخير T، قم بجدولة المحاولة التالية في T + X days للحفاظ على محاذاة وقت اليوم.
المنطقة الزمنية وDST: استخدم معيار وقت ثابت للجدولة وتحويله للعرض فقط للحفاظ على الفترات.
رموز الرفض التي يجب ألا تعيد المحاولة عليها
STOLEN_CARD
DO_NOT_HONOR
FRAUDULENT
PICKUP_CARD
AUTHENTICATION_FAILURE
LOST_CARD
للحصول على قائمة شاملة بأسباب الرفض وما إذا كانت قابلة للتصحيح من قبل المستخدم، راجع
أخطاء المعاملات الوثائق.
قم بإعادة المحاولة فقط في حالات المشاكل المؤقتة/الناعمة (مثل insufficient_funds، issuer_unavailable، processing_error، مهلات الشبكة). إذا تكررت نفس حالة الرفض، توقف عن المحاولات الإضافية.
إرشادات التنفيذ (بدون كود)
استخدم جدولة/طابور يحتفظ بطوابع زمنية دقيقة؛ احسب المحاولة التالية في نفس وقت اليوم (مثل T + 3 days في نفس HH:MM).
احتفظ وارجع إلى طابع الزمن الخاص بآخر دفع ناجح T لحساب المحاولة التالية؛ لا تجمع بين اشتراكات متعددة في نفس اللحظة.
قم دائمًا بتقييم سبب الرفض الأخير؛ توقف عن المحاولات في حالات الرفض القاسية في قائمة التجاوز أعلاه.
حدد المحاولات المتزامنة لكل عميل ولكل حساب لمنع الارتفاعات العرضية.
تواصل بشكل استباقي: أرسل بريدًا إلكترونيًا/SMS للعميل لتحديث وسيلة الدفع الخاصة بهم قبل المحاولة المجدولة التالية.
استخدم البيانات الوصفية فقط للرؤية (مثل retry_attempt)؛ لا تحاول “التملص” من أنظمة الاحتيال/المخاطر عن طريق تدوير الحقول غير المهمة.
تتبع النتائج باستخدام الويب هوكس
قم بتنفيذ معالجة الويب هوك لتتبع رحلة العميل. راجع تنفيذ الويب هوكس .
subscription.active : تم تفويض التفويض وتم تفعيل الاشتراك
subscription.failed : فشل الإنشاء (مثل فشل التفويض)
subscription.on_hold : تم وضع الاشتراك في حالة تعليق (مثل الحالة غير المدفوعة)
payment.succeeded : تم تحصيل الرسوم بنجاح
payment.failed : فشل تحصيل الرسوم
بالنسبة لتدفقات الاشتراك عند الطلب، ركز على payment.succeeded وpayment.failed لمصالحة الرسوم المستندة إلى الاستخدام.
الاختبار والخطوات التالية
إنشاء في وضع الاختبار
استخدم مفتاح API الخاص بك للاختبار لإنشاء الاشتراك مع payment_link: true، ثم افتح الرابط وأكمل التفويض.
تحفيز الرسوم
استدعِ نقطة نهاية الرسوم بمبلغ صغير product_price (مثل 100) وتحقق من أنك تتلقى payment.succeeded.
الانتقال إلى الوضع المباشر
قم بالتبديل إلى مفتاح API المباشر الخاص بك بمجرد التحقق من الأحداث وتحديثات الحالة الداخلية.
استكشاف الأخطاء وإصلاحها
422 طلب غير صالح : تأكد من تقديم on_demand.mandate_only عند الإنشاء وproduct_price مقدمة للرسوم.
أخطاء العملة : إذا قمت بتجاوز product_currency، تأكد من أنه مدعوم لحسابك وعميلك.
لم يتم استلام الويب هوكس : تحقق من عنوان URL الخاص بالويب هوك وتكوين سر التوقيع.