Ir al contenido

Cobros (pay-ins)

Un cobro es dinero que entra. Hay cuatro métodos, en dos familias:

  • debit_otp: el banco del pagador le manda un código a su usuario; su usuario lo teclea en la pantalla de usted; usted lo confirma. Tres pasos con una persona en el medio.
  • direct_debit: débito domiciliado, con un mandate (contrato) que el pagador firmó con su banco. Un solo paso.
  • incoming_mobile_payment e incoming_transfer: el pagador manda el dinero por su cuenta (un Pago Móvil o una transferencia desde su banco) a la cuenta receptora; usted lo espera con un cobro creado antes.
  1. POST /v2/payins con method: incoming_mobile_payment (o incoming_transfer), el amount exacto y en payer quién va a pagar: documento, banco y, en Pago Móvil, teléfono. 201 con status: pending y pending_reason: awaiting_payment; expires_at dice hasta cuándo se espera (48 h por omisión; expires_in_hours la acorta, nunca más de 72).
  2. Muéstrele a su pagador la cuenta receptora: GET /v2/capabilities la trae en receiving_account de cada método incoming_* (banco, teléfono o cuenta, documento y titular).
  3. Su pagador paga desde su banco. Cuando llega un pago que coincide en todo (monto exacto, banco emisor y teléfono o documento del pagador, posterior a la creación del cobro y dentro de la ventana), el cobro pasa a confirmed con bank_reference y confirmed_at, su saldo sube por el neto y le llega payin.confirmed.
  4. Si la ventana vence sin pago: failed / expired y payin.failed. Mientras espera, puede cancelarlo con POST /v2/payins/{id}/cancel.

Un pago que llega sin un cobro que lo espere no se le atribuye a nadie: cree el cobro antes de pedirle a su pagador que pague. La comisión por un Pago Móvil recibido se descuenta del monto (el saldo sube por el neto); la transferencia recibida no tiene comisión.

  1. POST /v2/payins con method: debit_otp → 201 con status: pending y pending_reason: awaiting_code. El banco le manda el código al pagador. code_expires_at dice hasta cuándo vale.
  2. Su usuario teclea el código en su pantalla.
  3. POST /v2/payins/{id}/confirm con {"code": "…"} → el débito se ejecuta. confirmed trae bank_reference; failed trae failure.code.

El código tiene un solo intento. Uno equivocado falla la operación con failure.code: code_rejected: cree otro cobro, o pida otro código con POST /v2/payins/{id}/resend-code antes de confirmar (el anterior deja de servir; tope de cinco por operación). El código no se guarda nunca.

Si el resultado no llegó en el momento, la operación queda pending / processing: la API la resuelve sola y le avisa por webhook.

Lo que el pagador ve en su banco es concept, por ejemplo «Cobro TCP7K2M9Q»: lo pone el sistema con el prefijo de su empresa y seis caracteres del id de la operación. Su reference no viaja al banco.

POST/v2/payins

Alcancepayins:crear

Crea un cobro. Con method: debit_otp, el banco de su usuario le manda un código y la operación queda pending / awaiting_code hasta que usted la confirme.

Con method: incoming_mobile_payment o incoming_transfer usted declara el monto y quién va a pagar, y la operación queda pending / awaiting_payment hasta que ese pago llegue a la cuenta receptora (la que GET /v2/capabilities muestra en receiving_account) o venza la ventana (expires_at; expires_in_hours la acorta, hasta 72 h). Cuando llega un pago que coincide en monto, banco y pagador, la operación pasa a confirmed con bank_reference y le llega payin.confirmed; si vence, failed / expired. Mientras espera, puede cancelarla con POST /v2/payins/{id}/cancel.

Requiere el alcance payins:crear.

Los campos de payer son los que el método declara en GET /v2/capabilities; se validan contra esa misma declaración y un campo que el método no declara se rechaza (details[].code = unknown).

Reintentar este POST es seguro con la misma Idempotency-Key: devuelve el mismo cobro y no pide otro código.

Parámetro En Obligatorio Descripción
Idempotency-Key header sí Una clave única por pedido, de 8 a 128 caracteres. La misma clave devuelve la misma operación.
Código Significa
201 El cobro existe. status dice qué pasó: con awaiting_code su usuario tiene que confirmarlo; con awaiting_payment se espera el pago hasta expires_at.
400 El pedido no pasó la validación. details nombra cada campo. Códigos: amount_out_of_range, bank_unsupported, body_invalid, country_unsupported, currency_unsupported, field_invalid, field_required, idempotency_key_required, method_unavailable.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
409 La misma Idempotency-Key con otro cuerpo. Códigos: idempotency_key_reused.
503 No se pudo procesar ahora. Reintente más tarde con la misma Idempotency-Key. Códigos: temporarily_unavailable.
GET/v2/payins/{id}

Alcanceoperaciones:leer

El estado actual. Requiere el alcance operaciones:leer.

Parámetro En Obligatorio Descripción
id path sí El id de la operación.
Código Significa
200 El cobro.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
404 No hay una operación con ese identificador en su empresa. Códigos: not_found.
POST/v2/payins/{id}/cancel

Alcancepayins:crear

Sólo para un cobro recibido (incoming_mobile_payment / incoming_transfer) que todavía está pending / awaiting_payment. Queda failed con failure.code = cancelled y le llega payin.failed. Un pago que llegue después ya no se le atribuye.

Cualquier otro cobro, o uno que ya terminó, 409 not_cancellable.

Requiere el alcance payins:crear.

Parámetro En Obligatorio Descripción
id path sí El id de la operación.
Código Significa
200 El cobro, cancelado.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
404 No hay una operación con ese identificador en su empresa. Códigos: not_found.
409 El cobro no está esperando un pago. Códigos: not_cancellable.
POST/v2/payins/{id}/confirm

