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 cuenta 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.
IDs de recursos
Los IDs de Pharos son cadenas opacas con prefijo de recurso, como cs_…, price_…, cus_… o pi_…. Guárdalos y envíalos sin modificarlos; no interpretes su sufijo. Durante la ventana de compatibilidad, la API también acepta un CUID histórico devuelto antes de este formato.
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 método 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 coninvoice.paidverificado 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.
Asociar un cliente conocido
Envía customer con el ID de un Customer existente cuando tu aplicación ya conoce al comprador. Pharos valida que pertenezca a la misma cuenta y entorno y completa el checkout alojado con el perfil guardado. Si envías customer, omite customerEmail; usa customerEmail para un invitado cuyo perfil deba resolverse o crearse durante el checkout.
customerUpdate define qué campos guardados puede reemplazar el comprador durante el checkout. Los campos configurados como auto se guardan automáticamente en el Customer cuando el checkout se completa correctamente. Las tarjetas elegibles se guardan automáticamente después de que el comprador acepta los términos obligatorios del checkout. savedPaymentMethodOptions.redisplay: "enabled" controla si los métodos guardados aparecen en sesiones posteriores; la opción heredada save se acepta por compatibilidad, pero ya no cambia el comportamiento del checkout.
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
Para un cobro directo desde tu backend, creá un PaymentIntent con POST /api/v1/payment_intents. No cobra por defecto: enviá confirm: true o llamá a /api/v1/payment_intents/{id}/confirm para crear un Charge. Consultá un Charge por ID y creá un Refund separado con POST /api/v1/refunds.
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 invoice.paid 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 cuenta. - 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.
Continuar
Analítica de compras
Después de verificar invoice.paid, consulta la Checkout Session desde tu backend. customerDetails contiene los datos capturados del comprador; paymentDetails informa el pago exitoso y las cuotas efectivas. discountDetails.coupon mantiene el cupón separado de promotions. El value del cupón es su porcentaje o importe fijo configurado; discountAmount es el descuento monetario aplicado. Los importes usan unidades monetarias principales. Un discountDetails null indica que una sesión anterior no tiene copia guardada; { "coupon": null } confirma que no hubo cupón. No infieras crédito a partir de CARD, cuotas desde allowedInstallments ni cupón desde el descuento total. Las copias están disponibles para sesiones preparadas después de la migración y la publicación del servicio.