الانتقال إلى المحتوى الرئيسي
تُتيح عمليات الترقيات والتخفيضات عرض منتجات إضافية أو تغييرات في الخطط للعملاء باستخدام طرق الدفع المحفوظة لديهم. هذا يمكِّن من عمليات شراء بنقرة واحدة تتخطى جمع بيانات الدفع، مما يحسّن معدلات التحويل بشكل كبير.

Post-Purchase Upsells

عرض منتجات تكميلية فور الانتهاء من الدفع مع إمكانية شراء بنقرة واحدة.

Subscription Upgrades

نقل العملاء إلى مستويات أعلى مع التوزيع التلقائي للرسوم والفوترة الفورية.

Cross-Sells

إضافة منتجات ذات صلة للعملاء الحاليين دون الحاجة إلى إعادة إدخال بيانات الدفع.

Overview

الترقيات والتخفيضات تعتبر استراتيجيات قوية لتحسين الإيرادات:
  • Upsells: عرض منتج أعلى قيمة أو ترقية (مثل خطة Pro بدلًا من Basic)
  • Downsells: عرض خيار بسعر أقل عندما يرفض العميل أو يطلب تخفيضًا
  • Cross-sells: اقتراح منتجات تكميلية (مثل الإضافات أو العناصر ذات الصلة)
تمكِّن Dodo Payments هذه التدفقات عبر المعامل payment_method_id، الذي يتيح لك تحصيل مقابل طريقة الدفع المحفوظة للعميل دون الحاجة لإعادة إدخال تفاصيل البطاقة.

Key Benefits

BenefitImpact
One-click purchasesتخطي نموذج الدفع تمامًا للعملاء العائدين
Higher conversionتقليل الاحتكاك في لحظة اتخاذ القرار
Instant processingيتم معالجة الرسوم فورًا باستخدام confirm: true
Seamless UXيبقى العملاء داخل تطبيقك طوال التدفق

How It Works

Prerequisites

قبل تنفيذ الترقيات والتخفيضات، تأكد من توفر ما يلي:
1

Customer with Saved Payment Method

يجب أن يكون العملاء قد أتمّوا عملية شراء واحدة على الأقل. يتم حفظ طرق الدفع تلقائيًا عند إكمال العملاء لعملية الدفع.
2

Products Configured

أنشئ منتجات الترقيات داخل لوحة Dodo Payments. يمكن أن تكون هذه مدفوعات لمرة واحدة أو اشتراكات أو إضافات.
3

Webhook Endpoint

أعدد Webhooks للتعامل مع أحداث payment.succeeded، payment.failed، وsubscription.plan_changed.

Getting Customer Payment Methods

قبل عرض ترقية، استرجع طرق الدفع المحفوظة للعميل:
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'live_mode',
});

async function getPaymentMethods(customerId: string) {
  const paymentMethods = await client.customers.listPaymentMethods(customerId);
  
  // Returns array of saved payment methods
  // Each has: payment_method_id, type, card (last4, brand, exp_month, exp_year)
  return paymentMethods;
}

// Example usage
const methods = await getPaymentMethods('cus_123');
console.log('Available payment methods:', methods);

// Use the first available method for upsell
const primaryMethod = methods[0]?.payment_method_id;
يتم حفظ طرق الدفع تلقائيًا عند إكمال العملاء لعملية الدفع. لا تحتاج إلى حفظها يدويًا.

Post-Purchase One-Click Upsells

قدِّم منتجات إضافية فور إتمام عملية شراء ناجحة. يمكن للعميل القبول بنقرة واحدة لأن طريقة الدفع محفوظة بالفعل.

Implementation

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'live_mode',
});