Alcancepayins:crear

Ejecuta el débito con el código que tecleó su usuario. El código no se guarda.

confirmed trae bank_reference. failed con code_rejected es un código equivocado o vencido: pida otro con POST /v2/payins/{id}/resend-code sobre una operación nueva, porque un cobro fallido es final.

Requiere el alcance payins:crear.

Parámetro En Obligatorio Descripción
id path sí El id de la operación.
Código Significa
200 El resultado del débito.
400 Falta el código o el cuerpo no se pudo leer. Códigos: body_invalid, field_required.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
404 No hay una operación con ese identificador en su empresa. Códigos: not_found.
409 La operación no está esperando un código. Códigos: code_not_expected.
503 No se pudo procesar ahora. Reintente más tarde con la misma Idempotency-Key. Códigos: temporarily_unavailable.
POST/v2/payins/{id}/resend-code

Alcancepayins:crear

Para cuando el mensaje no llegó. Tiene un tope por operación. Requiere el alcance payins:crear.

Parámetro En Obligatorio Descripción
id path sí El id de la operación.
Código Significa
200 La operación, con el nuevo vencimiento del código.
401 La llave falta o no es válida. Códigos: unauthorized.
403 La llave es válida pero no tiene el alcance. Códigos: scope_missing.
404 No hay una operación con ese identificador en su empresa. Códigos: not_found.
409 La operación no está esperando un código, o ya se pidieron demasiados. Códigos: code_not_expected, too_many_codes.
503 No se pudo procesar ahora. Reintente más tarde con la misma Idempotency-Key. Códigos: temporarily_unavailable.
Campo Tipo Obligatorio Descripción
amount string
^[0-9]+(\.[0-9]+)?$
sí Cadena decimal con punto y los decimales de la moneda. Nunca un número JSON.
country string
^[A-Za-z]{2}$
sí ISO 3166-1 alfa-2.
currency string
^[A-Za-z]{3}$
sí ISO 4217.
expires_in_hours integer no Sólo cobros recibidos: cuántas horas se espera el pago. Sin él, la ventana por omisión (48 h). Nunca más de 72.
mandate Mandato no
metadata object no Lo que usted quiera guardar, hasta 4 KB. Vuelve tal cual.
method CodigoDeMetodo sí
payer Pagador sí
reference string no Su referencia. Vuelve tal cual en la operación.
Campo Tipo Obligatorio Descripción
account_number string no
account_type mobile | account no
bank_code string no
document object no
email string no
name string no
phone string no

Qué campos exige cada método y cada banco lo dice GET /v2/capabilities (fields): mándelos tal cual, y sólo esos. En un cobro recibido no hay name: el pago se reconoce por documento, banco y teléfono.

Campo Tipo Obligatorio Descripción
contract_date string no
contract_id string
^[A-Za-z0-9]{1,30}$
no Alfanumérico, hasta 30: es lo que acepta el banco.
POST /v2/payins
curl -X POST https://api.tucapi.app/v2/payins \
-H "Authorization: Bearer $LLAVE" \
-H "Idempotency-Key: orden-4821" \
-H "Content-Type: application/json" \
-d '{
"amount": "1500.50",
"country": "VE",
"currency": "VES",
"metadata": {
"orden": "A-1"
},
"method": "debit_otp",
"payer": {
"account_number": "04121234567",
"account_type": "mobile",
"bank_code": "0102",
"document": {
"number": "12345678",
"type": "V"
},
"name": "Nombre Apellido"
},
"reference": "factura 123"
}'
Respuesta 201
{
"amount": "1500.50",
"bank_reference": null,
"code_expires_at": "2026-09-23T15:04:05Z",
"country": "VE",
"created_at": "2026-09-23T14:59:05Z",
"created_by": "company",
"currency": "VES",
"failure": null,
"id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f",
"metadata": {
"orden": "A-1"
},
"method": "debit_otp",
"pending_reason": "awaiting_code",
"reference": "factura 123",
"status": "pending",
"type": "payin",
"updated_at": "2026-09-23T14:59:05Z"
}
POST /v2/payins/{id}/confirm
curl -X POST https://api.tucapi.app/v2/payins/$ID/confirm \
-H "Authorization: Bearer $LLAVE" \
-H "Content-Type: application/json" \
-d '{
"code": "123456"
}'
Respuesta 200
{
"amount": "1500.50",
"bank_reference": "000123456789",
"confirmed_at": "2026-09-23T15:04:05Z",
"country": "VE",
"created_at": "2026-09-23T14:59:05Z",
"created_by": "company",
"currency": "VES",
"failure": null,
"id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f",
"metadata": {
"orden": "A-1"
},
"method": "debit_otp",
"reference": "factura 123",
"status": "confirmed",
"type": "payin",
"updated_at": "2026-09-23T14:59:05Z"
}
POST /v2/payins/{id}/cancel
curl -X POST https://api.tucapi.app/v2/payins/$ID/cancel \
-H "Authorization: Bearer $LLAVE"
Respuesta 200
{
"amount": "1500.50",
"bank_reference": null,
"country": "VE",
"created_at": "2026-09-23T14:59:05Z",
"created_by": "company",
"currency": "VES",
"expires_at": null,
"failure": {
"code": "cancelled",
"message": "La operación fue cancelada."
},
"id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f",
"metadata": {
"orden": "A-1"
},
"method": "incoming_mobile_payment",
"pending_reason": null,
"reference": "factura 123",
"status": "failed",
"type": "payin",
"updated_at": "2026-09-23T14:59:05Z"
}