This commit is contained in:
2026-05-23 21:31:34 +02:00
parent 51b5ddc6f2
commit 641bb10479
6155 changed files with 3775717 additions and 291 deletions

View File

@@ -120,6 +120,12 @@ def _ensure_storage() -> None:
con.execute("ALTER TABLE licenses ADD COLUMN practice_id TEXT")
if "current_period_start" not in cols:
con.execute("ALTER TABLE licenses ADD COLUMN current_period_start INTEGER")
try:
from aza_ai_credit import ensure_ai_credit_schema
ensure_ai_credit_schema(con)
except Exception:
pass
con.commit()
@@ -898,113 +904,167 @@ async def stripe_webhook(
try:
if etype == "checkout.session.completed":
subscription_id = obj.get("subscription")
customer_id = obj.get("customer")
client_reference_id = obj.get("client_reference_id")
checkout_md = obj.get("metadata") or {}
checkout_purpose = (checkout_md.get("aza_purpose") or "").strip()
if checkout_purpose in ("ai_topup", "ai_topup_setup"):
from aza_ai_credit import (
ensure_ai_credit_schema,
process_setup_checkout_completed,
process_topup_checkout_completed,
)
_log_event("checkout_entered", {
"event_id": event_id,
"subscription_id": subscription_id,
"customer_id": customer_id,
})
if not subscription_id or not customer_id:
_log_event("license_skip", {
with sqlite3.connect(DB_PATH) as con:
ensure_ai_credit_schema(con)
if checkout_purpose == "ai_topup":
ai_result = process_topup_checkout_completed(con, session=obj)
else:
ai_result = process_setup_checkout_completed(con, session=obj)
_log_event("ai_credit_checkout", {
"event_id": event_id,
"purpose": checkout_purpose,
"result": ai_result,
})
else:
subscription_id = obj.get("subscription")
customer_id = obj.get("customer")
client_reference_id = obj.get("client_reference_id")
_log_event("checkout_entered", {
"event_id": event_id,
"reason": "missing_subscription_or_customer",
"subscription_id": subscription_id,
"customer_id": customer_id,
})
else:
# customer_email: cascade through all known locations.
customer_email = (
obj.get("customer_email")
or (obj.get("customer_details") or {}).get("email")
)
if not customer_email and customer_id:
try:
cust = _stripe_to_dict(stripe.Customer.retrieve(customer_id))
customer_email = cust.get("email")
except Exception:
pass
customer_email = (customer_email or "").strip() or None
sub = _stripe_to_dict(stripe.Subscription.retrieve(subscription_id, expand=["items.data.price"]))
status = sub.get("status", "") or ""
current_period_end = sub.get("current_period_end")
if not current_period_end:
try:
current_period_end = sub["items"]["data"][0]["current_period_end"]
except Exception:
current_period_end = None
current_period_start = sub.get("current_period_start")
if not current_period_start:
try:
current_period_start = sub["items"]["data"][0]["current_period_start"]
except Exception:
current_period_start = None
md = sub.get("metadata") or {}
lookup_key = (md.get("lookup_key") or "").strip()
allowed_users = md.get("allowed_users")
devices_per_user = md.get("devices_per_user")
if not lookup_key:
try:
price = sub["items"]["data"][0]["price"]
lookup_key = _lookup_key_from_price(price)
except Exception:
lookup_key = ""
def _to_int(x: Any) -> Optional[int]:
try:
return int(x)
except Exception:
return None
if lookup_key and not allowed_users:
policy = _policy_for_lookup_key(lookup_key)
allowed_users = allowed_users or str(policy.allowed_users)
devices_per_user = devices_per_user or str(policy.devices_per_user)
if not lookup_key:
if not subscription_id or not customer_id:
_log_event("license_skip", {
"event_id": event_id,
"reason": "missing_lookup_key",
"reason": "missing_subscription_or_customer",
"subscription_id": subscription_id,
"customer_id": customer_id,
})
else:
# customer_email: cascade through all known locations.
customer_email = (
obj.get("customer_email")
or (obj.get("customer_details") or {}).get("email")
)
if not customer_email and customer_id:
try:
cust = _stripe_to_dict(stripe.Customer.retrieve(customer_id))
customer_email = cust.get("email")
except Exception:
pass
customer_email = (customer_email or "").strip() or None
sub = _stripe_to_dict(stripe.Subscription.retrieve(subscription_id, expand=["items.data.price"]))
status = sub.get("status", "") or ""
current_period_end = sub.get("current_period_end")
if not current_period_end:
try:
current_period_end = sub["items"]["data"][0]["current_period_end"]
except Exception:
current_period_end = None
current_period_start = sub.get("current_period_start")
if not current_period_start:
try:
current_period_start = sub["items"]["data"][0]["current_period_start"]
except Exception:
current_period_start = None
md = sub.get("metadata") or {}
lookup_key = (md.get("lookup_key") or "").strip()
allowed_users = md.get("allowed_users")
devices_per_user = md.get("devices_per_user")
if not lookup_key:
try:
price = sub["items"]["data"][0]["price"]
lookup_key = _lookup_key_from_price(price)
except Exception:
lookup_key = ""
def _to_int(x: Any) -> Optional[int]:
try:
return int(x)
except Exception:
return None
if lookup_key and not allowed_users:
policy = _policy_for_lookup_key(lookup_key)
allowed_users = allowed_users or str(policy.allowed_users)
devices_per_user = devices_per_user or str(policy.devices_per_user)
if not lookup_key:
_log_event("license_skip", {
"event_id": event_id,
"reason": "missing_lookup_key",
"subscription_id": subscription_id,
})
generated_key = _upsert_license(
subscription_id=subscription_id,
customer_id=customer_id,
status=status,
lookup_key=lookup_key,
allowed_users=_to_int(allowed_users),
devices_per_user=_to_int(devices_per_user),
customer_email=customer_email,
client_reference_id=(client_reference_id or "").strip() or None,
current_period_end=_to_int(current_period_end),
current_period_start=_to_int(current_period_start),
)
_log_event("license_upsert", {
"event_id": event_id,
"etype": etype,
"subscription_id": subscription_id,
"status": status,
"lookup_key": lookup_key,
"email": customer_email,
"client_reference_id": client_reference_id,
"license_key": generated_key,
})
generated_key = _upsert_license(
subscription_id=subscription_id,
customer_id=customer_id,
status=status,
lookup_key=lookup_key,
allowed_users=_to_int(allowed_users),
devices_per_user=_to_int(devices_per_user),
customer_email=customer_email,
client_reference_id=(client_reference_id or "").strip() or None,
current_period_end=_to_int(current_period_end),
current_period_start=_to_int(current_period_start),
)
_log_event("license_upsert", {
"event_id": event_id,
"etype": etype,
"subscription_id": subscription_id,
"status": status,
"lookup_key": lookup_key,
"email": customer_email,
"client_reference_id": client_reference_id,
"license_key": generated_key,
})
if customer_email and generated_key:
try:
send_license_email(customer_email, generated_key)
except Exception as mail_exc:
_log_event("license_email_failed", {
"event_id": event_id,
"email": customer_email,
"error": str(mail_exc),
})
if customer_email and generated_key:
try:
send_license_email(customer_email, generated_key)
except Exception as mail_exc:
_log_event("license_email_failed", {
"event_id": event_id,
"email": customer_email,
"error": str(mail_exc),
})
elif etype == "setup_intent.succeeded":
from aza_ai_credit import ensure_ai_credit_schema, process_setup_intent_succeeded
with sqlite3.connect(DB_PATH) as con:
ensure_ai_credit_schema(con)
ai_result = process_setup_intent_succeeded(con, setup_intent=obj)
_log_event("ai_credit_setup_intent", {
"event_id": event_id,
"result": ai_result,
})
elif etype == "payment_intent.succeeded":
pi_md = obj.get("metadata") or {}
pi_purpose = (pi_md.get("aza_purpose") or "").strip()
if pi_purpose in ("ai_topup", "ai_auto_topup"):
from aza_ai_credit import (
ensure_ai_credit_schema,
process_auto_topup_payment_intent_succeeded,
process_topup_payment_intent_succeeded,
)
with sqlite3.connect(DB_PATH) as con:
ensure_ai_credit_schema(con)
if pi_purpose == "ai_topup":
ai_result = process_topup_payment_intent_succeeded(con, payment_intent=obj)
else:
ai_result = process_auto_topup_payment_intent_succeeded(con, payment_intent=obj)
_log_event("ai_credit_payment_intent", {
"event_id": event_id,
"purpose": pi_purpose,
"result": ai_result,
})
elif etype in ("customer.subscription.updated", "customer.subscription.deleted"):
subscription_id = obj.get("id")