Objetivo
Trasladar una selección de plan a través de la frontera entre la landing (visitante, aún sin sesión) y el dashboard (usuario autenticado a punto de ser redirigido a Stripe Checkout). El endpoint firma con HMAC-SHA256 un payload (plan_slug, currency, billing_interval, exp) y lo guarda en la cookie app_checkout_intent. Tras verificar su teléfono via OTP, el RegistrationController lee la cookie, valida firma + TTL y crea la Checkout Session automáticamente.
Este es el único endpoint /v1/users/me/* que no requiere autenticación — el visitante aún no se ha registrado al momento de elegir un plan.
Prerrequisitos
- Un slug de plan obtenido de
GET /v1/plans/public(por ejemplopro,starter). - Un origen de sitio autorizado a fijar cookies first-party bajo
.example.com(o el dominio raíz donde sirvas tu landing).
Pasos
1. Firmar la intención
curl -X POST 'https://api.example.com/v1/users/me/subscription/checkout-intent' \
-H 'Content-Type: application/json' \
-c cookies.txt \
-d '{
"plan_slug": "pro",
"currency": "MXN",
"billing_interval": "month"
}'La respuesta fija la cookie app_checkout_intent:
Set-Cookie: app_checkout_intent=<payload>.<hmac>;
Domain=.example.com;
Max-Age=1800;
HttpOnly; SameSite=Lax; SecureCuerpo (200):
{
"data": {
"type": "checkout_intent",
"attributes": {
"plan_slug": "pro",
"currency": "MXN",
"billing_interval": "month",
"expires_at": "2026-05-28T11:30:00Z"
}
}
}2. Continuar al flujo de registro
Tras firmar la intención, redirige al visitante a /register. Cuando verifica su teléfono via OTP, el paso post-OTP lee la cookie, valida HMAC + TTL e inicia la Checkout Session de Stripe sin ningún round-trip adicional.
La cookie se borra al consumirse — si el usuario re-entra al flujo, pídele que elija el plan otra vez.
Integración con Stripe.js
Típicamente no llamas confirmCardPayment en este paso. Este endpoint solo firma la intención; la Checkout Session real se crea server-side en el handler de verify-OTP del registro, que después redirige a session.url. El único trabajo de la landing es llamar a este endpoint cuando el visitante clickee "Elegir Pro / Elegir Starter" y dejar que la cadena de redirects haga el resto.
Errores
| Estado | Código | Causa |
|---|---|---|
| 422 | billing_plan_slug_invalid | Falta plan_slug o no cumple [a-z0-9_-]{1,50}. |
| 422 | billing_intent_invalid_shape | currency o billing_interval fuera del enum (MXN/USD, month/year). |
| 429 | rate_limited | Demasiadas firmas desde la misma IP. Reintenta tras el header Retry-After. |
Notas
- El endpoint no valida que el plan exista en la base de datos. Un slug fantasma se firma normalmente; el flujo post-OTP falla en silencio (sin redirect a Stripe) y el usuario queda en el dashboard. Si necesitas mejor UX, valida slugs contra
GET /v1/plans/publicclient-side antes de hacer el POST. - El TTL es 30 minutos (
exp = now + 1800). Replay fuera de esa ventana devuelve400 intent_expireden el paso post-OTP. - En producción la cookie lleva
Secure. En dev local no, así que probar contrahttp://localhostfunciona sin TLS. - Ver ADR-0024 (Stripe Billing Architecture) para el flujo Stripe completo y ADR-0090 (URL Convention) para el razonamiento detrás del path
/v1/users/me/subscription/checkout-intent.
Nuevo en v1.49.6 — ver ADR-0090.