Saltar al contenido
Studeia Docs
AI-assisted translation — last updated 2026-05-24. For original (pt-BR or en-US), use the language switcher.

Integración Stripe (checkout + webhooks + portal de billing)

Stripe es el proveedor de pagos B2B de Studeia: checkout self-service, webhooks idempotentes para ciclo de vida de suscripciones, portal self-manage y dual currency BRL + USD

2026-05-24 6 min
Resposta curta

Stripe es el proveedor de pagos B2B internacional de Studeia. Checkout self-service mediante Stripe Checkout Session, webhooks idempotentes (HMAC SHA-256 + ordering guard mediante lastEventAt) para el ciclo de vida de suscripción, portal Stripe self-manage para que el admin cambie de plan/tarjeta, moneda dual BRL + USD detectada por geolocalización. Para PIX/boleto en Brasil: Asaas (integración paralela).

Configuración

1. Stripe Dashboard

  1. https://dashboard.stripe.com > Developers > API Keys > copia la Secret Key
  2. Webhooks > Add endpoint > URL: https://[tenant].studeia.com/api/webhooks/stripe
  3. Events to send:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  4. Copia el Webhook signing secret

2. Crear productos + prices

Para cada plan B2B de Studeia (mini, growth, pro_100):

  1. Products > Add product > nombre (ej: "Studeia Mini")
  2. Pricing: Recurring monthly + BRL R$ 250.00
  3. Copia el price_id (ej: price_1TZk...)
  4. Repite para USD si deseas soportar clientes internacionales

3. Env vars

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_MINI=price_...
STRIPE_PRICE_GROWTH=price_...
STRIPE_PRICE_PRO_100=price_...
STRIPE_PRICE_MINI_USD=price_...
STRIPE_PRICE_GROWTH_USD=price_...
STRIPE_PRICE_PRO_100_USD=price_...

Flujo de checkout

El admin hace clic en "Contratar Mini" en /institution/billing
  ↓
POST /api/institution/billing/checkout
  Body: { planSlug: "mini" }
  ↓
Studeia createCheckoutSession() (lib/billing/create-checkout.ts):
  1. resolveCustomerIdForProvider() — obtiene/crea stripeCustomerId
  2. Detecta moneda mediante getCurrencyFromHeaders() (server-side, anti-fraude)
  3. Stripe Checkout Session con line_items=[price_id correcto]
  4. Retorna { url } del Stripe Checkout
  ↓
Frontend redirige: window.location.href = data.url
  ↓
El admin paga en Stripe Checkout
  ↓
Stripe envía webhook checkout.session.completed
  ↓
Studeia applyWebhookEvent():
  1. Valida la firma HMAC
  2. stripe.subscriptions.retrieve(subscriptionId) — siempre re-fetch (no confía en metadata)
  3. Valida price_id contra allowlist
  4. Cross-check tenantId entre session.metadata y subscription.metadata
  5. Crea/actualiza TenantSubscription con currentPeriodEnd REAL
  6. Promueve Tenant.plan
  7. Crea PaymentLog (idempotente mediante [provider, externalEventId])

Hardening (reglas 129-139)

  • Webhook retorna 5xx en caso de fallo: Stripe reintenta durante 3 días
  • Ordering guard: lastEventAt + lastEventId en TenantSubscription. Los eventos fuera de orden se omiten con warning + PaymentLog
  • Idempotencia: unique [provider, externalEventId] en PaymentLog
  • Schema Zod: z.enum(PAID_PLAN_SLUGS) en el body del checkout (anti-typo)
  • PII redact: PaymentLog.rawPayload pasa por redactPaymentPayload() (elimina email/name/address/billing_details/cpfCnpj/card.last4 antes de persistir)
  • Past_due gate: isAccessBlocked(status) en layouts (reemplaza status === "suspended"). Cron /api/cron/billing-grace-expire hace la transición tras el período de gracia de 7 días

Portal Stripe self-manage

POST /api/institution/billing/portal retorna una URL temporal del portal de Stripe:

  • El admin cambia de plan (upgrade/downgrade)
  • Actualiza la tarjeta
  • Ve facturas
  • Cancela la suscripción
  • Sin pasar por el soporte de Studeia

Multi-currency

AspectoBRLUSD
DetecciónPor defectoHeader x-vercel-ip-country / cf-ipcountry
Price IDsSTRIPE_PRICE_MINI, _GROWTH, _PRO_100STRIPE_PRICE_MINI_USD, _GROWTH_USD, _PRO_100_USD
Asaas fallbackSí (PIX/boleto)NO (Asaas es solo para Brasil)
WebhookMismo endpointMismo endpoint

Ver también

FAQ

¿Stripe es cómo Studeia recibe el pago de la mensualidad?

Sí, para B2B internacional/tarjeta. El plan se selecciona en /institution/billing > Studeia llama a POST /api/institution/billing/checkout que crea una Stripe Checkout Session > el admin paga > el webhook subscription.activated promueve tenant.plan automáticamente. Para PIX/boleto en Brasil: usa Asaas (integración paralela).

¿Los webhooks de Stripe son confiables?

Sí, pero Studeia los trata de forma defensiva. (1) Verifica HMAC SHA-256 mediante el header stripe-signature con STRIPE_WEBHOOK_SECRET. (2) Stripe reintenta durante 3 días con backoff en caso de 5xx. (3) Idempotencia mediante unique [provider, externalEventId] en PaymentLog. (4) Ordering guard mediante lastEventAt/lastEventId — los eventos fuera de orden se omiten con warning. (5) checkout.session.completed siempre hace stripe.subscriptions.retrieve() para validar (no confía en metadata).

¿Studeia soporta la moneda USD para clientes fuera de Brasil?

Sí. STRIPE_PRICE_IDS (BRL) + STRIPE_PRICE_IDS_USD (USD) configurables mediante env vars. Detecta la moneda server-side mediante el header x-vercel-ip-country / cf-ipcountry. Los clientes fuera de Brasil ven el precio en USD automáticamente.

¿Puedo usar el Stripe Customer Portal para self-service?

Sí. POST /api/institution/billing/portal retorna la URL del portal de Stripe — el admin cambia de plan, actualiza la tarjeta, ve facturas, cancela. Sin necesidad de pasar por soporte.

Veja tambem

Integración Stripe (checkout + webhooks + portal de billing)