Saltar al contenido principal

Solicitud de pago

La Solicitud es la sesión para cuando el pago tiene que esperar: el enlace sigue vivo hasta una fecha que pones tú, así que puedes mandarlo por WhatsApp, por correo o como un QR y que lo paguen mañana. Se crean por lote, porque cuando mandas facturas normalmente no mandas una sola. → Las formas de recibir un pago

Una Solicitud no se marca failed

Solo tiene dos finales: o se paga (paid) o vence (expired). Si tu código reacciona a failed, esa rama nunca se ejecuta aquí. → Ciclo de vida.

Solicitud de pago: cuando el pago tiene que esperarGuion: .docx · .yaml

Crear un lote de solicitudes

curl -X POST https://sim-productos.abiertolab.com/api/v1/ext/payment-sessions/request/batch \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{
"continue_on_error": true,
"items": [{
"title": "Pago de Rafael",
"currency_reference": "USD",
"amount_reference": 100,
"agreement_id": "<agreement_id>",
"identifier_label": "Nombre Cliente",
"identifier": "Rafael",
"description": "Pago de servicio",
"due_date_session": "2026-12-31T23:59:59Z",
"due_date_reached_behavior": "keep_active",
"late_notice_message": "Tu enlace de pago está por vencer",
"internal_reference": "REF-0001",
"success_url": "https://miapp.com/pago-exitoso",
"failure_url": "https://miapp.com/pago-fallido",
"webhook_url": "https://miapp.com/webhooks/spidi"
}]
}'
  • continue_on_error — si un ítem del lote falla, sigue con los demás.
  • items — una entrada por solicitud; cada una nace pending con su payment_url.
  • due_date_session / due_date_reached_behavior — hasta cuándo vale el enlace, y qué pasa al llegar la fecha:
due_date_reached_behaviorQué pasa al pasar la fecha
expireEl enlace deja de funcionar
keep_activeEl enlace sigue activo y se muestra el mensaje de aviso
Manda siempre el comportamiento, no te apoyes en el defecto

Si envías due_date_session sin due_date_reached_behavior, estás dejando en manos del defecto algo que tiene dos desenlaces opuestos: un enlace que muere solo o uno que sigue admitiendo pagos pasada la fecha.

Escríbelo explícito en cada sesión. Son cuatro caracteres de más en el cuerpo y te ahorran la clase de sorpresa que solo aparece cuando ya hay dinero de por medio.

El vencimiento se aplica al consultar el estado

La fecha no mata el enlace sola: lo mata la consulta. Si tu sistema da por válido un enlace sin consultar antes GET /api/v1/ext/payment-sessions/status/<session_id>, va a aceptar uno vencido — y va a parecer que due_date_session no funciona.

Consulta el estado antes de dar por bueno un enlace. Es el mismo hábito que te salva en Success URL, aquí por un segundo motivo.

Pruébalo en el simulador

El sandbox lo reproduce: crea una sesión con due_date_session en el pasado y due_date_reached_behavior: "expire", consulta su estado, y la verás en expired. Con keep_active seguirá pending, que es justo la diferencia.

curl https://sim-productos.abiertolab.com/api/v1/ext/payment-sessions/status/<session_id> -H "Authorization: Bearer <token>" # -> { "data": { "status": "expired" } }

Y fíjate en lo que no pasa: hasta que no consultas, la sesión sigue pending. Esa es la lección, no un detalle del sandbox.

  • late_notice_message — texto del aviso cuando el enlace está por vencer (obligatorio).
  • internal_reference — tu referencia interna de la solicitud (obligatorio).

La respuesta resume el lote: data.processed_count, data.successful_count y data.failed_count, un arreglo data.items[] (una sesión creada por ítem, cada una con su session_id y su payment_url) y data.errors[] con los ítems que no se pudieron crear.

Confirmar el pago

Igual que el Botón: confirma con GET status y/o el webhook payment_session.paid. → Success URL: verificar status · Manejar notificaciones.

paid no es el dinero acreditado

paid confirma el débito (fase 1) — no que el dinero ya está en manos del receptor. La acreditación (fase 2) no cambia el status: te llega por el webhook payment_session.accredited, o la consultas en data.session_payment.receiver_credits, en la misma respuesta del status. → Transacción a dos fases.

Probar en el simulador

# fuerza el pago de una sesión del lote (su session_id sale en data.items[].session_id)
curl -X POST https://sim-productos.abiertolab.com/control/sessions/<session_id>/outcome \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"leg":1,"outcome":"paid"}'

Este flujo (crear lote → pendingpaid) está verificado contra el simulador (simulador/test/func/guides.func.test.ts).

Usar el simulador