Langsung ke konten utama
Upsells dan downsells memungkinkan Anda menawarkan produk tambahan atau perubahan paket kepada pelanggan menggunakan metode pembayaran yang tersimpan. Ini memungkinkan pembelian sekali klik yang melewati pengumpulan pembayaran, secara dramatis meningkatkan rasio konversi.

Post-Purchase Upsells

Tawarkan produk pelengkap segera setelah checkout dengan pembelian sekali klik.

Subscription Upgrades

Pindahkan pelanggan ke tingkat yang lebih tinggi dengan proration otomatis dan penagihan instan.

Cross-Sells

Tambahkan produk terkait untuk pelanggan yang sudah ada tanpa memasukkan kembali detail pembayaran.

Overview

Upsells dan downsells adalah strategi optimalisasi pendapatan yang ampuh:
  • Upsells: Tawarkan produk bernilai tinggi atau peningkatan (misalnya, paket Pro daripada Basic)
  • Downsells: Tawarkan alternatif dengan harga lebih rendah saat pelanggan menolak atau menurunkan tingkat
  • Cross-sells: Sarankan produk pelengkap (misalnya, add-on, barang terkait)
Dodo Payments mendukung alur ini melalui parameter payment_method_id, yang memungkinkan Anda menagih metode pembayaran tersimpan pelanggan tanpa mengharuskan mereka memasukkan detail kartu lagi.

Key Benefits

ManfaatDampak
Pembelian sekali klikLewati formulir pembayaran sepenuhnya untuk pelanggan lama
Konversi lebih tinggiKurangi gesekan saat keputusan dibuat
Pemrosesan instanPenagihan diproses segera dengan confirm: true
Pengalaman pengguna tanpa hambatanPelanggan tetap berada dalam aplikasi Anda sepanjang alur

How It Works

Prerequisites

Sebelum menerapkan upsells dan downsells, pastikan Anda memiliki:
1

Customer with Saved Payment Method

Pelanggan harus sudah menyelesaikan setidaknya satu pembelian. Metode pembayaran otomatis disimpan saat pelanggan menyelesaikan checkout.
2

Products Configured

Buat produk upsell Anda di dasbor Dodo Payments. Ini bisa berupa pembayaran satu kali, langganan, atau add-on.
3

Webhook Endpoint

Atur webhook untuk menangani payment.succeeded, payment.failed, dan subscription.plan_changed.

Getting Customer Payment Methods

Sebelum menawarkan upsell, ambil metode pembayaran tersimpan pelanggan:
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;
Metode pembayaran otomatis disimpan saat pelanggan menyelesaikan checkout. Anda tidak perlu menyimpannya secara eksplisit.

Post-Purchase One-Click Upsells

Tawarkan produk tambahan segera setelah pembelian berhasil. Pelanggan dapat menerima dengan satu klik karena metode pembayaran mereka sudah tersimpan.

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;
}
Saat menggunakan payment_method_id, Anda harus mengatur confirm: true dan menyediakan customer_id yang sudah ada. Metode pembayaran harus milik pelanggan tersebut.

Subscription Upgrades

Pindahkan pelanggan ke paket langganan tingkat lebih tinggi dengan proration otomatis.

Preview Before Committing

Selalu buat pratinjau perubahan paket untuk menunjukkan kepada pelanggan persis berapa yang akan mereka bayar:
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

Pilih bagaimana pelanggan ditagih saat melakukan upgrade:
ModePerilakuTerbaik Untuk
difference_immediatelyMenagih selisih harga secara instan (3030→80 = $50)Upgrade sederhana
prorated_immediatelyMenagih berdasarkan sisa waktu dalam siklus tagihanPenagihan adil berbasis waktu
full_immediatelyMenagih harga penuh paket baru, mengabaikan waktu tersisaSiklus tagihan direset
Gunakan difference_immediately untuk alur upgrade yang sederhana. Gunakan prorated_immediately ketika Anda ingin memperhitungkan waktu yang tidak terpakai pada paket saat ini.

Cross-Sells

Tambahkan produk pelengkap untuk pelanggan yang sudah ada tanpa mengharuskan mereka memasukkan kembali detail pembayaran.

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

Ketika pelanggan ingin pindah ke paket tingkat lebih rendah, tangani transisi dengan mulus menggunakan kredit otomatis.

How Downgrades Work

  1. Pelanggan meminta downgrade (Pro → Basic)
  2. Sistem menghitung nilai tersisa pada paket saat ini
  3. Kredit ditambahkan ke langganan untuk perpanjangan berikutnya
  4. Pelanggan langsung beralih ke paket baru
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');
Kredit dari downgrade yang menggunakan difference_immediately berskala langganan dan secara otomatis diterapkan ke perpanjangan berikutnya. Mereka berbeda dari Kredit Pelanggan.

Complete Example: Post-Purchase Upsell Flow

Berikut implementasi lengkap yang menunjukkan bagaimana menawarkan upsell setelah pembelian berhasil:
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

Waktu terbaik untuk menawarkan upsell adalah segera setelah pembelian berhasil ketika pelanggan berada dalam pola pikir membeli. Momen efektif lainnya:
  • Setelah pencapaian penggunaan fitur
  • Saat mendekati batas paket
  • Saat menyelesaikan onboarding
Sebelum mencoba penagihan sekali klik, verifikasi metode pembayaran:
  • Kompatibel dengan mata uang produk
  • Belum kadaluarsa
  • Milik pelanggan tersebut
API akan memvalidasi ini, tetapi memeriksa secara proaktif meningkatkan pengalaman pengguna.
Saat penagihan sekali klik gagal:
  1. Kembalikan ke alur checkout standar
  2. Beri tahu pelanggan dengan pesan yang jelas
  3. Tawarkan pembaruan metode pembayaran
  4. Jangan mencoba lagi berkali-kali untuk penagihan yang gagal
Upsell memiliki konversi lebih baik saat pelanggan memahami nilainya:
  • Tunjukkan apa yang mereka dapatkan dibandingkan paket saat ini
  • Sorot selisih harga, bukan total harga
  • Gunakan bukti sosial (misalnya, “Upgrade paling populer”)
  • Selalu sediakan cara mudah untuk menolak
  • Jangan tampilkan upsell yang sama berulang kali setelah ditolak
  • Lacak dan analisis upsell mana yang berhasil untuk mengoptimalkan penawaran

Webhooks to Monitor

Pantau event webhook ini untuk alur upsell dan downgrade:
EventPemicuAksi
payment.succeededPembayaran upsell/cross-sell selesaiKirim produk, perbarui akses
payment.failedPenagihan sekali klik gagalTampilkan kesalahan, tawarkan pengulangan atau alternatif
subscription.plan_changedUpgrade/downgrade selesaiPerbarui fitur, kirim konfirmasi
subscription.activeLangganan diaktifkan kembali setelah perubahan paketBerikan akses ke tingkat baru

Webhook Integration Guide

Pelajari cara mengatur dan memverifikasi endpoint webhook.