169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Post-check auto-topup live test + optional restore (no charges)."""
|
|
import json
|
|
import sqlite3
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
DB = Path("/root/aza-app/data/stripe_webhook.sqlite")
|
|
PRACTICE = "prac_883ddc21fb6a"
|
|
SUB = "wc_sub_1502"
|
|
TEST_REQUEST_ID = "test_popup_budget_20260521_exhaust"
|
|
BACKUP = "/root/aza-app/backups/auto_topup_trigger_live_test_20260521_195425"
|
|
AUTO_COOLDOWN = 86400
|
|
sys.path.insert(0, "/root/aza-app")
|
|
|
|
from aza_ai_budget import LicenseBudgetRow, check_allows_openai_call, compute_budget_snapshot
|
|
from aza_ai_credit import (
|
|
AUTO_TOPUP_COOLDOWN_SEC,
|
|
get_topup_settings,
|
|
last_successful_auto_topup_ts,
|
|
sum_topups_chf_this_month,
|
|
)
|
|
|
|
|
|
def mask_stripe_id(val: str | None, prefix: str) -> str | None:
|
|
s = (val or "").strip()
|
|
if not s.startswith(prefix):
|
|
return None
|
|
if len(s) <= 10:
|
|
return f"{prefix}***"
|
|
return f"{s[:7]}...{s[-4:]}"
|
|
|
|
|
|
def load_lic(con):
|
|
row = con.execute(
|
|
"SELECT subscription_id, customer_email, customer_id, practice_id, lookup_key, status, "
|
|
"current_period_start, current_period_end FROM licenses WHERE subscription_id=? AND practice_id=?",
|
|
(SUB, PRACTICE),
|
|
).fetchone()
|
|
return LicenseBudgetRow(
|
|
subscription_id=row[0],
|
|
customer_email=row[1] or "",
|
|
customer_id=row[2],
|
|
practice_id=row[3],
|
|
lookup_key=row[4],
|
|
status=row[5] or "",
|
|
period_start=int(row[6]),
|
|
period_end=int(row[7]),
|
|
)
|
|
|
|
|
|
def budget_json(con, lic):
|
|
snap = compute_budget_snapshot(con, lic)
|
|
ok, _ = check_allows_openai_call(con, lic)
|
|
return {
|
|
"monthly_budget": snap.get("budget_usd"),
|
|
"used_usd": snap.get("used_usd"),
|
|
"monthly_remaining": snap.get("remaining_usd"),
|
|
"extra_credit_remaining": snap.get("extra_credit_remaining_usd"),
|
|
"total_available": snap.get("total_available_usd"),
|
|
"available_percent": snap.get("available_percent"),
|
|
"allows_call": ok,
|
|
}
|
|
|
|
|
|
def check_auto_topups(con):
|
|
rows = con.execute(
|
|
"""
|
|
SELECT id, created_at, event_type, status, amount_paid_chf, amount_internal_usd,
|
|
stripe_payment_intent_id, stripe_checkout_session_id
|
|
FROM ai_credit_ledger
|
|
WHERE practice_id=? AND event_type IN ('auto_topup_purchase','failed_auto_topup')
|
|
ORDER BY created_at ASC
|
|
""",
|
|
(PRACTICE,),
|
|
).fetchall()
|
|
auto = [r for r in rows if r[2] == "auto_topup_purchase"]
|
|
failed = [r for r in rows if r[2] == "failed_auto_topup"]
|
|
return auto, failed
|
|
|
|
|
|
def main():
|
|
mode = (sys.argv[1] if len(sys.argv) > 1 else "check").strip().lower()
|
|
con = sqlite3.connect(str(DB))
|
|
lic = load_lic(con)
|
|
settings = get_topup_settings(con, PRACTICE)
|
|
|
|
print("=== BACKUP ===")
|
|
print(BACKUP)
|
|
|
|
print("\n=== AUTO TOPUP SETTINGS ===")
|
|
print(
|
|
"enabled=", settings.auto_topup_enabled,
|
|
"trigger=", settings.trigger_below_percent,
|
|
"monthly_limit_chf=", settings.monthly_limit_chf,
|
|
"cus=", mask_stripe_id(settings.stripe_customer_id, "cus_"),
|
|
"pm=", mask_stripe_id(settings.default_payment_method_id, "pm_"),
|
|
)
|
|
|
|
auto, failed = check_auto_topups(con)
|
|
print("\n=== LEDGER auto_topup / failed ===")
|
|
print("auto_topup_purchase count=", len(auto))
|
|
print("failed_auto_topup count=", len(failed))
|
|
for r in auto:
|
|
print(
|
|
" auto:",
|
|
"status=", r[3],
|
|
"chf=", r[4],
|
|
"usd=", r[5],
|
|
"pi=", mask_stripe_id(r[6], "pi_"),
|
|
"created_at=", r[1],
|
|
)
|
|
for r in failed:
|
|
print(" failed:", r[3], "chf=", r[4], "created_at=", r[1])
|
|
|
|
auto24 = con.execute(
|
|
"SELECT COUNT(*) FROM ai_credit_ledger WHERE practice_id=? "
|
|
"AND event_type='auto_topup_purchase' AND status='succeeded' "
|
|
"AND created_at >= ?",
|
|
(PRACTICE, int(time.time()) - AUTO_COOLDOWN),
|
|
).fetchone()[0]
|
|
print("auto_topup_succeeded_last_24h=", auto24)
|
|
|
|
last_ts = last_successful_auto_topup_ts(con, PRACTICE)
|
|
cooldown_active = bool(last_ts and (time.time() - last_ts) < AUTO_TOPUP_COOLDOWN_SEC)
|
|
print("cooldown_active=", cooldown_active)
|
|
if last_ts:
|
|
print("last_auto_topup_age_sec=", int(time.time() - last_ts))
|
|
|
|
spent_month = sum_topups_chf_this_month(con, PRACTICE)
|
|
print("auto_topups_chf_this_month=", spent_month, "limit=", settings.monthly_limit_chf)
|
|
print("monthly_limit_ok=", spent_month <= settings.monthly_limit_chf + 1e-9)
|
|
|
|
snap_before_restore = budget_json(con, lic)
|
|
print("\n=== BUDGET (before restore) ===")
|
|
print(json.dumps(snap_before_restore, ensure_ascii=False))
|
|
|
|
print("\n=== 24H BLOCK (read-only) ===")
|
|
print("would_block_retrigger=", cooldown_active)
|
|
|
|
test_events = con.execute(
|
|
"SELECT COUNT(*) FROM ai_usage_events WHERE request_id=? AND practice_id=?",
|
|
(TEST_REQUEST_ID, PRACTICE),
|
|
).fetchone()[0]
|
|
print("\n=== TEST EVENT ===")
|
|
print("test_popup_events=", test_events)
|
|
|
|
if mode == "restore":
|
|
cur = con.execute(
|
|
"DELETE FROM ai_usage_events WHERE request_id=? AND subscription_id=? AND practice_id=?",
|
|
(TEST_REQUEST_ID, SUB, PRACTICE),
|
|
)
|
|
con.commit()
|
|
print("RESTORE_OK deleted=", cur.rowcount)
|
|
snap_after = budget_json(con, lic)
|
|
print("\n=== BUDGET (after restore) ===")
|
|
print(json.dumps(snap_after, ensure_ascii=False))
|
|
settings2 = get_topup_settings(con, PRACTICE)
|
|
print("\n=== SETTINGS AFTER RESTORE ===")
|
|
print("auto_topup_enabled=", settings2.auto_topup_enabled)
|
|
last_ts2 = last_successful_auto_topup_ts(con, PRACTICE)
|
|
cooldown2 = bool(last_ts2 and (time.time() - last_ts2) < AUTO_TOPUP_COOLDOWN_SEC)
|
|
print("cooldown_still_active=", cooldown2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|