Skip to Content
ConceptosSesiones

Sesiones

Una CheckoutSession es con lo que interactúa el comprador: un objeto con TTL, de un solo uso, que posee la URL del checkout alojado. Es la más liviana de las tres entidades centrales; el trabajo pesado ocurre en Órdenes y los Payment Intents (ver abajo).

El modelo de datos de tres entidades

CheckoutSession ← 1:1 → Order ← 1:N → PaymentIntent (comprador) (catálogo) (cada intento de pago)
EntidadPropósitoVida útil
CheckoutSessionDe cara al comprador: tiene session_key, checkout_url, TTLMinutos (por defecto 30)
OrderEstado de tu catálogo: line items, totales, reembolsosRegistro permanente
PaymentIntentUn intento de pago en una cadena/activoHoras; liquida o expira

Creas una sesión y una orden juntas (vía POST /b2b/v1/checkout-sessions/quick). Cada vez que el comprador elige un activo en la página de checkout, se abre un PaymentIntent fresco contra la cadena correcta. Si cambian de activo a mitad de checkout, el intent anterior va a EXPIRED y empieza uno nuevo.

Identificador

Una sesión se identifica por su session_key:

cst_G-SO92J7HNWkwMHEHjD4oO1Z

URL-safe, ~24 caracteres tras el prefijo (18 bytes aleatorios codificados en base64url, sin padding). La página de checkout alojada es https://checkout.infraio.xyz/<session_key>: trata la session key como una credencial bearer para ese único checkout.

Ciclo de vida — CheckoutSession

EstadoSignifica
ACTIVESesión creada y abierta. La URL del checkout es usable.
COMPLETEDUn PaymentIntent de esta sesión liquidó. La Order ahora es PAID (o PARTIAL_PAID si pagaron de menos).
EXPIREDexpires_at pasó sin liquidación. El cleanup worker cambió el estado y canceló cualquier Order abierta.
CANCELEDCancelación explícita: o el comprador pulsó “cancelar” o tú llamaste al endpoint de cancelación.

Los tres estados terminales son mutuamente excluyentes y finales. Se puede crear una nueva CheckoutSession contra la misma Order si quieres reintentar (p. ej., tras un pago insuficiente).

TTL

  • Por defecto: 30 minutos (configurable vía el campo expires_in al crear, en segundos).
  • Límites: No hay min/max duro aplicado en el lado del servidor. Usa valores sensatos: menos de 60 segundos arriesga que compradores legítimos se queden sin tiempo; más de 7 días retiene capacidad en un token que casi con certeza está abandonado. Elige un número que coincida con la ventana de decisión esperada de tu comprador.
  • Por defecto por comerciante: Configurable vía dashboard, pero solo la ruta heredada POST /b2b/v1/checkout-sessions (dos pasos) lo honra. La ruta POST /b2b/v1/checkout-sessions/quick siempre cae a 30 minutos si se omite expires_in, independientemente del ajuste por comerciante. Si necesitas un valor por defecto distinto en la ruta quick, envía expires_in explícitamente en cada llamada.
  • Aplicación: Lazy al leer + un cleanup worker periódico. Una sesión cuyo expires_at ha pasado se trata como EXPIRED aunque el campo de estado aún no se haya escrito, así que no dependas de leer el estado vía API en el momento exacto de la expiración.

Pago insuficiente

Si un comprador envía menos que el importe de la sesión, el PaymentIntent aún liquida por el importe parcial y la Order transiciona a PARTIAL_PAID. La CheckoutSession se mueve a COMPLETED (un PaymentIntent liquidado), así que ya no es reutilizable.

Para aceptar el déficit como pago completo, la orden puede resolverse a PAID: ver Conceptos → Órdenes (no hay API pública de resolve hoy; para un flujo self-serve, cobra el remanente en su lugar). Para cobrar el remanente, crea una nueva CheckoutSession contra la misma Order con el importe residual.

Sobrepago

Si el comprador envía más que el importe de la sesión (raro pero ocurre con transferencias manuales), el escáner on-chain captura el sobrepago en 24 horas y emite una alerta al comerciante. No hay reembolso automático: emite uno manualmente vía la API de reembolso o el dashboard.

Pagos con activo equivocado

La dirección de depósito se genera por tupla (sesión, cadena, activo). Si un comprador envía el activo equivocado a la dirección, el matcher on-chain no lo reconoce y el PaymentIntent permanece abierto hasta la expiración del TTL. Podemos recuperar los fondos pero es un flujo de soporte, no automático: instruye a los compradores para que envíen el activo exacto mostrado en la página de checkout.

Idempotencia al crear

POST /b2b/v1/checkout-sessions/quick acepta un campo idempotency_key en el body de la petición (nota: campo del body, no cabecera HTTP). Auto-genera un UUID para él si tu cliente no tiene una clave natural: el SDK lo hace por defecto.

La clave deduplica la Order, no la CheckoutSession. En un reintento con la misma clave, /quick devuelve la orden original (order_id es estable) pero emite una CheckoutSession fresca: una nueva session_key y checkout_url cada vez. Eso es intencional: una Order puede respaldar varios intentos de checkout (ver Órdenes), así que un /quick reintentado entrega al comprador una sesión limpia sin duplicar la orden.

Dos comportamientos difieren de una capa de idempotencia al estilo Stripe: no te pillen desprevenido:

  • El body no se hashea ni compara. Reutilizar una clave con un body distinto no devuelve 409: el servidor devuelve silenciosamente la orden ya almacenada bajo esa clave e ignora el nuevo body. Así que trata un idempotency_key como un token de un solo uso para una sola orden lógica; nunca lo recicles entre distintos carritos.
  • Solo la Order se deduplica, no la sesión. Si necesitas la misma URL de checkout de vuelta, persiste session_key / checkout_url de la primera respuesta: llamar a /quick de nuevo no devolverá la antigua. Para enumerar cada sesión emitida contra una orden, usa GET /b2b/v1/checkout-sessions/by-order/:order_id.

Endpoints de la API

MétodoRutaNotas
POST/b2b/v1/checkout-sessions/quickCrea order + session en una llamada
POST/b2b/v1/checkout-sessionsCrea sesión contra una orden existente
GET/b2b/v1/checkout-sessions/by-order/:order_idLista todas las sesiones de una orden (para historial de reintentos)
GET/checkout/:session_keyPúblico: lo que pega el navegador del comprador

Consulta el Inicio rápido para el body completo de la petición de creación y la firma.

Qué sigue