Cart Checkout
Crea un carrito controlado por tu backend con Checkout Sessions y checkout alojado.
Usa Checkout Sessions cuando tu backend controla un carrito o intento de compra concreto. La sesión puede referenciar precios del catálogo con priceId o guardar precios inline con priceData sin crear productos, planes o precios.
Conserva
x-api-keyen tu backend. No llames/api/v1desde código del navegador ni aceptes precios, recurrencia o empresa como datos autorizados enviados por el cliente.
Convenciones de la API
Crea claves de API separadas con los permisos mínimos necesarios. Las consultas requieren READ; crear, actualizar, eliminar, vencer, cancelar y reembolsar requieren WRITE. Una clave válida sin el permiso de la operación devuelve 403; una clave ausente, inválida o vencida devuelve 401.
Lista Productos, Enlaces de pago y Checkout Sessions con limit (20 por defecto, 100 como máximo), startingAfter o endingBefore. Usa un solo cursor y toma el siguiente desde meta.lastId o el anterior desde meta.firstId. Todas las listas devuelven { data, meta }.
Todos los errores devuelven { error: { code, message, details? }, correlationId }. Registra el identificador de correlación junto con el identificador de tu solicitud. Reintenta sólo los errores seguros para la operación y reutiliza el mismo Idempotency-Key; consulta el recurso actualizado antes de reintentar un conflicto de versión. Revisa la Referencia de API para conocer estados y esquemas de cada operación.
Arquitectura
Navegador del comprador
→ tu backend
→ Pharos /api/v1/checkout-sessions
→ checkout alojado /checkout/session/:id
→ cálculo, factura, suscripción y pago
→ webhook firmado a tu backendTu backend arma el carrito y modifica la sesión. Pharos administra el checkout y los registros comerciales. El navegador sólo necesita la url devuelta.
Flujo de compra
- El navegador pide checkout a tu backend usando la identidad del carrito, no precios autorizados por el navegador.
- Tu backend crea una sesión con
POST /api/v1/checkout-sessionsy unIdempotency-Keyestable para esa operación. - Guarda
id,version,ETagyurlasociados al carrito. - Si necesitas modificarlo, hacelo mientras la sesión esté
openy sin preparar. Cada operación usa su propia clave y el últimoETagenIf-Match. - Devuelve la URL al navegador para abrir el checkout alojado.
- El comprador completa datos y medio de pago. Los cambios de cantidad respetan los límites guardados por el servidor.
- Pharos calcula el resultado y crea cliente, factura, suscripción y pago según corresponda.
- El pago puede ser inmediato, requerir acción o quedar pendiente.
- Las URLs de éxito y cancelación controlan navegación; no prueban un pago ni vencen la sesión.
- Reconcilia por
checkoutSession.idyclientReferenceIdy confirma la compra sólo conpurchase_succeededverificado y deduplicado.
Crear una sesión en el backend
const pharosApi = 'https://api.example.com/api/v1'
type Cart = {
id: string
items: Array<{ priceId: string; quantity: number }>
}
export async function createCheckout(cart: Cart) {
const response = await fetch(`${pharosApi}/checkout-sessions`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.PHAROS_API_KEY!,
'idempotency-key': `cart:${cart.id}:checkout-session`,
},
body: JSON.stringify({
mode: 'PAYMENT',
theme: 'light',
clientReferenceId: cart.id,
lineItems: cart.items.map((item) => ({
...item,
adjustableQuantity: {
enabled: true,
minimum: 1,
maximum: 10,
},
})),
successUrl: 'https://shop.example/success?session={CHECKOUT_SESSION_ID}',
cancelUrl: 'https://shop.example/cart',
}),
})
if (!response.ok) {
throw new Error(await response.text())
}
return {
session: await response.json(),
etag: response.headers.get('etag'),
}
}Deriva la clave de creación del carrito o intento. Repetir la misma operación con la misma clave devuelve la misma sesión lógica. Reutilizarla con un cuerpo distinto causa un conflicto de idempotencia.
Elegir catálogo o precios inline
Usa priceId cuando la oferta ya existe en el catálogo. Usa priceData para guardar un precio contextual definido por tu backend:
const customLineItem = {
priceData: {
currency: 'USD',
unitAmount: '25.00',
productData: {
name: 'Custom support package',
description: 'Configured for this order',
},
recurring: {
interval: 'MONTH',
intervalCount: 1,
trialDays: 7,
},
},
quantity: 1,
}Los datos inline quedan guardados en la sesión. El navegador del checkout no puede reemplazar importes unitarios ni recurrencia.
Modificar un carrito abierto
export async function addItem(sessionId: string, etag: string, priceId: string) {
const response = await fetch(`${pharosApi}/checkout-sessions/${sessionId}/line-items`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.PHAROS_API_KEY!,
'idempotency-key': crypto.randomUUID(),
'if-match': etag,
},
body: JSON.stringify({
lineItems: [{ priceId, quantity: 1 }],
}),
})
if (response.status === 409) {
throw new Error('Reload and reconcile the session before retrying')
}
if (!response.ok) {
throw new Error(await response.text())
}
return {
session: await response.json(),
etag: response.headers.get('etag'),
}
}Después de una modificación exitosa, actualiza la sesión, version y ETag guardados. Ante 409 SESSION_VERSION_CONFLICT, consulta la sesión, reconcilia el carrito y crea una modificación nueva. No repitas a ciegas datos de una versión vieja.
Dentro de /api/v1 también puedes usar:
PATCH /checkout-sessions/{id}para configuración no monetaria admitida, como tema, redirecciones, email, referencia o metadata.PATCH /checkout-sessions/{id}/line-items/{itemId}para cantidad o límites ajustables.DELETE /checkout-sessions/{id}/line-items/{itemId}para quitar un ítem permitido.POST /checkout-sessions/{id}/expirepara impedir otro uso del carrito.
Las modificaciones de API requieren una sesión abierta cuyo snapshot no esté bloqueado por la preparación del pago.
Redirigir desde el navegador
const response = await fetch('/api/cart/checkout', { method: 'POST' })
if (!response.ok) throw new Error('Checkout could not be created')
const { url } = await response.json()
window.location.assign(url)Tu handler /api/cart/checkout realiza la llamada autenticada a Pharos. No expongas la clave de API en la respuesta ni en código del cliente.
Estados y confirmación
El status puede ser open, complete o expired; describe el flujo del comprador. paymentStatus puede ser unpaid, processing, paid, failed o no_payment_required; describe el cobro.
La redirección de éxito y checkout_session_completed no son señales de entrega de la compra. Consulta la sesión para reconciliar sus registros y confirma sólo con purchase_succeeded firmado. Un flujo gratuito o de prueba requiere tu política de acceso y el contrato de eventos correspondiente.
Resolver fallos
- Ante fallos de red, repite con la misma clave sólo la operación exacta.
- Ante
400, corrige la petición o composición del carrito. - Ante
401o403, revisa clave, entorno y permisos. - Ante
404, verifica que clave y sesión correspondan a la misma empresa. - Ante
409, distingue conflicto de idempotencia y de versión y reconcilia antes de reintentar. - Si el snapshot está bloqueado, crea otro intento en lugar de modificarlo.
- Si el pago está en proceso, espera el webhook firmado.
Configuración comercial
Consulta Productos y precios, Cupones, Promociones y Descuentos. Las condiciones por producto requieren identidades coincidentes del catálogo; un nombre inline igual no garantiza elegibilidad. Usa sólo opciones admitidas por la API, sin suponer que cada ajuste del panel tiene un equivalente público.
Consulta Suscripciones y Facturas para interpretar los registros creados.