Sesiones de pago
Una sesión de pago representa una intención de cobro. La creas en tu servidor y obtienes un checkoutUrl para cobrar al comprador. Referencia pública: cs_…. Es de corta duración (actualmente ~5 minutos): créala justo antes de cobrar y usa expiresAt para el momento exacto de expiración.
Crear una sesión
POST /v1/sessionsAutenticación: clave secreta (Authorization: Bearer sk_…).
Cuerpo
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
amountMinor | entero | Sí | Monto en unidades menores (céntimos). Mínimo 1. |
currency | string | Sí | VES o USD. Hoy el cobro con tarjeta se liquida en VES. |
customerRef | string | No | Tu id de cliente. La tarjeta guardada quedará asociada a él. Máx. 120. |
cardToken | string | No | Token de una tarjeta ya guardada (tok_seif_…). El checkout pedirá solo el CVV. Ver Cobrar una tarjeta guardada. |
returnUrl | string (URL) | No | A dónde redirigir al comprador tras un cobro aprobado. |
description | string | No | Texto corto mostrado en el checkout. Máx. 140. |
maxFailures | entero | No | Cuántos intentos fallidos tolera la sesión antes de cerrarse sola. Omítelo para el comportamiento por defecto. Entre 0 y 10. Ver Limitar los intentos. |
Ejemplo
curl -X POST https://api.seif.pagosripei.com/v1/sessions \
-H "Authorization: Bearer sk_test_tu_clave" \
-H "Content-Type: application/json" \
-d '{
"amountMinor": 105000,
"currency": "VES",
"customerRef": "usuario_123",
"description": "Pedido #1042",
"returnUrl": "https://mitienda.com/gracias"
}'Respuesta 201
{
"id": "9f0c1f2a-...-e3",
"reference": "cs_8sKd2pQ1Aa",
"status": "created",
"amountMinor": 105000,
"currency": "VES",
"expiresAt": "2026-06-09T18:30:00.000Z",
"checkoutUrl": "https://checkout.seif.pagosripei.com/checkout?session=cs_8sKd2pQ1Aa",
"description": "Pedido #1042"
}| Campo | Descripción |
|---|---|
reference | Referencia pública de la sesión (cs_…). |
status | Estado de la sesión (ver abajo). |
checkoutUrl | URL para abrir el checkout incrustado. |
expiresAt | Cuándo expira (ISO-8601). |
Cobrar una tarjeta guardada (solo CVV)
Cuando tu cliente ya tiene una tarjeta guardada, no vuelvas a pedirle el número. Crea la sesión con el cardToken de esa tarjeta y el checkout mostrará únicamente:
- la tarjeta con la que va a pagar (marca y últimos 4 dígitos),
- el campo CVV,
- su documento de identidad.
El número y el vencimiento salen de la bóveda del lado de Seif. Tu servidor nunca toca datos de tarjeta, así que el flujo es 100 % PCI para ti.
curl -X POST https://api.seif.pagosripei.com/v1/sessions \
-H "Authorization: Bearer sk_test_tu_clave" \
-H "Content-Type: application/json" \
-d '{
"amountMinor": 105000,
"currency": "VES",
"cardToken": "tok_seif_Xa92...kQ",
"description": "Pedido #1043"
}'La respuesta es idéntica a la de cualquier sesión: abre el checkoutUrl (o móntalo con seif-js) y el comprador solo confirma su CVV.
Si no envías customerRef, la sesión hereda el de la tarjeta. Y si tu pasarela
pide OTP, el flujo del checkout es el mismo que en un pago normal.
Errores propios de este flujo
| HTTP | Causa | Solución |
|---|---|---|
400 | cardToken no empieza por tok_seif_. | Usa el seifToken tal cual lo devuelve GET /v1/cards. |
404 | La tarjeta no existe en tu comercio, o fue eliminada. | Vuelve a listar las tarjetas del cliente y usa una activa. |
409 | La tarjeta no admite este flujo (por ejemplo, quedó guardada en una pasarela que no pide CVV). | Escríbenos a soporte: esa tarjeta se cobra con un flujo servidor a servidor. |
El cardToken se valida al crear la sesión, no al pagar: un token inválido
falla en tu servidor, nunca delante del comprador.
Consultar una sesión
GET /v1/sessions/{reference}curl https://api.seif.pagosripei.com/v1/sessions/cs_8sKd2pQ1Aa \
-H "Authorization: Bearer sk_test_tu_clave"Devuelve el mismo objeto, con el status actualizado.
Estados de una sesión
| Estado | Significado |
|---|---|
created | Lista para cobrar. |
processing | El comprador inició el pago. |
completed | Pagada con éxito. |
failed | El cobro falló o fue rechazado. |
expired | Se alcanzó expiresAt sin pagarse. |
canceled | Cancelada. |
failed significa que un cobro falló pero la sesión sigue viva y se puede
reintentar. Una sesión cerrada —por tiempo o por agotar sus intentos— siempre
se reporta como expired.
Limitar los intentos
Por defecto una sesión se puede reintentar libremente hasta que expire. Si un cobro es rechazado, el comprador puede corregir los datos o probar otra tarjeta. Eso funciona bien para la mayoría, pero no para todos: hay integraciones que necesitan un número fijo de intentos y una respuesta definitiva, para que su propio sistema deje de esperar.
Para eso está maxFailures.
{
"amountMinor": 105000,
"currency": "VES",
"maxFailures": 0
}Qué significa el número
maxFailures es cuántos fallos tolera la sesión, no cuántos intentos
concede. El intento que rompe el límite también cuenta, así que el total de
intentos es maxFailures + 1.
| Valor | Intentos totales | Comportamiento |
|---|---|---|
| (omitido) | Ilimitados | Reintentos libres hasta expiresAt. Es el comportamiento por defecto. |
0 | 1 | Al primer fallo la sesión se cierra. No espera a que expire. |
1 | 2 | Tolera un fallo; el segundo la cierra. |
2 | 3 | Tolera dos fallos; el tercero la cierra. |
0 no es lo mismo que omitir el campo. Omitirlo significa “sin límite”;
0 significa “cierra al primer fallo”. Si tu cliente HTTP descarta los ceros
al serializar, revísalo: la diferencia entre los dos es total.
Qué cuenta como fallo
Cualquier intento que termine en error. No distinguimos entre un rechazo del banco y un dato mal escrito: si el cobro no salió, gastó un intento.
- Rechazado por el banco emisor — cuenta.
- Fallido en el procesador — cuenta.
- Datos inválidos (
400: falta el documento, falta el nombre del titular) — cuenta.
No consumen intentos las peticiones que nunca llegaron a ser un intento:
- Una sesión ya cerrada, expirada o con un cobro en curso (
409/410). - Los OTP incorrectos, que tienen su propio límite independiente.
Cómo se cierra
Una sesión ahora puede terminar de dos maneras: por tiempo (expiresAt) o
por fallos (maxFailures). La que ocurra primero.
Se cierre como se cierre, el resultado es el mismo:
- Su estado pasa a
expiredyattemptsRemainingqueda en0. - El checkout muestra la pantalla de sesión expirada, sin botón de reintento.
- Un nuevo intento de cobro devuelve
409. - Se emite el webhook
session.expired, con el mismo formato en ambos casos. El camporeasondice de qué manera murió.
{
"event": "session.expired",
"data": {
"sessionReference": "cs_8sKd2pQ1Aa",
"reason": "max_failures_reached",
"failureCount": 1,
"maxFailures": 0
}
}reason | Significado |
|---|---|
expired | Se alcanzó expiresAt sin completarse. |
max_failures_reached | Se agotaron los intentos permitidos. |
Es un solo evento con un solo formato: si hoy manejas session.expired, no
tienes que cambiar nada para enterarte también de los cierres por fallos.
reason está ahí solo si quieres distinguirlos.
En el checkout
Al comprador se le avisa cuando le queda un solo intento, para que no lo gaste reintentando la misma tarjeta. Antes de eso no se le dice nada: mostrar un contador desde el principio agrega presión sin cambiar nada.
Cerrar y volver a cobrar
Una sesión cerrada no se reabre. Si quieres darle otra oportunidad al
comprador, crea una sesión nueva. Es deliberado: el sentido de maxFailures es
que la decisión de reintentar sea tuya y explícita, no del comprador.
Errores
| HTTP | Causa | Solución |
|---|---|---|
400 | Falta amountMinor/currency, o amountMinor < 1, o returnUrl no es una URL válida. | Revisa el cuerpo según la tabla de campos. |
401 | Clave secreta inválida o ausente. | Verifica el header Authorization. |
404 | La sesión consultada no existe. | Revisa la reference. |
409 | El comercio aún no está aprovisionado. | Contacta a soporte para activar tu comercio. |
La sesión es de un solo uso. Una vez completed (o expired), genera una nueva
para un nuevo cobro.