Resumen
Tarea Huey (cola de tareas) que se ejecuta diariamente a las 06:40 para sincronizar el estado cacheado en Organization con el estado real de cada suscripción viva en Stripe. Cubre el riesgo “webhook perdido” del plan técnico: si un evento no llegó por timeout, este cron lo detecta y corrige.
Ubicación: billing/tasks.py:reconcile_stripe_subscriptions()
Horario: @db_periodic_task(crontab(hour=6, minute=40))
Función
@db_periodic_task(crontab(hour=6, minute=40))
def reconcile_stripe_subscriptions():
if not settings.STRIPE_SECRET_KEY:
return # No-op silencioso en dev/test
for org in Organization.objects.exclude(stripe_customer_id=""):
subs = stripe.Subscription.list(
customer=org.stripe_customer_id,
status="all",
limit=1
)
if subs.data:
if sync_subscription_to_org(org, subs.data[0]):
logger.warning(
"Stripe reconcile: org %s divergía → corregida a %s",
org.id, org.subscription_status
)
Flujo
- Check de configuración: Si
STRIPE_SECRET_KEYno existe (dev/test, PROD antes PR-4) → retorna silenciosamente (no-op) - Itera orgs: Para cada org con
stripe_customer_idno vacío - Lee Stripe: Obtiene la suscripción más reciente viva del customer (limit=1)
- Compara y sincroniza: Llama
sync_subscription_to_org()(el mismo helper queapply_event)- Si cambio detectado → loguea warning (⚠️ webhook perdido)
- Guarda en BD
- Termina: Sin rollback, todos los cambios se persisten
Detección de webhooks perdidos
Cada divergencia encontrada se loguea como warning con patrón "Stripe reconcile: org {id} divergía → corregida":
logger.warning(
"Stripe reconcile: la org %s divergía del estado real; corregida a %s (¿webhook perdido?)",
org.id,
org.subscription_status,
)
Esto es un indicador de riesgo. En post-análisis, el equipo de ops puede:
- Revisar el log de webhooks de Stripe (dashboard)
- Verificar si hubo timeouts de red
- Medir SLA del webhook (anotado en plan técnico como “riesgo 2.2”)
Sin-op en dev/test
En entornos sin STRIPE_SECRET_KEY:
- La tarea no falla (solo retorna)
- No se loguea nada
- Tests locales sin account de Stripe no salen afectados
En PROD pre-go-live (PR-4): mismo comportamiento, hasta que se configure la clave.
Tolerancia a errores
try:
subs = stripe.Subscription.list(customer=org.stripe_customer_id, status="all", limit=1)
except stripe.StripeError:
logger.exception("Stripe reconcile: no se pudo listar... org %s", org.id)
continue # Sigue con la siguiente org
Si un customer produce error de Stripe (ej. no existe, token revocado), el error se loguea pero no aborta la tarea. Las otras orgs se procesan normalmente.
Orgs sin stripe_customer_id
Se excluyen con .exclude(stripe_customer_id=""), así solo procesa orgs que alguna vez tuvieron un checkout exitoso.
Integración con webhook
| Mecanismo | Cubre |
|---|---|
Webhook (stripe_webhook()) | Cambios en tiempo real (99% de los casos) |
| Reconciliación diaria (6:40) | Webhooks perdidos por timeout/red (1%) |
El combo da garantía de consistencia eventual: en el peor caso, la org vuelve al estado real en <24h.
Configuración
Necesita STRIPE_SECRET_KEY (misma que para stripe.Webhook.construct_event en el webhook):
# config/settings/base.py
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY") or ""
Vacío en dev/test. En PROD, solo activo en PR-4.
Comportamiento en producción esperado
Tras go-live de PR-4:
- Ejecución normal: 06:40 UTC cada día
- Logs esperados: Casi nunca (webhooks raramente se pierden)
- Logs de warning: Indican un problema de conectividad Stripe ↔ CreaRack (escalable a ops)
Véase también
- [[entity—billing—service—event-applier]]
- [[entity—billing—endpoint—stripe-webhook]]
- [[entity—core—model—organization]]
- [[feature—billing—stripe-pr2-webhooks]]
- [[concept—saas—reliability]]