async function createOneClickUpsell(
  customerId: string,
  paymentMethodId: string,
  upsellProductId: string
) {
  // Create checkout session with saved payment method
  // confirm: true processes the payment immediately
  const session = await client.checkoutSessions.create({
    product_cart: [
      {
        product_id: upsellProductId,
        quantity: 1
      }
    ],
    customer: {
      customer_id: customerId
    },
    payment_method_id: paymentMethodId,
    confirm: true,  // Required when using payment_method_id
    return_url: 'https://yourapp.com/upsell-success',
    feature_flags: {
      redirect_immediately: true  // Skip success page
    },
    metadata: {
      upsell_source: 'post_purchase',
      original_order_id: 'order_123'
    }
  });

  return session;
}

// Example: Offer premium add-on after initial purchase
async function handlePostPurchaseUpsell(customerId: string) {
  // Get customer's payment methods
  const methods = await client.customers.listPaymentMethods(customerId);
  
  if (methods.length === 0) {
    console.log('No saved payment methods available');
    return null;
  }

  // Create the upsell with one-click checkout
  const upsell = await createOneClickUpsell(
    customerId,
    methods[0].payment_method_id,
    'prod_premium_addon'
  );

  console.log('Upsell processed:', upsell.session_id);
  return upsell;
}
عند استخدام payment_method_id، يجب تعيين confirm: true وتوفير customer_id موجود مسبقًا. يجب أن تنتمي طريقة الدفع إلى ذلك العميل.

Subscription Upgrades

نقل العملاء إلى خطط اشتراك أعلى مع التعامل التلقائي مع التوزيع النسبى للرسوم.

Preview Before Committing

دائمًا اعرض معاينة لتغييرات الخطة لإظهار ما سيتم تحصيله بالضبط من العميل:
async function previewUpgrade(
  subscriptionId: string,
  newProductId: string
) {
  const preview = await client.subscriptions.previewChangePlan(subscriptionId, {
    product_id: newProductId,
    quantity: 1,
    proration_billing_mode: 'difference_immediately'
  });

  return {
    immediateCharge: preview.immediate_charge?.summary,
    newPlan: preview.new_plan,
    effectiveDate: preview.effective_date
  };
}

// Show customer the charge before confirming
const preview = await previewUpgrade('sub_123', 'prod_pro_plan');
console.log(`Upgrade will charge: ${preview.immediateCharge}`);

Execute the Upgrade

async function upgradeSubscription(
  subscriptionId: string,
  newProductId: string,
  prorationMode: 'prorated_immediately' | 'difference_immediately' | 'full_immediately' = 'difference_immediately'
) {
  const result = await client.subscriptions.changePlan(subscriptionId, {
    product_id: newProductId,
    quantity: 1,
    proration_billing_mode: prorationMode
  });

  return {
    status: result.status,
    subscriptionId: result.subscription_id,
    paymentId: result.payment_id,
    invoiceId: result.invoice_id
  };
}

// Upgrade from Basic ($30) to Pro ($80)
// With difference_immediately: charges $50 instantly
const upgrade = await upgradeSubscription('sub_123', 'prod_pro_plan');
console.log('Upgrade status:', upgrade.status);

Proration Modes

اختر كيف يتم فوترة العملاء عند الترقية:
ModeBehaviorBest For
difference_immediatelyيخصم فرق السعر فورًا (3030→80 = $50)الترقيات المباشرة
prorated_immediatelyيخصم بناءً على الوقت المتبقي في دورة الفوترةالفوترة العادلة المبنية على الوقت
full_immediatelyيخصم السعر الكامل للخطة الجديدة، متجاهلًا الوقت المتبقيإعادة ضبط دورة الفوترة
استخدم difference_immediately لتدفقات الترقيات البسيطة. استخدم prorated_immediately عندما تريد احتساب الوقت غير المستخدم في الخطة الحالية.

Cross-Sells

أضف منتجات تكميلية للعملاء الحاليين دون الحاجة إلى إعادة إدخال تفاصيل الدفع.

Implementation

async function createCrossSell(
  customerId: string,
  paymentMethodId: string,
  productId: string,
  quantity: number = 1
) {
  // Create a one-time payment using saved payment method
  const payment = await client.payments.create({
    product_cart: [
      {
        product_id: productId,
        quantity: quantity
      }
    ],
    customer_id: customerId,
    payment_method_id: paymentMethodId,
    return_url: 'https://yourapp.com/purchase-complete',
    metadata: {
      purchase_type: 'cross_sell',
      source: 'product_recommendation'
    }
  });

  return payment;
}

