Vai al contenuto principale
Upsell e downsell ti permettono di offrire prodotti aggiuntivi o modifiche di piano ai clienti che utilizzano i metodi di pagamento salvati. Ciò consente acquisti con un solo clic che saltano la raccolta del pagamento, migliorando drasticamente i tassi di conversione.

Post-Purchase Upsells

Offri prodotti complementari subito dopo il checkout con acquisti con un solo clic.

Subscription Upgrades

Sposta i clienti su livelli superiori con la proration automatica e la fatturazione immediata.

Cross-Sells

Aggiungi prodotti correlati ai clienti esistenti senza reinserire i dati di pagamento.

Panoramica

Gli upsell e downsell sono potenti strategie di ottimizzazione dei ricavi:
  • Upsell: Offri un prodotto o un aggiornamento di valore superiore (es. piano Pro invece del piano Basic)
  • Downsell: Offri un’alternativa a prezzo inferiore quando un cliente rifiuta o downgrade
  • Cross-sell: Suggerisci prodotti complementari (es. addon, articoli correlati)
Dodo Payments abilita questi flussi tramite il parametro payment_method_id, che ti consente di addebitare il metodo di pagamento salvato del cliente senza richiedere nuovamente i dati della carta.

Vantaggi chiave

VantaggioImpatto
Acquisti con un clicSalta completamente il modulo di pagamento per i clienti di ritorno
Conversioni più alteRiduci l’attrito nel momento della decisione
Elaborazione istantaneaGli addebiti vengono elaborati immediatamente con confirm: true
Esperienza fluidaI clienti restano nella tua app per tutto il flusso

Come funziona

Requisiti

Prima di implementare upsell e downsell, assicurati di avere:
1

Customer with Saved Payment Method

I clienti devono aver completato almeno un acquisto. I metodi di pagamento vengono salvati automaticamente quando completano il checkout.
2

Products Configured

Crea i tuoi prodotti upsell nella dashboard di Dodo Payments. Possono essere pagamenti una tantum, abbonamenti o componenti aggiuntivi.
3

Webhook Endpoint

Configura webhook per gestire gli eventi payment.succeeded, payment.failed e subscription.plan_changed.

Ottieni i metodi di pagamento dei clienti

Prima di offrire un upsell, recupera i metodi di pagamento salvati del cliente:
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;
I metodi di pagamento vengono salvati automaticamente quando i clienti completano il checkout. Non è necessario salvarli esplicitamente.

Upsell post-acquisto con un clic

Offri prodotti aggiuntivi immediatamente dopo un acquisto riuscito. Il cliente può accettare con un solo clic poiché il suo metodo di pagamento è già salvato.

Implementazione

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;
}
Quando usi payment_method_id, devi impostare confirm: true e fornire un customer_id esistente. Il metodo di pagamento deve appartenere a quel cliente.

Aggiornamenti di Abbonamento

Sposta i clienti a piani di abbonamento di livello superiore con gestione automatica della proporzione.

Anteprima prima di impegnarsi

Anteprima sempre le modifiche al piano per mostrare ai clienti esattamente cosa verrà addebitato:
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}`);

Esegui l’aggiornamento

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

Modalità di proporzione

Scegli come vengono addebitati i clienti quando aggiornano:
ModalitàComportamentoIdeale per
difference_immediatelyAddebita immediatamente la differenza di prezzo (3030→80 = $50)Upgrade semplici
prorated_immediatelyAddebita in base al tempo residuo nel ciclo di fatturazioneFatturazione equa basata sul tempo
full_immediatelyAddebita l’intero importo del nuovo piano, ignorando il tempo rimanenteIl ciclo di fatturazione si resetta
Usa difference_immediately per flussi di upgrade lineari. Usa prorated_immediately quando vuoi considerare il tempo inutilizzato sul piano corrente.

Cross-Sell

Aggiungi prodotti complementari per i clienti esistenti senza richiederne il reinserimento dei dettagli di pagamento.

Implementazione

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

Downgrade dell’abbonamento

Quando i clienti vogliono passare a un piano di livello inferiore, gestisci la transizione in modo fluido con crediti automatici.

Come funzionano i downgrade

  1. Il cliente richiede il downgrade (Pro → Basic)
  2. Il sistema calcola il valore rimanente del piano attuale
  3. Il credito viene aggiunto all’abbonamento per i rinnovi futuri
  4. Il cliente passa immediatamente al nuovo piano
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');
I crediti derivanti da downgrade tramite difference_immediately sono limitati all’abbonamento e vengono applicati automaticamente ai rinnovi futuri. Sono distinti dalle assegnazioni di Credit-Based Billing.

Esempio completo: Flusso di upsell post-acquisto

Ecco un’implementazione completa che mostra come offrire un upsell dopo un acquisto riuscito:
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);

Migliori Pratiche

Il momento migliore per offrire un upsell è subito dopo un acquisto riuscito, quando i clienti sono ancora in modalità di acquisto. Altri momenti efficaci:
  • Dopo il raggiungimento di traguardi di utilizzo delle funzionalità
  • Quando si avvicinano ai limiti del piano
  • Al completamento dell’onboarding
Prima di tentare un addebito con un clic, verifica il metodo di pagamento:
  • È compatibile con la valuta del prodotto
  • Non è scaduto
  • Appartiene al cliente
L’API convaliderà questi elementi, ma verificarli proattivamente migliora l’UX.
Quando gli addebiti con un clic falliscono:
  1. Torna al flusso di checkout standard
  2. Notifica il cliente con messaggi chiari
  3. Offri la possibilità di aggiornare il metodo di pagamento
  4. Non ripetere continuamente addebiti falliti
Gli upsell convertono meglio quando i clienti capiscono il valore:
  • Mostra cosa stanno ottenendo rispetto al piano attuale
  • Evidenzia la differenza di prezzo, non il prezzo totale
  • Usa la prova sociale (es. «Upgrade più popolare»)
  • Fornisci sempre un modo semplice per rifiutare
  • Non mostrare lo stesso upsell ripetutamente dopo un rifiuto
  • Monitora e analizza quali upsell convertono per ottimizzare le offerte

Webhook da monitorare

Monitora questi eventi webhook per flussi di upsell e downgrade:
EventoTriggerAzione
payment.succeededPagamento upsell/cross-sell completatoConsegna il prodotto, aggiorna l’accesso
payment.failedAddebito con un clic fallitoMostra l’errore, offri tentativo o fallback
subscription.plan_changedUpgrade/downgrade completatoAggiorna funzionalità, invia conferma
subscription.activeAbbonamento riattivato dopo cambio pianoConcedi accesso al nuovo livello

Webhook Integration Guide

Scopri come configurare e verificare gli endpoint webhook.

Risorse correlate