Recaudos
Simula que un pagador paga tus recaudos, y provoca las fallas que necesitas probar.
En producción el dinero llega cuando una persona real decide pagarte. En el sandbox esa persona eres tú: una sola petición hace que un pago aterrice en uno de tus recaudos, y todo lo que sigue se comporta como si el dinero fuera real. Así construyes y pruebas un recaudo de punta a punta antes de tocar producción.
El pago simulado corre sobre el riel real: Bre-B, la red de transferencias inmediatas de Colombia. Los mismos estados, los mismos webhooks, los mismos tiempos. Sólo el dinero no es real.
Antes de empezar
Necesitas:
- Una API key de sandbox, y las peticiones apuntando a
https://api.sandbox.cuentamono.com. - El scope
payinsen tus credenciales. - Algo que se pueda pagar: una plantilla en estado
ready, o una intención de pago en estadoready_to_pay.
Ese último punto es el que más tropieza. Una plantilla o una intención de pago no es pagable en el
momento en que la creas. Mono todavía tiene que registrar su llave Bre-B ante la red. Espera
primero el webhook
payin_template_ready o
payin_intent_ready_to_pay.
Simular contra un objetivo que aún no tiene llave responde 400 con payment_token_not_found.
Sólo sandbox. En cualquier otro ambiente este endpoint responde 403 con
feature_not_enabled, así que una llamada que se filtre al código de producción no puede
acuñar pagos.
Qué hace realmente una simulación
Mono opera directamente sobre Bre-B, así que el sandbox no finge el pago: recorre el mismo flujo de aceptación que una transferencia real. Nombras un objetivo y un monto, y Mono construye un pago entrante contra la llave Bre-B de ese objetivo, exactamente como si un pagador hubiera escaneado su QR.
La respuesta es un recibo, no un veredicto. Te dice que existe un pago; su resultado te llega unos segundos después por los webhooks de recaudos, igual que uno real.
Las dos formas de un pago
Cuál envías depende de qué estás pagando. Una plantilla acepta muchos pagos y no sabe nada de este en particular, así que la petición tiene que traer su propio método y monto. Una intención de pago fue declarada para un pago específico y ya fija ambos.
| Si pagas una... | Envías | Porque |
|---|---|---|
| Plantilla | template_id + method + amount | La plantilla acepta muchos pagos; este trae los suyos |
| Intención de pago | intent_id | La intención de pago ya fijó el método y el monto |
Las dos formas son excluyentes. Nombrar ambos objetivos, o enviar un method junto a un
intent_id, responde 422.
Pagar una plantilla
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" }
}method tiene que ser uno que la plantilla acepte, o la llamada responde 400 con
method_not_allowed. Hoy co_breb es el único método de pago que ofrece Recaudos de Core. Los
montos son enteros en centavos, así que el ejemplo de arriba son 50.000 COP.
Pagar una intención de pago
{
"intent_id": "cpint_5tgliBmzjZ6mpQPRbQjfKj"
}Esa es toda la petición. La intención de pago aporta el método y el monto, y eso es justo lo que la hace útil para probar: si envías esto, estás verificando el monto que la intención de pago exige, no uno que retecleaste.
Leer la respuesta
El código de estado te dice si nació un pago:
| Estado | Cuerpo | Qué pasó |
|---|---|---|
201 | { "payin_id": "cpin_...", "state_reason": null } | El pago entró. Síguelo por los webhooks. |
201 | { "payin_id": "cpin_...", "state_reason": "bank_rejected" } | El pago existe y ya fue rechazado por sus propias reglas. |
200 | { "payin_id": null, "state_reason": "amount_below_minimum" } | El pago se rehusó antes de quedar registrado. |
Ese 200 merece atención. Cuando la autorización de pago de Mono rehúsa el pago (un monto fuera
de los límites de la plantilla, un pagador que no espera), el pago no queda registrado y no se
emite ningún webhook. En producción la app del banco del pagador es la que reporta el rechazo,
así que esta respuesta es el único lugar donde tu sistema puede enterarse. Provocar esos rechazos
es buena parte de para qué existe este endpoint.
Elegir quién paga
Si omites payer, Mono se inventa uno: un nombre colombiano al azar y un documento CC de 10
dígitos al azar. Suficiente para la mayoría de pruebas. Envíalo cuando el pagador sea el punto de la
prueba:
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"payer": {
"document": { "type": "CC", "number": "1234567890" },
"name": "Juan Garcia"
}
}Así ejercitas la lista
expected_payers de una
plantilla. Envía un documento de la lista para confirmar que el pago pasa; envía uno que no esté
para confirmar que obtienes payer_not_allowed. El type del documento acepta CC, CE, NIT,
PA y TI.
Provocar fallas
Hay dos tipos de falla, y se provocan de dos maneras distintas. Tener clara la diferencia ahorra mucha confusión.
Fallas del riel: el campo error
Son condiciones de infraestructura que el riel reporta después de que el pago ya fue aceptado.
Nombra una en error y el sandbox le pide al riel que la produzca:
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"error": "risk_control"
}La llamada sigue respondiendo 201 con un payin_id y un state_reason nulo: el pago fue
aceptado, así que Mono ya lo registró. La falla llega 2 a 5 segundos después, por el webhook:
error | El pago termina | Significado |
|---|---|---|
risk_control | failed / risk_control | Los controles de riesgo del proveedor lo frenaron |
provider_unavailable | failed / provider_unavailable | El proveedor de pagos estaba fuera de servicio |
breb_timeout | failed / breb_timeout | El riel Bre-B no respondió a tiempo |
unknown | failed / unknown | Una falla sin clasificar |
Las cuatro dejan el pago en failed y te llegan como
payin_failed. Si el pago iba
dirigido a una intención de pago, esta reporta aparte: se rearma para un reintento con
payin_intent_attempt_failed,
o se da por vencida con
payin_intent_failed.
Rechazos de negocio: por construcción
Todo lo que atrapa la validación de Mono no tiene palanca, y no la necesita: lo provocas enviando un
pago que el objetivo genuinamente rehúsa. Se rehúsan de forma sincrónica, así que vuelven en la
respuesta misma como un 200 sin pago registrado.
| Para obtener | Envía |
|---|---|
amount_below_minimum | Un monto por debajo del minimum_payin_amount de la plantilla |
amount_exceeds_maximum | Un monto por encima de su maximum_payin_amount |
amount_exceeds_total_maximum | Un monto que la pase de su total_maximum_amount |
payer_not_allowed | Un documento en payer que no esté en expected_payers |
template_expired / intent_expired | Un pago después del expires_at del objetivo |
template_paid | Un segundo pago a una plantilla single_use |
template_disabled | Un pago a una plantilla que actualizaste a enabled: false |
template_deleted | Un pago a una plantilla que eliminaste |
Cada código, con su causa y qué hacer al respecto, está en Errores de pago.
Una razón que no alcanzas desde aquí
intent_amount_mismatch queda fuera de alcance en el sandbox. Un pago a una intención de pago
siempre toma el monto de la intención de pago misma, así que no hay manera de enviar uno
distinto. Para ese caso, confía en la validación propia de la intención de pago.
Simular el QR que escaneó el pagador
Un pago Bre-B puede llevar el identificador del QR del que salió. Envíalo cuando tu conciliación lea ese campo:
{
"template_id": "cpitpl_5tgliBmzjZ6mpQPRbQjfKj",
"method": "co_breb",
"amount": { "amount": 5000000, "currency": "COP" },
"method_params": { "payment_id": "CO.COM.CRB.TRXIDP1234567891234" }
}Si lo omites, el riel asigna uno. method_params es específico del método: para co_breb el único
campo es payment_id.
Errores del endpoint mismo
Son fallas en la petición, no resultados de pago simulados.
| Estado | Código | Causa |
|---|---|---|
400 | method_not_allowed | La plantilla no acepta el method que enviaste. |
400 | payin_invalid_method_params | Los method_params no corresponden al método. |
400 | payment_token_not_found | El objetivo aún no tiene llave Bre-B: no está ready / ready_to_pay. |
401 | missing_authorization_header, invalid_token | Problema de credenciales. |
403 | feature_not_enabled | No es un ambiente sandbox, o Recaudos no está habilitado para tu cuenta. |
404 | template_not_found, payin_intent_not_found | No existe el objetivo. El de otro tenant se lee como uno que no existe. |
422 | No aplica | La petición viola el esquema: ambos objetivos, o un campo faltante. |
Una prueba completa, de punta a punta
Todo junto, un recaudo que puedes correr de una sentada:
- Crea una plantilla. Espera el
payin_template_ready. - Simula un pago por debajo del mínimo. Espera
200,amount_below_minimum, ningún webhook. - Simula un pago válido. Espera
201, y luegopayin_created. - Unos segundos después, espera
payin_approved_in_providerypayin_subaccount_credited. - Simula otro con
"error": "breb_timeout". Espera201, y luegopayin_failed. - Lee la plantilla. Su
paid_amountcuenta el paso 3 y no los pasos 2 ni 5.
Si tu integración maneja los seis, maneja un recaudo real.
Siguientes pasos
- Recaudar con una plantilla: el flujo que prueba este endpoint.
- Recaudar con intenciones de pago: lo mismo, un pago declarado a la vez.
- Errores de pago: todas las razones por las que un pago termina mal.
- Simula un pago: la referencia del endpoint.