Billing · Stripe PR-2 — webhook receiver + estado de suscripción
Billing · Stripe PR-2 (v1.67.0, task #136)
Primera pieza de la plataforma de cobro. Decisión de plataforma (14-08, apuesta #12): Stripe como pasarela pura — Merchant of Record descartado; las facturas las emite SIEMPRE Holded (Verifactu 2027).
Qué hace
POST /api/billing/webhook/stripe(billing/api.py): endpoint público (auth=None) cuya seguridad es la verificación de la firma HMAC del headerStripe-Signature(stripe.Webhook.construct_event). Fail-closed: sinSTRIPE_WEBHOOK_SECRETresponde 503.- Idempotencia: tabla-ledger
StripeEvent(billing/models.py),event_idúnico. Reentrega de un evento ya digerido → 200 sin re-procesar; fallo de procesado → 500 (Stripe reintenta).StripeEventes tabla GLOBAL sin FK a Organization (mismo criterio queInvitationCode: fuera de RLS — los eventos llegan sin tenant). - Estado cacheado en
Organization(migración core 0032):stripe_customer_id,subscription_status(none/trialing/active/past_due/canceled),current_period_end. La verdad vive en Stripe. - Eventos mapeados (
billing/services.py):checkout.session.completed(liga customer porclient_reference_id),customer.subscription.created/updated(estado + fin de periodo, con fallback a los subscription items para la API Basil 2025-03+),customer.subscription.deleted(canceled),invoice.payment_failed(past_due). - Reconciliación diaria (
billing/tasks.py, Huey 06:40): re-sincroniza contra la suscripción viva y loguea divergencias (webhook perdido). No-op sinSTRIPE_SECRET_KEY.
Límites deliberados
- NO toca
Organization.is_activeniplan: suspender por impago es decisión de producto (PR-3+). - El eje de licencia (¿por rack, por equipo, por sitio?) está APARCADO hasta las llamadas MSP de septiembre (grill 15-08, acta en task #136; medido en PROD: 98% de lo monitorizado no cuelga de un rack). Todo lo de esta página es agnóstico al eje.
- Sin UI: checkout y Customer Portal = PR-3; claves live + Stripe Tax = PR-4.
Config
STRIPE_SECRET_KEY · STRIPE_PUBLISHABLE_KEY · STRIPE_WEBHOOK_SECRET (settings base, patrón getenv-or; vacíos hasta el go-live; el compose de PROD los declarará en PR-4 — footgun Dokploy ${VAR:-}).
Tests
tests/billing/test_stripe_webhook.py — 12 tests con eventos sintéticos firmados con el esquema HMAC real (firma inválida/ausente/vieja, 503 sin secret, los 5 eventos, idempotencia, customer fantasma, status desconocido).
Dependencia
stripe==15.5.0 (MIT, registrada en core/licenses.py). dj-stripe descartado (40 tablas para 3 flujos).
Véase también
- [[entity—core—model—organization]]
- [[entity—core—model—plan]]