Saltar al contenido principal
Los upsells y downsells te permiten ofrecer productos adicionales o cambios de plan a los clientes utilizando sus métodos de pago guardados. Esto habilita compras de un clic que omiten la recopilación de pagos, mejorando drásticamente las tasas de conversión.

Upsells Post-Pedido

Ofrece productos complementarios inmediatamente después de la compra con compra de un clic.

Actualizaciones de Suscripción

Mueve a los clientes a niveles más altos con prorrateo automático y facturación instantánea.

Ventas Cruzadas

Agrega productos relacionados a clientes existentes sin necesidad de reingresar detalles de pago.

Descripción General

Los upsells y downsells son poderosas estrategias de optimización de ingresos:
  • Upsells: Ofrecer un producto o actualización de mayor valor (por ejemplo, plan Pro en lugar de Básico)
  • Downsells: Ofrecer una alternativa de menor precio cuando un cliente rechaza o degrada
  • Ventas Cruzadas: Sugerir productos complementarios (por ejemplo, complementos, artículos relacionados)
Dodo Payments habilita estos flujos a través del payment_method_id parámetro, que te permite cargar el método de pago guardado de un cliente sin requerir que reingrese los detalles de la tarjeta.

Beneficios Clave

BeneficioImpacto
Compras de un clicOmitir completamente el formulario de pago para clientes recurrentes
Mayor conversiónReducir la fricción en el momento de la decisión
Procesamiento instantáneoLos cargos se procesan inmediatamente con confirm: true
Experiencia de usuario sin interrupcionesLos clientes permanecen en tu aplicación durante todo el flujo

Cómo Funciona

Requisitos Previos

Antes de implementar upsells y downsells, asegúrate de tener:
1

Cliente con Método de Pago Guardado

Los clientes deben haber completado al menos una compra. Los métodos de pago se guardan automáticamente cuando los clientes terminan el proceso de compra.
2

Productos Configurados

Crea tus productos de upsell en el panel de control de Dodo Payments. Estos pueden ser pagos únicos, suscripciones o complementos.
3

Webhook Endpoint

Configura webhooks para manejar los eventos payment.succeeded, payment.failed y subscription.plan_changed.

Obtener Métodos de Pago del Cliente

Antes de ofrecer un upsell, recupera los métodos de pago guardados 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;
Los métodos de pago se guardan automáticamente cuando los clientes completan el proceso de compra. No necesitas guardarlos explícitamente.

Upsells de Uno-Clic Post-PCompra

Ofrece productos adicionales inmediatamente después de una compra exitosa. El cliente puede aceptarlo con un solo clic ya que su método de pago ya está guardado.

Implementación

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;
}
Al usar payment_method_id, debes establecer confirm: true y proporcionar un customer_id existente. El método de pago debe pertenecer a ese cliente.

Actualizaciones de Suscripción

Mueve a los clientes a planes de suscripción de nivel superior con manejo automático del prorrateo.

Vista Previa Antes de Confirmar

Siempre visualiza los cambios de plan para mostrar a los clientes exactamente lo que se les cobrará:
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}`);

Ejecutar la Actualización

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 Prorrateo

Elige cómo se facturan los clientes al actualizar:
ModoComportamientoMejor Para
difference_immediatelyCarga la diferencia de precio al instante (3030→80 = $50)Actualizaciones simples
prorated_immediatelyCarga basada en el tiempo restante en el ciclo de facturaciónFacturación justa basada en el tiempo
full_immediatelyCarga el precio completo del nuevo plan, ignora el tiempo restanteEl ciclo de facturación se reinicia
Usa difference_immediately para flujos de actualización sencillos. Usa prorated_immediately cuando quieras tener en cuenta el tiempo no utilizado en el plan actual.

Ventas Cruzadas

Agrega productos complementarios para clientes existentes sin requerir que reingresen detalles de pago.

Implementación

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 Suscripción

Cuando los clientes quieran moverse a un plan de nivel inferior, maneja la transición de manera suave con créditos automáticos.

Cómo Funcionan los Downgrades

  1. El cliente solicita un downgrade (Pro → Básico)
  2. El sistema calcula el valor restante del plan actual
  3. Se agregan créditos a la suscripción para futuras renovaciones
  4. El cliente se mueve al nuevo plan inmediatamente
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');
Los créditos de los downgrades usando difference_immediately son específicos de la suscripción y se aplican automáticamente a futuras renovaciones. Son distintos de los Créditos de Cliente.

Ejemplo Completo: Flujo de Upsell Post-PCompra

Aquí tienes una implementación completa que muestra cómo ofrecer un upsell después de una compra exitosa:
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);

Mejores Prácticas

El mejor momento para ofrecer un upsell es inmediatamente después de una compra exitosa, cuando los clientes están en una mentalidad de compra. Otros momentos efectivos:
  • Después de alcanzar hitos de uso de funciones
  • Cuando se acercan a los límites del plan
  • Durante la finalización del onboarding
Antes de intentar un cargo de un clic, verifica el método de pago:
  • Es compatible con la moneda del producto
  • No ha caducado
  • Pertenece al cliente
La API validará esto, pero verificar proactivamente mejora la experiencia del usuario.
Cuando los cargos de un clic fallan:
  1. Vuelve al flujo estándar de compra
  2. Notifica al cliente con un mensaje claro
  3. Ofrece actualizar el método de pago
  4. No intentes cargas fallidas repetidamente
Los upsells convierten mejor cuando los clientes entienden el valor:
  • Muestra lo que están obteniendo frente al plan actual
  • Destaca la diferencia de precio, no el precio total
  • Usa prueba social (por ejemplo, “Actualización más popular”)
  • Siempre proporciona una forma fácil de rechazar
  • No muestres el mismo upsell repetidamente después de un rechazo
  • Rastrea y analiza qué upsells se convierten para optimizar las ofertas

Webhooks para Monitorear

Rastrea estos eventos de webhook para los flujos de upsell y downgrade:
EventoDisparadorAcción
payment.succeededPago de upsell/venta cruzada completadoEntregar producto, actualizar acceso
payment.failedFallo en el cargo de un clicMostrar error, ofrecer reintentar o recurrir
subscription.plan_changedActualización/downgrade completadoActualizar características, enviar confirmación
subscription.activeSuscripción reactivada después del cambio de planOtorgar acceso al nuevo nivel

Guía de Integración de Webhooks

Aprende cómo configurar y verificar endpoints de webhook.

Recursos Relacionados