Chuyển đến nội dung chính
Upsells và downsells giúp bạn đề xuất thêm sản phẩm hoặc thay đổi gói cho khách hàng thông qua các phương thức thanh toán đã lưu của họ. Điều này cho phép mua hàng chỉ bằng một cú nhấp chuột mà không cần thu thập thanh toán, cải thiện đáng kể tỷ lệ chuyển đổi.

Post-Purchase Upsells

Cung cấp các sản phẩm bổ sung ngay sau khi thanh toán với tính năng mua hàng chỉ bằng một cú nhấp chuột.

Subscription Upgrades

Chuyển khách hàng sang các cấp cao hơn với việc tự động tính tỷ lệ và thanh toán ngay lập tức.

Cross-Sells

Thêm các sản phẩm liên quan cho khách hàng hiện có mà không cần nhập lại thông tin thanh toán.

Tổng quan

Upsells và downsells là những chiến lược tối ưu hóa doanh thu mạnh mẽ:
  • Upsells: Đề xuất sản phẩm giá trị cao hơn hoặc nâng cấp (ví dụ: gói Pro thay vì Basic)
  • Downsells: Đưa ra lựa chọn giá thấp hơn khi khách hàng từ chối hoặc hạ cấp
  • Cross-sells: Gợi ý các sản phẩm bổ sung (ví dụ: add-on, mặt hàng liên quan)
Dodo Payments hỗ trợ các luồng này thông qua tham số payment_method_id, cho phép bạn tính phí lên phương thức thanh toán đã lưu của khách hàng mà không yêu cầu họ nhập lại chi tiết thẻ.

Lợi ích chính

Lợi íchTác động
Mua hàng chỉ với một cú nhấp chuộtBỏ qua hoàn toàn biểu mẫu thanh toán cho khách hàng quay lại
Tỷ lệ chuyển đổi cao hơnGiảm độ cản trở ngay thời điểm đưa ra quyết định
Xử lý ngay lập tứcCác khoản phí được xử lý ngay với confirm: true
Trải nghiệm liền mạchKhách hàng vẫn ở trong ứng dụng của bạn trong suốt luồng

Cách hoạt động

Yêu cầu trước

Trước khi triển khai upsells và downsells, hãy đảm bảo bạn có:
1

Customer with Saved Payment Method

Khách hàng phải đã hoàn tất ít nhất một giao dịch mua. Các phương thức thanh toán được tự động lưu khi khách hàng hoàn tất thanh toán.
2

Products Configured

Tạo các sản phẩm upsell của bạn trong bảng điều khiển Dodo Payments. Chúng có thể là giao dịch một lần, đăng ký định kỳ hoặc add-on.
3

Webhook Endpoint

Thiết lập webhook để xử lý các sự kiện payment.succeeded, payment.failedsubscription.plan_changed.

Lấy phương thức thanh toán của khách hàng

Trước khi đề xuất upsell, hãy lấy các phương thức thanh toán đã lưu của khách hàng:
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;
Các phương thức thanh toán được tự động lưu khi khách hàng hoàn tất thanh toán. Bạn không cần lưu chúng một cách rõ ràng.

Upsells sau khi mua với một cú nhấp chuột

Đề xuất thêm sản phẩm ngay sau khi giao dịch mua thành công. Khách hàng có thể chấp nhận chỉ với một cú nhấp chuột vì phương thức thanh toán đã được lưu trước đó.

Triển khai

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;
}
Khi sử dụng payment_method_id, bạn phải đặt confirm: true và cung cấp một customer_id hiện có. Phương thức thanh toán phải thuộc về khách hàng đó.

Nâng cấp đăng ký

Chuyển khách hàng sang các gói đăng ký cao hơn với việc xử lý tỷ lệ tự động.

Xem trước trước khi xác nhận