// Example: Customer bought a course, offer related ebook
async function offerRelatedProduct(customerId: string, relatedProductId: string) {
  const methods = await client.customers.listPaymentMethods(customerId);
  
  if (methods.length === 0) {
    // Fall back to standard checkout
    return client.checkoutSessions.create({
      product_cart: [{ product_id: relatedProductId, quantity: 1 }],
      customer: { customer_id: customerId },
      return_url: 'https://yourapp.com/purchase-complete'
    });
  }

  // One-click purchase
  return createCrossSell(customerId, methods[0].payment_method_id, relatedProductId);
}

Subscription Downgrades

عندما يرغب العملاء في الانتقال إلى خطة أقل، تعامل مع الانتقال بسلاسة باستخدام الاعتمادات التلقائية.

How Downgrades Work

  1. يطلب العميل تخفيضًا (Pro → Basic)
  2. يحسب النظام القيمة المتبقية في الخطة الحالية
  3. تُضاف اعتمادات للاشتراك للتجديدات المستقبلية
  4. ينتقل العميل إلى الخطة الجديدة فورًا
async function downgradeSubscription(
  subscriptionId: string,
  newProductId: string
) {
  // Preview the downgrade first
  const preview = await client.subscriptions.previewChangePlan(subscriptionId, {
    product_id: newProductId,
    quantity: 1,
    proration_billing_mode: 'difference_immediately'
  });

  console.log('Credit to be applied:', preview.credit_amount);

  // Execute the downgrade
  const result = await client.subscriptions.changePlan(subscriptionId, {
    product_id: newProductId,
    quantity: 1,
    proration_billing_mode: 'difference_immediately'
  });

  // Credits are automatically applied to future renewals
  return result;
}

// Downgrade from Pro ($80) to Basic ($30)
// $50 credit added to subscription, auto-applied on next renewal
const downgrade = await downgradeSubscription('sub_123', 'prod_basic_plan');
الاعتمادات الناتجة عن التخفيض باستخدام difference_immediately تكون مخصصة للاشتراك وتُطبق تلقائيًا على التجديدات المستقبلية. وهي متميزة عن Customer Credits.

Complete Example: Post-Purchase Upsell Flow

إليك تنفيذًا كاملاً يوضح كيفية عرض ترقية بعد عملية شراء ناجحة:
import DodoPayments from 'dodopayments';
import express from 'express';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'live_mode',
});

const app = express();

// Store for tracking upsell eligibility (use your database in production)
const eligibleUpsells = new Map<string, { customerId: string; productId: string }>();

// Webhook handler for initial purchase success
app.post('/webhooks/dodo', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = JSON.parse(req.body.toString());
  
  switch (event.type) {
    case 'payment.succeeded':
      // Check if customer is eligible for upsell
      const customerId = event.data.customer_id;
      const productId = event.data.product_id;
      
      // Define upsell rules (e.g., bought Basic, offer Pro)
      const upsellProduct = getUpsellProduct(productId);
      
      if (upsellProduct) {
        eligibleUpsells.set(customerId, {
          customerId,
          productId: upsellProduct
        });
      }
      break;
      
    case 'payment.failed':
      console.log('Payment failed:', event.data.payment_id);
      // Handle failed upsell payment
      break;
  }
  
  res.json({ received: true });
});

// API endpoint to check upsell eligibility
app.get('/api/upsell/:customerId', async (req, res) => {
  const { customerId } = req.params;
  const upsell = eligibleUpsells.get(customerId);
  
  if (!upsell) {
    return res.json({ eligible: false });
  }
  
  // Get payment methods
  const methods = await client.customers.listPaymentMethods(customerId);
  
  if (methods.length === 0) {
    return res.json({ eligible: false, reason: 'no_payment_method' });
  }
  
  // Get product details for display
  const product = await client.products.retrieve(upsell.productId);
  
  res.json({
    eligible: true,
    product: {
      id: product.product_id,
      name: product.name,
      price: product.price,
      currency: product.currency
    },
    paymentMethodId: methods[0].payment_method_id
  });
});

