Mono Colombia

Errores de intención de pago

Por qué una intención de pago termina fallida o expirada, y qué razones hereda del pago que la cerró.

Una intención de pago lleva state_reason en sus dos estados sin éxito:

  • failed: no queda nada por pagar. O el token de pago nunca llegó a existir, o un pago cerró la intención de pago definitivamente.
  • expired: la ventana se cerró sin que nadie pagara.

Lees el código en los webhooks payin_intent_failed y payin_intent_expired, y en la intención de pago misma con get o list.

Ejemplo de payload del webhook

{
  "timestamp": "2026-02-11T14:22:07.913204Z",
  "event": {
    "type": "payin_intent_failed",
    "data": {
      "id": "cpii_034EASMIAFVq06j2NxhnIJ",
      "state": "failed",
      "state_reason": "payment_token_registration_failed",
      "external_id": "order-88213",
      "template_id": "cpit_034EASMHlq36oZISbc9W4b"
    }
  }
}

Las razones propias de la intención de pago

Lo que le puede pasar a una intención de pago antes de que exista un pago, más el comodín para un pago que falló sin una explicación que valga la pena darle al pagador.

CódigoDescripciónCausaSolución sugerida
expiredVentana cerradaNadie pagó antes del expires_at. Para Bre-B la ventana es de 30 minutos.Crea una intención de pago nueva. La anterior libera su external_id al expirar.
key_registration_failedFalló el registro de la llaveRegistrar la llave de pago propia de la intención de pago en la red agotó sus reintentos.Crea la intención de pago otra vez. Si se repite, contacta a soporte con el id de la intención de pago.
payment_token_registration_failedFalló la creación del token de pagoLa llave se registró, pero el token de pago no pudo armarse a partir de ella.Crea la intención de pago otra vez. Si se repite, contacta a soporte con el id de la intención de pago.
payment_failedEl pago fallóUn pago cerró la intención de pago por una razón que no le dice nada al pagador: una falla de infraestructura como un timeout, un fallo del ledger o una caída del proveedor.Lee el state_reason del pago para el detalle; crea una intención de pago nueva para reintentar.
unknownDesconocidoEl pago que cerró la intención de pago falló sin una razón conocida.Contacta a soporte con el id de la intención de pago.

Razones heredadas del pago

Cuando un pago cierra una intención de pago por una razón sobre la que el pagador o el comercio pueden actuar, la intención de pago adopta la razón del pago con el mismo nombre en vez de aplanarla en payment_failed. Cada una significa exactamente lo mismo que en el pago, así que la tabla de allá es la referencia:

GrupoCódigos
Límites de montoamount_below_minimum, amount_exceeds_maximum, amount_below_total_minimum, amount_exceeds_total_maximum, intent_amount_mismatch
Restricciones del pagadorpayer_not_allowed
Banco y riesgobank_rejected, risk_control
Condiciones de la plantillatemplate_paid, template_expired, template_disabled, template_deleted, template_not_found, template_invalid_state

La división es deliberada: lo que el pagador puede corregir (el monto, no ser él quien puede pagar) y lo que el comercio necesita distinguir (de qué forma dejó de recibir pagos el cobro, o que la cuenta receptora no puede recibir dinero) conservan su nombre; las fallas de infraestructura no, porque no significan nada para quien está mirando un checkout.

Hoy no son alcanzables con Bre-B

Estas razones heredadas sólo aparecen con métodos de pago que no re-arman la intención de pago tras un pago fallido. Bre-B, el único método disponible hoy, sí re-arma: un pago fallido devuelve la intención de pago a ready_to_pay para que el pagador reintente con la misma llave mientras dure la ventana, y la intención de pago no se cierra. Hasta que exista otro método, una intención de pago que termine failed llevará una de sus razones propias. Manéjalas de forma defensiva en vez de esperarlas.

Un pago fallido no siempre es una intención de pago fallida

La mayoría de los pagos fallidos no cierran la intención de pago. Con un método que re-arma, la intención de pago vuelve a ready_to_pay y emite payin_intent_attempt_failed. El intento fallido lo reporta el propio webhook payin_rejected o payin_failed del pago, que lleva la razón real.

O sea, hay dos preguntas distintas y dos eventos distintos:

PreguntaEventoDónde vive la razón
¿Por qué falló este intento?payin_rejected / payin_failedEl state_reason del pago (ver Errores de pago).
¿Por qué esta orden ya no puede pagarse?payin_intent_failed / payin_intent_expiredEl state_reason de la intención de pago (esta página).

Cómo manejarlos

app.post('/webhook', (req, res) => {
  const { event } = req.body;

  // La orden todavía puede pagarse: el pagador sólo tiene que reintentar
  if (event.type === 'payin_intent_attempt_failed') {
    // Mantén el checkout abierto; la llave sigue siendo válida
  }

  // La orden terminó
  if (event.type === 'payin_intent_failed' || event.type === 'payin_intent_expired') {
    const { external_id, state_reason } = event.data;

    switch (state_reason) {
      case 'expired':
        // Nadie pagó a tiempo: ofrece un checkout nuevo
        break;
      case 'key_registration_failed':
      case 'payment_token_registration_failed':
        // La intención de pago nunca llegó a ser pagable: créala de nuevo
        break;
      default:
      // Un pago la cerró: lee el pago para el detalle y emite una intención de pago nueva
    }
  }

  res.status(200).send();
});

Saber si una orden fue pagada

No infieras el pago por la ausencia de una falla. GET /v1/core/payins/intents/find_successful_by?external_id=... lo responde directo: existe a lo sumo una intención de pago successful por identificador.

Siguientes pasos

En esta página