This commit is contained in:
2026-10-23 12:19:40 -04:00
parent 6cf0f0525f
commit faf6c5f2b3
19 changed files with 785 additions and 12 deletions
+12 -4
View File
@@ -1,4 +1,4 @@
"""Canonical Stripe reads, not event arrival order, determine entitlements."""
"""Canonical provider reads, not callback arrival order, determine entitlements."""
import logging
from datetime import datetime, timezone as dt_timezone
@@ -10,7 +10,8 @@ from django.db import transaction
from django.utils import timezone
from .directory import reconcile_groups
from .models import Account, ManagedGroup, Subscription, WebhookEvent
from .models import Account, CryptoInvoice, ManagedGroup, Subscription, WebhookEvent
from . import shkeeper
logger = logging.getLogger(__name__)
@@ -93,6 +94,10 @@ def apply_groups(account):
tier = settings.MEMBERSHIP_TIERS.get(sub.tier)
if tier:
desired.add(tier["group_dn"])
for invoice in CryptoInvoice.objects.filter(account=account, status__in=["PAID", "OVERPAID"], access_start__lte=now, access_end__gt=now):
tier = settings.MEMBERSHIP_TIERS.get(invoice.tier)
if tier:
desired.add(tier["group_dn"])
managed = set(ManagedGroup.objects.filter(account=account).values_list("dn", flat=True))
account.ldap_dn = reconcile_groups(account, desired, managed) if managed else account.ldap_dn
account.last_ldap_sync = now
@@ -101,10 +106,12 @@ def apply_groups(account):
def sync_account(account_id, refresh_stripe=True):
"""Serialize Stripe/LDAP writes per account across web and worker processes.
"""Serialize Stripe/SHKeeper/LDAP writes across web and worker processes.
Local expiry runs even when Stripe is unreachable. LDAP failure is retried on
the next pass, with persisted managed DNs allowing crash-safe revocation.
The existing refresh_stripe=False option means a local-only sweep: neither
provider is contacted, so expiry remains independent of remote availability.
"""
# Commit management intent independently before external side effects. If
# LDAP succeeds but a later DB commit fails, the next sweep still knows
@@ -125,9 +132,10 @@ def sync_account(account_id, refresh_stripe=True):
except Exception as exc:
billing_ok = False
logger.error("Billing refresh failed account=%s kind=%s", account.pk, type(exc).__name__)
crypto_ok = shkeeper.refresh_account(account)
now = timezone.now()
count = apply_groups(account)
if billing_ok:
WebhookEvent.objects.filter(account=account, processed_at__isnull=True).update(processed_at=now)
logger.info("Membership reconciled account=%s groups=%s billing_ok=%s", account.pk, count, billing_ok)
return billing_ok
return billing_ok and crypto_ok