Solicitud
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
failedSolo 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.
Crear un Lote de Solicitudes
curl -X POST https://devsim.mispidi.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 nacependingcon supayment_url.due_date_session/due_date_reached_behavior— hasta cuándo vale el enlace, y qué pasa al llegar la fecha:
due_date_reached_behavior | Qué pasa al pasar la fecha |
|---|---|
expire | El enlace deja de funcionar |
keep_active | El enlace sigue activo y se muestra el mensaje de aviso |
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.
Son dos cosas distintas y conviene no mezclarlas:
- Para quien abre el enlace, la fecha basta. Pasada la fecha con
expire, la página muestra «Esta sesión ya no está disponible» y no deja pagar. Nadie tiene que hacer nada para que eso ocurra. (Comprobado en el sandbox el 9-sep-2026.) - Para tu sistema, no. Si das por válido un enlace sin consultar
GET /api/v1/ext/payment-sessions/status/<session_id>, vas a seguir tratándolo como si aún admitiera un pago — y va a parecer quedue_date_sessionno funciona, cuando lo que falta es la consulta.
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.
En el sandbox de SPIDI, la fecha tiene que estar en el futuro. Una due_date_session ya pasada se rechaza al crear la sesión, con 400 y "Due date must be in the future". Así que la receta que funciona es: pon el vencimiento a un minuto de ahora, espera a que pase, y consulta.
# due_date_session: dentro de ~60 s · due_date_reached_behavior: "expire"
curl https://sandbox.api.spidipagos.com/api/v1/ext/payment-sessions/status/<session_id> \
-H "Authorization: Bearer <token>"
# antes de la fecha -> { "data": { "status": "pending" } }
# después -> { "data": { "status": "expired" } } y user_message: "La sesión expiró…"
Con keep_active seguirá pending pasada la fecha, que es justo la diferencia.
En el simulador sí puedes usar una fecha en el pasado y ver el expired en la misma consulta, sin esperar. Es una diferencia deliberada para que probar no cueste un minuto de reloj, y está declarada en /fidelidad.
Y fíjate en lo que no pasa: hasta que no consultas, tu sistema no sabe nada. Esa es la lección, no un detalle del entorno.
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 acreditadopaid 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://devsim.mispidi.com/control/sessions/<session_id>/outcome \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"leg":1,"outcome":"paid"}'
Este flujo (crear lote → pending → paid) está verificado contra el simulador.