// API endpoint to accept upsell
app.post('/api/upsell/:customerId/accept', async (req, res) => {
  const { customerId } = req.params;
  const upsell = eligibleUpsells.get(customerId);
  
  if (!upsell) {
    return res.status(400).json({ error: 'No upsell available' });
  }
  
  try {
    const methods = await client.customers.listPaymentMethods(customerId);
    
    // Create one-click purchase
    const session = await client.checkoutSessions.create({
      product_cart: [{ product_id: upsell.productId, quantity: 1 }],
      customer: { customer_id: customerId },
      payment_method_id: methods[0].payment_method_id,
      confirm: true,
      return_url: `${process.env.APP_URL}/upsell-success`,
      feature_flags: { redirect_immediately: true },
      metadata: { upsell: 'true', source: 'post_purchase' }
    });
    
    // Clear the upsell offer
    eligibleUpsells.delete(customerId);
    
    res.json({ success: true, sessionId: session.session_id });
  } catch (error) {
    console.error('Upsell failed:', error);
    res.status(500).json({ error: 'Upsell processing failed' });
  }
});

// Helper function to determine upsell product
function getUpsellProduct(purchasedProductId: string): string | null {
  const upsellMap: Record<string, string> = {
    'prod_basic_plan': 'prod_pro_plan',
    'prod_starter_course': 'prod_complete_bundle',
    'prod_single_license': 'prod_team_license'
  };
  
  return upsellMap[purchasedProductId] || null;
}

app.listen(3000);

Best Practices

أفضل وقت لعرض ترقية هو فور إتمام عملية شراء ناجحة عندما يكون العملاء في ذهن الشراء. لحظات فعالة أخرى:
  • بعد بلوغ معالم استخدام الميزة
  • عند الاقتراب من حدود الخطة
  • عند إكمال عملية الإعداد
قبل محاولة خصم بنقرة واحدة، تحقق من طريقة الدفع:
  • هل تتوافق مع عملة المنتج
  • هل لم تنتهِ صلاحيتها
  • هل تنتمي إلى العميل
سوف تتحقق واجهة برمجة التطبيقات من ذلك، لكن الفحص المسبق يُحسن تجربة المستخدم.
عندما تفشل عمليات الخصم بنقرة واحدة:
  1. العودة إلى تدفق الدفع القياسي
  2. إخطار العميل برسالة واضحة
  3. عرض تحديث طريقة الدفع
  4. عدم تكرار المحاولات الفاشلة
تحقق من أن العملاء يفهمون القيمة لتحقيق أفضل تحويل:
  • أظهر ما سيحصلون عليه مقابل الخطة الحالية
  • أبرز فرق السعر، وليس السعر الكامل
  • استخدم الدليل الاجتماعي (مثل «الترقية الأكثر شعبية»)
  • دائمًا قدّم طريقة سهلة للرفض
  • لا تعرض نفس الترقية بعد الرفض مرة أخرى
  • تتبع وحلل الترقيات التي تتحول لتحسين العروض

Webhooks to Monitor

تابع هذه الأحداث في Webhooks لتدفقات الترقيات والتخفيضات:
EventTriggerAction
payment.succeededاكتمال دفعة الترقية/البيع المتقاطعتسليم المنتج، تحديث الوصول
payment.failedفشل خصم بنقرة واحدةعرض خطأ، عرض إعادة المحاولة أو العودة إلى الخيار البديل
subscription.plan_changedاكتمال الترقية/التخفيضتحديث الميزات، إرسال تأكيد
subscription.activeإعادة تنشيط الاشتراك بعد تغيير الخطةمنح الوصول إلى المستوى الجديد

Webhook Integration Guide

تعرّف على كيفية إعداد والتحقق من نقاط نهاية Webhook.