#!/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()