Pular para o conteúdo principal
Upsells e downsells permitem oferecer produtos adicionais ou mudanças de plano para os clientes usando seus métodos de pagamento salvos. Isso possibilita compras de um clique que ignoram a coleta de pagamento, melhorando drasticamente as taxas de conversão.

Post-Purchase Upsells

Ofereça produtos complementares imediatamente após o checkout com compras de um clique.

Subscription Upgrades

Mova clientes para níveis superiores com prorrateamento automático e faturamento instantâneo.

Cross-Sells

Adicione produtos relacionados a clientes existentes sem pedir novamente os dados de pagamento.

Visão geral

Upsells e downsells são estratégias poderosas de otimização de receita:
  • Upsells: Ofereça um produto de maior valor ou upgrade (ex.: plano Pro em vez de Basic)
  • Downsells: Ofereça uma alternativa de menor preço quando o cliente recusar ou fizer downgrade
  • Cross-sells: Sugira produtos complementares (ex.: add-ons, itens relacionados)
A Dodo Payments habilita esses fluxos por meio do parâmetro payment_method_id, que permite cobrar o método de pagamento salvo do cliente sem exigir que ele digite novamente os dados do cartão.

Benefícios principais

BenefícioImpacto
Compras de um cliquePule completamente o formulário de pagamento para clientes recorrentes
Maior conversãoReduza o atrito no momento da decisão
Processamento instantâneoAs cobranças são processadas imediatamente com confirm: true
Experiência contínuaOs clientes permanecem no seu app durante todo o fluxo

Como funciona

Pré-requisitos

Antes de implementar upsells e downsells, garanta que você tenha:
1

Customer with Saved Payment Method

Os clientes devem ter concluído pelo menos uma compra. Os métodos de pagamento são salvos automaticamente quando os clientes finalizam o checkout.
2

Products Configured

Crie seus produtos de upsell no painel da Dodo Payments. Eles podem ser pagamentos únicos, assinaturas ou complementos.
3

Webhook Endpoint

Configure webhooks para lidar com os eventos payment.succeeded, payment.failed e subscription.plan_changed.

Obtendo métodos de pagamento do cliente

Antes de oferecer um upsell, recupere os métodos de pagamento salvos do 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;
Os métodos de pagamento são salvos automaticamente quando os clientes finalizam o checkout. Você não precisa salvá-los explicitamente.

Upsells pós-compra de um clique

Ofereça produtos adicionais imediatamente após uma compra bem-sucedida. O cliente pode aceitar com um único clique, pois seu método de pagamento já está salvo.

Implementação

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;
}
Ao usar payment_method_id, você deve definir confirm: true e fornecer um customer_id existente. O método de pagamento deve pertencer a esse cliente.

Upgrades de assinatura

Mova os clientes para planos de assinatura de nível superior com tratamento automático de prorrateamento.

Visualizar antes de confirmar

Sempre visualize as alterações de plano para mostrar aos clientes exatamente o que será cobrado:
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 o 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);

Modos de prorrateamento

Escolha como os clientes são cobrados ao fazer upgrade:
ModoComportamentoIdeal para
difference_immediatelyCobra a diferença de preço instantaneamente (3030→80 = $50)Upgrades simples
prorated_immediatelyCobra com base no tempo restante no ciclo de faturamentoCobrança justa baseada no tempo
full_immediatelyCobra o preço total do novo plano, ignorando o tempo restanteCiclo de faturamento reinicia
Use difference_immediately para fluxos de upgrade diretos. Use prorated_immediately quando quiser contabilizar o tempo não utilizado no plano atual.

Cross-sells

Adicione produtos complementares para clientes existentes sem exigir que eles reentrem os dados de pagamento.

Implementação

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

Downgrades de assinatura

Quando os clientes quiserem migrar para um plano inferior, trate a transição com elegância usando créditos automáticos.

Como funcionam os downgrades

  1. Cliente solicita downgrade (Pro → Basic)
  2. Sistema calcula o valor restante no plano atual
  3. Crédito é adicionado à assinatura para futuras renovações
  4. Cliente muda para o novo plano imediatamente
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');
Os créditos de downgrades usando difference_immediately são vinculados à assinatura e aplicados automaticamente às renovações futuras. Eles são diferentes dos Customer Credits.

Exemplo completo: fluxo de upsell pós-compra

Aqui está uma implementação completa mostrando como oferecer um upsell após uma compra bem-sucedida:
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);

Melhores práticas

O melhor momento para oferecer um upsell é imediatamente após uma compra bem-sucedida, quando os clientes estão em uma mentalidade de compra. Outros momentos eficazes:
  • Após marcos de uso de recursos
  • Ao se aproximar dos limites do plano
  • Durante a conclusão do onboarding
Antes de tentar uma cobrança de um clique, verifique o método de pagamento:
  • É compatível com a moeda do produto
  • Não expirou
  • Pertence ao cliente
A API validará isso, mas verificar proativamente melhora a experiência.
Quando cobranças de um clique falharem:
  1. Retorne ao fluxo de checkout padrão
  2. Notifique o cliente com mensagens claras
  3. Ofereça atualizar o método de pagamento
  4. Não tente cobranças falhas repetidamente
Upsells convertem melhor quando os clientes entendem o valor:
  • Mostre o que estão recebendo em comparação ao plano atual
  • Destaque a diferença de preço, não o preço total
  • Use prova social (ex.: “Upgrade mais popular”)
  • Sempre ofereça uma forma fácil de recusar
  • Não mostre o mesmo upsell repetidamente após uma recusa
  • Acompanhe e analise quais upsells convertem para otimizar as ofertas

Webhooks para monitorar

Acompanhe estes eventos de webhook para fluxos de upsell e downgrade:
EventoGatilhoAção
payment.succeededPagamento de upsell/cross-sell concluídoEntregue o produto, atualize o acesso
payment.failedCobrança de um clique falhouMostre o erro, ofereça nova tentativa ou fallback
subscription.plan_changedUpgrade/downgrade concluídoAtualize os recursos, envie confirmação
subscription.activeAssinatura reativada após mudança de planoConceda acesso ao novo nível

Webhook Integration Guide

Aprenda como configurar e verificar endpoints de webhook.

Recursos relacionados