Passer au contenu principal
Les upsells et downsells vous permettent d’offrir des produits supplémentaires ou des modifications de plan aux clients en utilisant leurs moyens de paiement enregistrés. Cela rend possible des achats en un clic sans collecte de paiement, ce qui améliore considérablement les taux de conversion.

Post-Purchase Upsells

Proposez des produits complémentaires immédiatement après le paiement grâce à un achat en un clic.

Subscription Upgrades

Faites passer les clients à des paliers supérieurs avec attribution automatique et facturation instantanée.

Cross-Sells

Ajoutez des produits connexes aux clients existants sans leur faire ressaisir leurs informations de paiement.

Vue d’ensemble

Les upsells et downsells sont des stratégies puissantes d’optimisation des revenus :
  • Upsells : Proposez un produit de plus grande valeur ou une montée en gamme (par exemple : plan Pro au lieu de Basic)
  • Downsells : Proposez une alternative moins chère lorsqu’un client décline ou rétrograde
  • Cross-sells : Suggérez des produits complémentaires (par exemple : extensions, articles associés)
Dodo Payments rend ces flux possibles grâce au paramètre payment_method_id, qui vous permet de facturer le moyen de paiement enregistré d’un client sans lui demander de ressaisir les données de carte.

Avantages clés

AvantageImpact
Achats en un clicOmettez entièrement le formulaire de paiement pour les clients récurrents
Taux de conversion plus élevéRéduisez les frictions au moment de la décision
Traitement instantanéLes charges sont traitées immédiatement avec confirm: true
Expérience fluideLes clients restent dans votre application pendant tout le flux

Fonctionnement

Prérequis

Avant d’implémenter des upsells et downsells, assurez-vous d’avoir :
1

Customer with Saved Payment Method

Les clients doivent avoir réalisé au moins un achat. Les moyens de paiement sont automatiquement enregistrés lorsque les clients finalisent leur commande.
2

Products Configured

Créez vos produits d’upsell dans le tableau de bord Dodo Payments. Il peut s’agir de paiements uniques, d’abonnements ou d’extensions.
3

Webhook Endpoint

Configurez des webhooks pour gérer les événements payment.succeeded, payment.failed et subscription.plan_changed.

Récupérer les moyens de paiement des clients

Avant de proposer un upsell, récupérez les moyens de paiement enregistrés du client :
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;
Les moyens de paiement sont automatiquement enregistrés lorsque les clients finalisent leur commande. Vous n’avez pas besoin de les sauvegarder explicitement.

Upsells post-achat en un clic

Proposez des produits supplémentaires immédiatement après un achat réussi. Le client peut accepter d’un simple clic puisqu’il a déjà enregistré son moyen de paiement.

Mise en œuvre

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;
}
Lorsque vous utilisez payment_method_id, vous devez définir confirm: true et fournir un customer_id existant. Le moyen de paiement doit appartenir à ce client.

Montées d’abonnement

Faites passer les clients vers des plans d’abonnement supérieurs avec gestion automatique de la proratisation.

Prévisualiser avant de valider

Toujours prévisualiser les changements de plan pour montrer aux clients ce qu’ils vont exactement payer :
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}`);

Exécuter la montée de gamme

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

Modes de proratisation

Choisissez comment facturer les clients lors d’une montée en gamme :
ModeComportementIdéal pour
difference_immediatelyFacture immédiatement la différence de prix (3080→80 = 50$)Montées simples
prorated_immediatelyFacture en fonction du temps restant dans le cycle de facturationFacturation équitable basée sur le temps
full_immediatelyFacture le nouveau plan en entier, ignore le temps restantRéinitialisation du cycle de facturation
Utilisez difference_immediately pour les flux de montée simples. Utilisez prorated_immediately lorsque vous souhaitez tenir compte du temps inutilisé sur le plan actuel.

Cross-sells

Ajoutez des produits complémentaires pour les clients existants sans leur demander de ressaisir leurs informations de paiement.

Mise en œuvre

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

Rétrogradations d’abonnement

Lorsqu’un client souhaite passer à un plan inférieur, gérez la transition avec des crédits automatiques.

Comment fonctionnent les rétrogradations

  1. Le client demande une rétrogradation (Pro → Basic)
  2. Le système calcule la valeur restante du plan actuel
  3. Un crédit est ajouté à l’abonnement pour les prochains renouvellements
  4. Le client passe immédiatement au nouveau plan
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');
Les crédits issus des rétrogradations utilisant difference_immediately sont liés à l’abonnement et appliqués automatiquement aux futurs renouvellements. Ils sont distincts des Crédits client.

Exemple complet : flux d’upsell post-achat

Voici une mise en œuvre complète montrant comment proposer un upsell après un achat réussi :
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);

Bonnes pratiques

Le meilleur moment pour proposer un upsell est juste après un achat réussi, lorsque les clients sont dans un état d’esprit propice à l’achat. Autres moments efficaces :
  • Après des jalons d’utilisation des fonctionnalités
  • Lorsqu’ils approchent des limites du plan
  • À la fin de l’onboarding
Avant d’essayer une charge en un clic, vérifiez le moyen de paiement :
  • Est compatible avec la devise du produit
  • N’est pas expiré
  • Appartient au client
L’API validera ces éléments, mais les vérifier proactivement améliore l’expérience.
Lorsque les charges en un clic échouent :
  1. Revenir au parcours de paiement classique
  2. Informer le client avec un message clair
  3. Proposer de mettre à jour le moyen de paiement
  4. Ne pas retenter plusieurs fois les charges échouées
Les upsells convertissent mieux lorsque les clients comprennent la valeur :
  • Montrez ce qu’ils obtiennent par rapport au plan actuel
  • Mettez en avant la différence de prix, pas le prix total
  • Utilisez la preuve sociale (ex. : « Montée en gamme la plus populaire »)
  • Proposez toujours une manière simple de décliner
  • Ne pas afficher le même upsell plusieurs fois après un refus
  • Suivez et analysez quels upsells convertissent pour optimiser les offres

Webhooks à surveiller

Suivez ces événements webhook pour les flux d’upsell et de rétrogradation :
ÉvénementDéclencheurAction
payment.succeededPaiement d’upsell/cross-sell terminéLivrer le produit, mettre à jour l’accès
payment.failedCharge en un clic échouéeAfficher l’erreur, proposer une nouvelle tentative ou une solution de secours
subscription.plan_changedMontée/rétrogradation terminéeMettre à jour les fonctionnalités, envoyer une confirmation
subscription.activeRéactivation d’abonnement après changement de planAccorder l’accès au nouveau palier

Webhook Integration Guide

Apprenez à configurer et vérifier les points de terminaison webhook.

Ressources associées