Saltar al contenido principal
Cuando un pago falla, Dodo Payments te indica por qué a través de un error_code estandarizado y un error_message legible para humanos. Esta guía muestra cómo leer esos campos, decidir si vale la pena reintentar, y recuperar el pago sin exponer información sensible a los clientes.

Cómo Dodo Payments Informa un Fallo

Cada pago fallido, ya sea una compra única o una renovación de suscripción, lleva los mismos campos de fallo en el objeto de pago:
CampoTipoDescripción
statusstringfailed para un pago fallido. Otros estados no exitosos incluyen cancelled, requires_customer_action, y requires_payment_method.
error_codestring | nullLa razón estandarizada del fallo, por ejemplo INSUFFICIENT_FUNDS o PROCESSING_ERROR. Consulta la referencia de Fallos de Transacción para la lista completa.
error_messagestring | nullUna explicación legible para humanos del fallo.
retry_attemptinteger0 para el cargo original. 1 o superior identifica un reintento de renovación de suscripción programada.
error_code e error_message son null hasta que un pago realmente falle. Siempre verifica status primero, luego lee los campos de error.

El Webhook payment.failed

La manera más confiable de detectar un fallo es el webhook payment.failed. El evento envuelve el objeto de pago completo en data:
payment.failed payload
{
  "business_id": "bus_P3SXLcppjXgagmHS",
  "type": "payment.failed",
  "timestamp": "2025-08-04T05:36:41.609359Z",
  "data": {
    "payload_type": "Payment",
    "payment_id": "pay_2IjeQm4hqU6RA4Z4kwDee",
    "status": "failed",
    "error_code": "PROCESSING_ERROR",
    "error_message": "An error occurred while processing your card. Try again in a little bit.",
    "retry_attempt": 0,
    "subscription_id": null,
    "currency": "USD",
    "total_amount": 400,
    "payment_method": "card",
    "card_last_four": "0119",
    "card_network": "VISA",
    "payment_link": "https://test.checkout.dodopayments.com/cbq",
    "customer": {
      "customer_id": "cus_8VbC6JDZzPEqfB",
      "email": "test@acme.com",
      "name": "Test user"
    }
  }
}
Un manejador mínimo lee error_code y lo encamina:
import { Webhook } from "standardwebhooks";
import express from "express";

const app = express();
// Mount the raw body parser so the exact payload is available for verification
app.use(express.raw({ type: "application/json" }));

const webhook = new Webhook(process.env.DODO_PAYMENTS_WEBHOOK_KEY);

app.post("/webhooks/dodo", async (req, res) => {
  // Verify the signature against the raw body before trusting the payload
  const payload = req.body.toString();
  await webhook.verify(payload, req.headers);

  const event = JSON.parse(payload);

  if (event.type === "payment.failed") {
    const payment = event.data;

    console.log(
      `Payment ${payment.payment_id} failed: ${payment.error_code} (${payment.error_message})`
    );

    if (payment.subscription_id) {
      // Subscription renewal — Dodo retries soft declines for you
      await flagSubscriptionPaymentIssue(payment.subscription_id, payment.error_code);
    } else {
      // One-time payment — prompt the customer to try again
      await notifyCustomerOfFailedPayment(payment.customer.customer_id, payment.error_code);
    }
  }

  res.json({ received: true });
});
Siempre verifica la firma del webhook antes de procesar. Consulta la guía de Webhooks para la configuración completa, incluyendo la verificación de firma e idempotencia.

Decidir si Reintentar: Rechazos Suaves vs. Duros

El error_code te indica si vale la pena reintentar el mismo método de pago.
Tipo de RechazoQué significaQué hacer
Rechazo suaveTemporal o corregible (por ejemplo INSUFFICIENT_FUNDS, PROCESSING_ERROR, NETWORK_ERROR, TRY_AGAIN_LATER).Reintentar — después de un retraso, o una vez que el cliente corrija su entrada — puede tener éxito.
Rechazo duroTerminal (por ejemplo STOLEN_CARD, LOST_CARD, DO_NOT_HONOR, FRAUDULENT).No reintentar con la misma tarjeta. Solicita al cliente un método de pago diferente.
La referencia de Fallos de Transacción lista el tipo de rechazo y la acción recomendada para cada error_code.

Manejo de Fallos en el Pago vs. en la Renovación

Cómo te recuperas depende de si el cliente está presente.
El cliente está realizando la compra activamente. Muestra un mensaje claro y permite que reintenten inmediatamente o usen otra tarjeta.
  • requires_payment_method — el cliente nunca proporcionó un método de pago: no ingresaron detalles de la tarjeta o se les solicitó uno y no tomaron acción. Esto usualmente es un abandono de carrito de compra, no un rechazo — vuelve a involucrar al cliente para completar el pago (consulta Recuperación de Carrito Abandonado).
  • requires_customer_action — se necesita autenticación adicional (como 3DS); haz que el cliente la complete. Consulta el manejo de 3D Secure.

Reintentando un Pago Fallido

  • Suscripciones: Activa Reintentos de Pago de Suscripción para recuperar rechazos suaves sin trabajo de integración. También puedes desencadenar la recuperación haciendo que el cliente actualice su método de pago a través de la API de Actualización de Método de Pago, que carga cualquier saldo pendiente.
  • Pagos únicos: Reenvía el checkout o payment_link para que el cliente pueda intentarlo de nuevo con un método diferente. No hay reintento automático para pagos únicos.
No reintentes rechazos duros con la misma tarjeta. Las redes de tarjetas pueden marcar rechazos repetidos como abusivos, lo que perjudica tu tasa de autorización.

Mostrar Errores a los Clientes de Manera Segura

Muestra a los clientes un mensaje amigable — nunca el error_code crudo.
Customer-facing messaging
const CUSTOMER_MESSAGES = {
  INSUFFICIENT_FUNDS: "Your card has insufficient funds. Please use another card.",
  EXPIRED_CARD: "Your card has expired. Please use a card with a valid expiry date.",
  INCORRECT_CVC: "The security code (CVC) is incorrect. Please re-enter it.",
};

function customerMessage(errorCode) {
  // Sensitive declines must never reveal the real reason
  const SENSITIVE = ["STOLEN_CARD", "LOST_CARD", "PICKUP_CARD", "FRAUDULENT"];
  if (SENSITIVE.includes(errorCode)) {
    return "Your card was declined. Please contact your bank or use another card.";
  }
  return CUSTOMER_MESSAGES[errorCode] ?? "Your payment could not be processed. Please try another card.";
}
Nunca reveles la razón real para STOLEN_CARD, LOST_CARD, PICKUP_CARD, o FRAUDULENT. Mostrar estos puede alertar a un actor fraudulento. Muestra un mensaje genérico de rechazo y solo registra el error_code específico internamente.

Relacionado

Transaction Failures

Cada código de rechazo, su tipo, y la acción recomendada.

Error Codes

Errores de API y lógica de negocio que no son rechazos de tarjeta.

Subscription Payment Retries

Recuperación automática de rechazos suaves en renovaciones de suscripción.

Subscription Dunning

Secuencias de email que recuperan rechazos duros.

Payment Webhooks

Esquema completo de carga útil para eventos de pago.

Testing Failures

Tarjetas de prueba que simulan rechazos y fallos de renovación.
Última modificación el 18 de junio de 2026