Hoppa till huvudinnehåll
Uppersell och neddersell låter dig erbjuda ytterligare produkter eller ändringar av planer till kunder med deras sparade betalningsmetoder. Detta möjliggör engångsköp som hoppar över betalningsinsamling, vilket dramatiskt förbättrar konverteringsgraden.

Uppersell efter köp

Erbjud kompletterande produkter omedelbart efter kassaflödet med engångsköp.

Uppgraderingar av abonnemang

Flytta kunder till högre nivåer med automatisk proration och omedelbar fakturering.

Korsförsäljning

Lägg till relaterade produkter till befintliga kunder utan att behöva ange betalningsuppgifter igen.

Översikt

Uppersell och neddersell är kraftfulla strategier för intäktsoptimering:
  • Uppersell: Erbjud en produkt eller uppgradering med högre värde (t.ex. Pro-plan istället för Basic)
  • Neddersell: Erbjud ett alternativ med lägre pris när en kund avböjer eller nedgraderas
  • Korsförsäljning: Föreslå kompletterande produkter (t.ex. tillägg, relaterade artiklar)
Dodo Payments möjliggör dessa flöden genom payment_method_id parameter, som låter dig ta betalt från kundens sparade betalningsmetod utan att kräva att de anges kortuppgifter igen.

Nyckelfördelar

FördelPåverkan
EngångsköpHoppa över betalningsformulär helt för återkommande kunder
Högre konverteringMinska friktion vid beslutstillfället
Omedelbar behandlingAvgifter processas omedelbart med confirm: true
Smidig UXKunderna stannar kvar i din app under hela flödet

Hur det fungerar

Förutsättningar

Innan du implementerar uppersell och neddersell, se till att du har:
1

Kund med sparad betalningsmetod

Kunder måste ha genomfört minst ett köp. Betalningsmetoder sparas automatiskt när kunder slutför kassaflödet.
2

Produkter konfigurerade

Skapa dina uppersellprodukter i Dodo Payments-instrumentpanelen. Dessa kan vara engångsbetalningar, abonnemang eller tillägg.
3

Webhook-endpoint

Ställ in webhooks för att hantera payment.succeeded, payment.failed och subscription.plan_changed händelser.

Hämta kundbetalningsmetoder

Innan du erbjuder en uppersell, hämta kundens sparade betalningsmetoder:
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;
Betalningsmetoder sparas automatiskt när kunder slutför kassaflödet. Du behöver inte spara dem uttryckligen.

Engångsupersell efter köp

Erbjud ytterligare produkter omedelbart efter ett lyckat köp. Kunden kan godkänna med ett enda klick eftersom deras betalningsmetod redan är sparad.

Implementering

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;
}
När du använder payment_method_id, måste du ange confirm: true och ge en befintlig customer_id. Betalningsmetoden måste tillhöra den kunden.

Uppgraderingar av abonnemang

Flytta kunder till abonnemangsplaner av högre nivå med automatisk proration.

Förhandsgranska innan du åtar dig

Förhandsgranska alltid planändringar för att visa kunder exakt vad de kommer att debiteras:
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}`);

Genomföra uppgraderingen

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

Prorateringslägen

Välj hur kunder debiteras vid uppgradering:
LägeBeteendeBästa för
difference_immediatelyDebiterar prisskillnaden omedelbart (3030→80 = $50)Enkla uppgraderingar
prorated_immediatelyDebiterar baserat på återstående tid i faktureringscykelRättvis tidsbaserad fakturering
full_immediatelyDebiterar full pris för ny plan, ignorerar återstående tidFaktureringscykel återställs
Använd difference_immediately för raka uppgraderingsflöden. Använd prorated_immediately när du vill ta hänsyn till oanvänd tid på den aktuella planen.

Korsförsäljning

Lägg till kompletterande produkter för befintliga kunder utan att behöva ange betalningsuppgifter igen.

Implementering

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

Nedgradering av abonnemang

När kunder vill flytta till en plan av lägre nivå, hantera övergången smidigt med automatisk kredit.

Hur nedgraderingar fungerar

  1. Kunder begär nedgradering (Pro → Basic)
  2. Systemet beräknar återstående värde på aktuell plan
  3. Kredit läggs till abonnemanget för framtida förnyelser
  4. Kunden flyttas omedelbart till den nya planen
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');
Krediter från nedgreringar med difference_immediately är abonnemangsomfattade och tillämpas automatiskt på framtida förnyelser. De är åtskilda från Kundkrediter.

Fullständigt exempel: Uppersell-flöde efter köp

Här är en komplett implementering som visar hur man erbjuder en uppersell efter ett lyckat köp:
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);

Bästa metoder

Den bästa tiden att erbjuda en uppersell är omedelbart efter ett lyckat köp när kunder är i ett köpande sinne. Andra effektiva ögonblick:
  • Efter funktionanvändningens milstolpar
  • När de närmar sig planbegränsningar
  • Under fullständigt genomförande av onboarding
Innan du försöker en engångsdebitering, verifiera betalningsmetoden:
  • Är kompatibel med produktens valuta
  • Har inte gått ut
  • Tillhör kunden
API:et kommer att validera dessa, men att kontrollera proaktivt förbättrar UX.
När engångsdebiteringar misslyckas:
  1. Gå tillbaka till standard kassaflöde
  2. Meddela kunden med tydlig information
  3. Erbjud att uppdatera betalningsmetod
  4. Försök inte upprepade gånger misslyckade debiteringar
Uppersell konverterar bättre när kunder förstår värdet:
  • Visa vad de får i förhållande till aktuell plan
  • Framhäv prisskillnaden, inte totalpriset
  • Använd socialt bevis (t.ex. “Mest populära uppgradering”)
  • Ge alltid ett enkelt sätt att avböja
  • Visa inte samma uppersell upprepade gånger efter avböjning
  • Spåra och analysera vilka uppersell som konverterar för att optimera erbjudanden

Webhooks för övervakning

Spåra dessa webhook-händelser för uppersell och nedgradering flöden:
HändelseTriggerÅtgärd
payment.succeededBetalning av uppersell/korsförsäljning slutfördLeverera produkt, uppdatera åtkomst
payment.failedEngångsdebitering misslyckadesVisa fel, erbjud försök igen eller fallback
subscription.plan_changedUppgradering/nedgradering slutfördUppdatera funktioner, skicka bekräftelse
subscription.activeAbonnemang återaktiverat efter planändringGe åtkomst till ny nivå

Webbhook integrationsguide

Lär dig hur du ställer in och verifierar webhook-endpoints.

Relaterade resurser