Luôn xem trước sự thay đổi gói để cho khách hàng thấy chính xác số tiền họ sẽ bị tính phí:
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}`);

Thực hiện nâng cấp

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);

Chế độ tính tỷ lệ

Chọn cách khách hàng được tính phí khi nâng cấp:
Chế độHành viPhù hợp cho
difference_immediatelyTính ngay phần chênh lệch giá (3030→80 = $50)Các luồng nâng cấp đơn giản
prorated_immediatelyTính dựa trên thời gian còn lại trong chu kỳ thanh toánThanh toán công bằng theo thời gian
full_immediatelyTính toàn bộ giá của gói mới, bỏ qua thời gian còn lạiKhởi động lại chu kỳ thanh toán
Sử dụng difference_immediately cho các luồng nâng cấp đơn giản. Dùng prorated_immediately khi bạn muốn tính đến thời gian chưa sử dụng trong gói hiện tại.

Bán chéo

Thêm các sản phẩm bổ sung cho khách hàng hiện có mà không yêu cầu họ nhập lại thông tin thanh toán.

Triển khai

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);
}

Hạ cấp đăng ký

Khi khách hàng muốn chuyển sang gói thấp hơn, hãy xử lý chuyển đổi một cách nhẹ nhàng với các khoản tín dụng tự động.

Cách hạ cấp hoạt động

  1. Khách hàng yêu cầu hạ cấp (Pro → Basic)
  2. Hệ thống tính toán giá trị còn lại của gói hiện tại
  3. Tín dụng được thêm vào đăng ký cho các lần gia hạn sau
  4. Khách hàng chuyển sang gói mới ngay lập tức
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');
Các khoản tín dụng từ việc hạ cấp sử dụng difference_immediately là theo phạm vi đăng ký và tự động áp dụng cho các lần gia hạn sau. Chúng khác với Customer Credits.

Ví dụ hoàn chỉnh: Luồng upsell sau khi mua

Đây là một triển khai hoàn chỉnh cho thấy cách đề xuất upsell sau khi mua thành công:
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);

Thực hành tốt nhất

Thời điểm tốt nhất để đề xuất upsell là ngay sau khi mua thành công, khi khách hàng đang trong tâm thế sẵn sàng mua. Các thời điểm hiệu quả khác:
  • Sau các mốc sử dụng tính năng
  • Khi gần đạt giới hạn gói
  • Khi hoàn tất hành trình onboarding
Trước khi thử tính phí chỉ với một cú nhấp chuột, hãy xác minh phương thức thanh toán:
  • Có tương thích với tiền tệ của sản phẩm
  • Chưa hết hạn
  • Thuộc về khách hàng
API sẽ tự động xác thực những điều này, nhưng kiểm tra trước giúp cải thiện trải nghiệm người dùng.
Khi các giao dịch tính phí một cú nhấp chuột thất bại:
  1. Trở về luồng thanh toán tiêu chuẩn
  2. Thông báo cho khách hàng với thông điệp rõ ràng
  3. Đề nghị cập nhật phương thức thanh toán
  4. Không cố gắng tính phí thất bại lặp lại
Upsells mang lại tỷ lệ chuyển đổi tốt hơn khi khách hàng hiểu rõ giá trị:
  • Hiển thị những gì họ nhận được so với gói hiện tại
  • Làm nổi bật sự khác biệt giá, không phải tổng chi phí
  • Sử dụng chứng thực xã hội (ví dụ: “Nâng cấp phổ biến nhất”)
  • Luôn cung cấp cách từ chối dễ dàng
  • Không hiển thị cùng một upsell nhiều lần sau khi bị từ chối
  • Theo dõi và phân tích upsell nào chuyển đổi để tối ưu hóa đề nghị

Webhook cần theo dõi

Theo dõi các sự kiện webhook này cho luồng upsell và hạ cấp:
Sự kiệnKích hoạtHành động
payment.succeededThanh toán upsell/bán chéo hoàn tấtGiao sản phẩm, cập nhật quyền truy cập
payment.failedTính phí một cú nhấp chuột thất bạiHiển thị lỗi, đề nghị thử lại hoặc dự phòng
subscription.plan_changedNâng cấp/hạ cấp hoàn tấtCập nhật tính năng, gửi xác nhận
subscription.activeĐăng ký được kích hoạt lại sau khi thay đổi góiCấp quyền truy cập cấp mới

Webhook Integration Guide

Tìm hiểu cách thiết lập và xác minh các điểm cuối webhook.

Tài nguyên liên quan