CreaRack-SL

Billing · Stripe PR-2 — webhook receiver + estado de suscripción

Funcionalidadactivecreado Sat Aug 15#billing#stripe#webhooks#subscriptions

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 header Stripe-Signature (stripe.Webhook.construct_event). Fail-closed: sin STRIPE_WEBHOOK_SECRET responde 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). StripeEvent es tabla GLOBAL sin FK a Organization (mismo criterio que InvitationCode: 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 por client_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 sin STRIPE_SECRET_KEY.

Límites deliberados

  • NO toca Organization.is_active ni plan: 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]]