update
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
Backup Auto-Topup URL/Trigger/Disable
|
||||
Timestamp: 20260521_185350
|
||||
Restore: copy files back to project root
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,234 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""API-Routen KI-Zusatzguthaben (Phase 1, keine Live-Charges ohne Freigabe)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from aza_ai_budget import resolve_license_for_empfang
|
||||
from aza_ai_credit import (
|
||||
DEFAULT_TOPUP_CANCEL_URL,
|
||||
DEFAULT_TOPUP_CHF,
|
||||
DEFAULT_TOPUP_SUCCESS_URL,
|
||||
MAX_TOPUP_CHF,
|
||||
MIN_TOPUP_CHF,
|
||||
TopupSettings,
|
||||
chf_to_internal_usd,
|
||||
create_setup_checkout_session,
|
||||
create_topup_checkout_session,
|
||||
ensure_ai_credit_schema,
|
||||
get_topup_settings,
|
||||
list_user_credit_history,
|
||||
save_topup_settings,
|
||||
)
|
||||
from aza_security import require_api_token
|
||||
|
||||
router = APIRouter(tags=["ai-credit"], dependencies=[Depends(require_api_token)])
|
||||
|
||||
|
||||
def _stripe_db_path() -> Path:
|
||||
import os
|
||||
|
||||
base = Path(__file__).resolve().parent
|
||||
return Path(os.environ.get("STRIPE_DB_PATH", str(base / "data" / "stripe_webhook.sqlite")))
|
||||
|
||||
|
||||
def _resolve_practice_license(
|
||||
*,
|
||||
device_id: Optional[str],
|
||||
practice_id: Optional[str],
|
||||
):
|
||||
db_path = _stripe_db_path()
|
||||
if not db_path.exists():
|
||||
raise HTTPException(status_code=503, detail="Billing database unavailable")
|
||||
import backend_main as bm
|
||||
|
||||
bm.ensure_license_schema(db_path)
|
||||
with sqlite3.connect(str(db_path)) as con:
|
||||
ensure_ai_credit_schema(con)
|
||||
lic = resolve_license_for_empfang(
|
||||
con,
|
||||
x_device_id=device_id,
|
||||
session_practice_id=practice_id,
|
||||
)
|
||||
if not lic:
|
||||
raise HTTPException(status_code=402, detail="No active license mapping")
|
||||
if not (lic.practice_id or "").strip():
|
||||
raise HTTPException(status_code=400, detail="practice_id missing on license")
|
||||
return con, lic
|
||||
|
||||
|
||||
class TopupCheckoutIn(BaseModel):
|
||||
package_chf: Optional[float] = Field(default=30.0)
|
||||
custom_amount_chf: Optional[float] = None
|
||||
save_payment_method: bool = False
|
||||
success_url: str = Field(default=DEFAULT_TOPUP_SUCCESS_URL)
|
||||
cancel_url: str = Field(default=DEFAULT_TOPUP_CANCEL_URL)
|
||||
|
||||
|
||||
class AutoTopupSettingsIn(BaseModel):
|
||||
enabled: bool = False
|
||||
amount_chf: float = Field(default=30.0, ge=MIN_TOPUP_CHF, le=MAX_TOPUP_CHF)
|
||||
trigger_below_percent: int = Field(default=10, ge=1, le=50)
|
||||
monthly_limit_chf: float = Field(default=90.0, ge=MIN_TOPUP_CHF, le=900.0)
|
||||
|
||||
|
||||
class SetupSessionIn(BaseModel):
|
||||
success_url: str = Field(default="https://aza-medwork.ch/ki-topup/setup-success")
|
||||
cancel_url: str = Field(default="https://aza-medwork.ch/ki-topup/setup-cancel")
|
||||
|
||||
|
||||
@router.post("/v1/ai-credit/topup/checkout")
|
||||
def ai_credit_topup_checkout(
|
||||
body: TopupCheckoutIn,
|
||||
request: Request,
|
||||
x_device_id: Optional[str] = Header(default=None, alias="X-Device-Id"),
|
||||
x_practice_id: Optional[str] = Header(default=None, alias="X-Practice-Id"),
|
||||
) -> JSONResponse:
|
||||
device_id = (x_device_id or "").strip() or None
|
||||
practice_hdr = (x_practice_id or "").strip() or None
|
||||
db_path = _stripe_db_path()
|
||||
import backend_main as bm
|
||||
|
||||
bm.ensure_license_schema(db_path)
|
||||
with sqlite3.connect(str(db_path)) as con:
|
||||
ensure_ai_credit_schema(con)
|
||||
lic = resolve_license_for_empfang(
|
||||
con,
|
||||
x_device_id=device_id,
|
||||
session_practice_id=practice_hdr,
|
||||
)
|
||||
if not lic or not (lic.practice_id or "").strip():
|
||||
return JSONResponse(status_code=402, content={"ok": False, "error_code": "NO_LICENSE"})
|
||||
paid_chf = float(body.custom_amount_chf or body.package_chf or DEFAULT_TOPUP_CHF)
|
||||
if paid_chf < MIN_TOPUP_CHF or paid_chf > MAX_TOPUP_CHF:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"ok": False, "error_code": "INVALID_AMOUNT", "min_chf": MIN_TOPUP_CHF, "max_chf": MAX_TOPUP_CHF},
|
||||
)
|
||||
internal_usd = chf_to_internal_usd(paid_chf)
|
||||
result = create_topup_checkout_session(
|
||||
practice_id=lic.practice_id or "",
|
||||
subscription_id=lic.subscription_id,
|
||||
customer_email=lic.customer_email,
|
||||
paid_chf=paid_chf,
|
||||
internal_usd=internal_usd,
|
||||
save_payment_method=body.save_payment_method,
|
||||
success_url=DEFAULT_TOPUP_SUCCESS_URL,
|
||||
cancel_url=(body.cancel_url or DEFAULT_TOPUP_CANCEL_URL).strip() or DEFAULT_TOPUP_CANCEL_URL,
|
||||
)
|
||||
return JSONResponse(content=result)
|
||||
|
||||
|
||||
@router.get("/v1/ai-credit/history")
|
||||
def ai_credit_history(
|
||||
x_device_id: Optional[str] = Header(default=None, alias="X-Device-Id"),
|
||||
x_practice_id: Optional[str] = Header(default=None, alias="X-Practice-Id"),
|
||||
limit: int = Query(default=100, ge=1, le=200),
|
||||
) -> JSONResponse:
|
||||
device_id = (x_device_id or "").strip() or None
|
||||
practice_hdr = (x_practice_id or "").strip() or None
|
||||
db_path = _stripe_db_path()
|
||||
import backend_main as bm
|
||||
|
||||
bm.ensure_license_schema(db_path)
|
||||
with sqlite3.connect(str(db_path)) as con:
|
||||
ensure_ai_credit_schema(con)
|
||||
lic = resolve_license_for_empfang(
|
||||
con,
|
||||
x_device_id=device_id,
|
||||
session_practice_id=practice_hdr,
|
||||
)
|
||||
if not lic or not (lic.practice_id or "").strip():
|
||||
return JSONResponse(status_code=402, content={"ok": False, "error_code": "NO_LICENSE"})
|
||||
pid = lic.practice_id or ""
|
||||
items = list_user_credit_history(con, practice_id=pid, limit=limit)
|
||||
return JSONResponse(
|
||||
content={
|
||||
"ok": True,
|
||||
"practice_id": pid,
|
||||
"subscription_id": lic.subscription_id,
|
||||
"items": items,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/v1/ai-credit/auto-topup/settings")
|
||||
def ai_credit_auto_topup_settings(
|
||||
body: AutoTopupSettingsIn,
|
||||
x_device_id: Optional[str] = Header(default=None, alias="X-Device-Id"),
|
||||
x_practice_id: Optional[str] = Header(default=None, alias="X-Practice-Id"),
|
||||
) -> JSONResponse:
|
||||
device_id = (x_device_id or "").strip() or None
|
||||
practice_hdr = (x_practice_id or "").strip() or None
|
||||
db_path = _stripe_db_path()
|
||||
import backend_main as bm
|
||||
|
||||
bm.ensure_license_schema(db_path)
|
||||
with sqlite3.connect(str(db_path)) as con:
|
||||
ensure_ai_credit_schema(con)
|
||||
lic = resolve_license_for_empfang(
|
||||
con,
|
||||
x_device_id=device_id,
|
||||
session_practice_id=practice_hdr,
|
||||
)
|
||||
if not lic or not (lic.practice_id or "").strip():
|
||||
return JSONResponse(status_code=402, content={"ok": False, "error_code": "NO_LICENSE"})
|
||||
existing = get_topup_settings(con, lic.practice_id or "")
|
||||
settings = TopupSettings(
|
||||
practice_id=lic.practice_id or "",
|
||||
auto_topup_enabled=body.enabled,
|
||||
topup_amount_chf=body.amount_chf,
|
||||
internal_credit_usd=chf_to_internal_usd(body.amount_chf),
|
||||
trigger_below_percent=body.trigger_below_percent,
|
||||
monthly_limit_chf=body.monthly_limit_chf,
|
||||
stripe_customer_id=existing.stripe_customer_id,
|
||||
default_payment_method_id=existing.default_payment_method_id,
|
||||
)
|
||||
save_topup_settings(con, settings)
|
||||
return JSONResponse(
|
||||
content={
|
||||
"ok": True,
|
||||
"practice_id": settings.practice_id,
|
||||
"auto_topup_enabled": settings.auto_topup_enabled,
|
||||
"amount_chf": settings.topup_amount_chf,
|
||||
"trigger_below_percent": settings.trigger_below_percent,
|
||||
"monthly_limit_chf": settings.monthly_limit_chf,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/v1/ai-credit/auto-topup/setup-session")
|
||||
def ai_credit_auto_topup_setup_session(
|
||||
body: SetupSessionIn,
|
||||
x_device_id: Optional[str] = Header(default=None, alias="X-Device-Id"),
|
||||
x_practice_id: Optional[str] = Header(default=None, alias="X-Practice-Id"),
|
||||
) -> JSONResponse:
|
||||
device_id = (x_device_id or "").strip() or None
|
||||
practice_hdr = (x_practice_id or "").strip() or None
|
||||
db_path = _stripe_db_path()
|
||||
import backend_main as bm
|
||||
|
||||
bm.ensure_license_schema(db_path)
|
||||
with sqlite3.connect(str(db_path)) as con:
|
||||
ensure_ai_credit_schema(con)
|
||||
lic = resolve_license_for_empfang(
|
||||
con,
|
||||
x_device_id=device_id,
|
||||
session_practice_id=practice_hdr,
|
||||
)
|
||||
if not lic or not (lic.practice_id or "").strip():
|
||||
return JSONResponse(status_code=402, content={"ok": False, "error_code": "NO_LICENSE"})
|
||||
result = create_setup_checkout_session(
|
||||
practice_id=lic.practice_id or "",
|
||||
customer_email=lic.customer_email,
|
||||
success_url=body.success_url,
|
||||
cancel_url=body.cancel_url,
|
||||
)
|
||||
return JSONResponse(content=result)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,834 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests KI-Zusatzguthaben / Ledger (Mocks, keine Stripe-Charges)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from aza_ai_budget import (
|
||||
LicenseBudgetRow,
|
||||
budget_gate_blocked_payload_or_none,
|
||||
check_allows_openai_call,
|
||||
compute_budget_snapshot,
|
||||
ensure_ai_budget_schema,
|
||||
insert_usage_event,
|
||||
)
|
||||
from aza_ai_credit import (
|
||||
AUTO_TOPUP_COOLDOWN_SEC,
|
||||
DEFAULT_TOPUP_SUCCESS_URL,
|
||||
chf_to_internal_usd,
|
||||
compute_extra_credit_remaining,
|
||||
create_setup_checkout_session,
|
||||
create_topup_checkout_session,
|
||||
ensure_ai_credit_schema,
|
||||
get_topup_settings,
|
||||
has_pending_auto_topup,
|
||||
insert_ledger_event,
|
||||
last_successful_auto_topup_ts,
|
||||
ledger_exists_for_checkout,
|
||||
ledger_exists_for_payment_intent,
|
||||
list_ledger_for_practice,
|
||||
list_user_credit_history,
|
||||
mask_stripe_reference,
|
||||
maybe_run_auto_topup_for_practice,
|
||||
process_auto_topup_payment_intent_succeeded,
|
||||
process_setup_checkout_completed,
|
||||
process_setup_intent_succeeded,
|
||||
process_topup_checkout_completed,
|
||||
process_topup_payment_intent_succeeded,
|
||||
save_topup_settings,
|
||||
TopupSettings,
|
||||
)
|
||||
|
||||
|
||||
def _mk_db(path: Path) -> None:
|
||||
con = sqlite3.connect(str(path))
|
||||
con.execute(
|
||||
"""
|
||||
CREATE TABLE licenses (
|
||||
subscription_id TEXT PRIMARY KEY,
|
||||
customer_id TEXT,
|
||||
status TEXT,
|
||||
lookup_key TEXT,
|
||||
allowed_users INTEGER,
|
||||
devices_per_user INTEGER,
|
||||
customer_email TEXT,
|
||||
client_reference_id TEXT,
|
||||
current_period_end INTEGER,
|
||||
current_period_start INTEGER,
|
||||
updated_at INTEGER NOT NULL,
|
||||
license_key TEXT,
|
||||
practice_id TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
now = 1_700_000_000
|
||||
ps = now
|
||||
pe = now + 86400 * 30
|
||||
con.execute(
|
||||
"""
|
||||
INSERT INTO licenses(subscription_id, customer_id, status, lookup_key, allowed_users, devices_per_user,
|
||||
customer_email, client_reference_id, current_period_end, current_period_start, updated_at, license_key, practice_id)
|
||||
VALUES ('sub_top', 'cus_x', 'active', 'aza_basic_monthly', 1, 2,
|
||||
'cli@example.test', NULL, ?, ?, ?, 'KEY', 'prac_top')
|
||||
""",
|
||||
(pe, ps, now),
|
||||
)
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
|
||||
def _lic() -> LicenseBudgetRow:
|
||||
now = 1_700_000_000
|
||||
return LicenseBudgetRow(
|
||||
subscription_id="sub_top",
|
||||
customer_email="cli@example.test",
|
||||
customer_id="cus_x",
|
||||
practice_id="prac_top",
|
||||
lookup_key="aza_basic_monthly",
|
||||
status="active",
|
||||
period_start=now,
|
||||
period_end=now + 86400 * 30,
|
||||
)
|
||||
|
||||
|
||||
class TestAiCreditTopup(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
|
||||
self.tmp.close()
|
||||
self.db = Path(self.tmp.name)
|
||||
_mk_db(self.db)
|
||||
self.con = sqlite3.connect(str(self.db))
|
||||
ensure_ai_budget_schema(self.con)
|
||||
ensure_ai_credit_schema(self.con)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.con.close()
|
||||
self.db.unlink(missing_ok=True)
|
||||
|
||||
def test_chf30_gives_20_usd(self) -> None:
|
||||
self.assertAlmostEqual(chf_to_internal_usd(30), 20.0, places=2)
|
||||
|
||||
def test_custom_amount_formula(self) -> None:
|
||||
self.assertAlmostEqual(chf_to_internal_usd(45), 30.0, places=1)
|
||||
|
||||
def test_ledger_schema_idempotent(self) -> None:
|
||||
ensure_ai_credit_schema(self.con)
|
||||
ensure_ai_credit_schema(self.con)
|
||||
cols = [r[1] for r in self.con.execute("PRAGMA table_info(ai_credit_ledger)").fetchall()]
|
||||
self.assertIn("event_type", cols)
|
||||
|
||||
def test_webhook_writes_single_credit(self) -> None:
|
||||
session = {
|
||||
"id": "cs_test_001",
|
||||
"payment_status": "paid",
|
||||
"metadata": {
|
||||
"aza_purpose": "ai_topup",
|
||||
"practice_id": "prac_top",
|
||||
"subscription_id": "sub_top",
|
||||
"internal_credit_usd": "20",
|
||||
"paid_chf": "30",
|
||||
},
|
||||
}
|
||||
r1 = process_topup_checkout_completed(self.con, session=session)
|
||||
r2 = process_topup_checkout_completed(self.con, session=session)
|
||||
self.assertTrue(r1.get("credited"))
|
||||
self.assertTrue(r2.get("duplicate"))
|
||||
cnt = self.con.execute(
|
||||
"SELECT COUNT(*) FROM ai_credit_ledger WHERE stripe_checkout_session_id='cs_test_001'"
|
||||
).fetchone()[0]
|
||||
self.assertEqual(cnt, 1)
|
||||
|
||||
def test_failed_payment_no_credit(self) -> None:
|
||||
session = {
|
||||
"id": "cs_test_fail",
|
||||
"payment_status": "unpaid",
|
||||
"metadata": {
|
||||
"aza_purpose": "ai_topup",
|
||||
"practice_id": "prac_top",
|
||||
"subscription_id": "sub_top",
|
||||
"internal_credit_usd": "20",
|
||||
"paid_chf": "30",
|
||||
},
|
||||
}
|
||||
r = process_topup_checkout_completed(self.con, session=session)
|
||||
self.assertFalse(r.get("credited"))
|
||||
row = self.con.execute(
|
||||
"SELECT status, amount_internal_usd FROM ai_credit_ledger WHERE stripe_checkout_session_id='cs_test_fail'"
|
||||
).fetchone()
|
||||
self.assertEqual(row[0], "failed")
|
||||
self.assertEqual(row[1], 0.0)
|
||||
|
||||
def test_budget_includes_extra_when_monthly_exhausted(self) -> None:
|
||||
lic = _lic()
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_checkout_session_id="cs_paid",
|
||||
status="succeeded",
|
||||
)
|
||||
ps, pe = lic.period_start, lic.period_end
|
||||
insert_usage_event(
|
||||
self.con,
|
||||
lic=lic,
|
||||
device_id=None,
|
||||
period_start=ps,
|
||||
period_end=pe,
|
||||
operation_type="chat",
|
||||
model="gpt-4o-mini",
|
||||
input_tokens=1,
|
||||
output_tokens=1,
|
||||
total_tokens=2,
|
||||
audio_seconds=0.0,
|
||||
estimated_cost_usd=20.0,
|
||||
request_id="u1",
|
||||
status="success",
|
||||
)
|
||||
snap = compute_budget_snapshot(self.con, lic)
|
||||
self.assertEqual(snap["remaining_usd"], 0.0)
|
||||
self.assertGreater(snap.get("extra_credit_remaining_usd", 0), 0)
|
||||
ok, _ = check_allows_openai_call(self.con, lic)
|
||||
self.assertTrue(ok)
|
||||
|
||||
def test_month_rollover_extra_persists(self) -> None:
|
||||
lic = _lic()
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=15.0,
|
||||
amount_paid_chf=22.5,
|
||||
stripe_checkout_session_id="cs_roll",
|
||||
status="succeeded",
|
||||
)
|
||||
extra_before = compute_extra_credit_remaining(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
monthly_budget_usd=20.0,
|
||||
)
|
||||
lic_new = LicenseBudgetRow(
|
||||
subscription_id="sub_top",
|
||||
customer_email="cli@example.test",
|
||||
customer_id="cus_x",
|
||||
practice_id="prac_top",
|
||||
lookup_key="aza_basic_monthly",
|
||||
status="active",
|
||||
period_start=lic.period_end + 1,
|
||||
period_end=lic.period_end + 86400 * 30,
|
||||
)
|
||||
extra_after = compute_extra_credit_remaining(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
monthly_budget_usd=20.0,
|
||||
)
|
||||
self.assertAlmostEqual(extra_before, extra_after, places=2)
|
||||
snap = compute_budget_snapshot(self.con, lic_new)
|
||||
self.assertEqual(snap["used_usd"], 0.0)
|
||||
self.assertAlmostEqual(snap.get("extra_credit_remaining_usd", 0), 15.0, places=2)
|
||||
|
||||
def test_auto_topup_settings_save(self) -> None:
|
||||
settings = TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id=None,
|
||||
default_payment_method_id=None,
|
||||
)
|
||||
save_topup_settings(self.con, settings)
|
||||
loaded = get_topup_settings(self.con, "prac_top")
|
||||
self.assertTrue(loaded.auto_topup_enabled)
|
||||
self.assertEqual(loaded.monthly_limit_chf, 90.0)
|
||||
|
||||
def test_setup_session_dry_run_no_charge(self) -> None:
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
|
||||
r = create_setup_checkout_session(
|
||||
practice_id="prac_top",
|
||||
customer_email="cli@example.test",
|
||||
success_url="https://example/s",
|
||||
cancel_url="https://example/c",
|
||||
)
|
||||
self.assertTrue(r.get("dry_run"))
|
||||
self.assertIsNone(r.get("checkout_url"))
|
||||
|
||||
def test_topup_checkout_dry_run(self) -> None:
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
|
||||
r = create_topup_checkout_session(
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email="cli@example.test",
|
||||
paid_chf=30.0,
|
||||
internal_usd=20.0,
|
||||
success_url="https://example/s",
|
||||
cancel_url="https://example/c",
|
||||
)
|
||||
self.assertTrue(r.get("dry_run"))
|
||||
self.assertIsNone(r.get("checkout_url"))
|
||||
|
||||
def test_auto_topup_respects_monthly_limit(self) -> None:
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=50,
|
||||
monthly_limit_chf=60.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
for i in range(2):
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="auto_topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_checkout_session_id=f"cs_auto_{i}",
|
||||
status="succeeded",
|
||||
)
|
||||
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=True)
|
||||
self.assertEqual(r.get("reason"), "monthly_limit_reached")
|
||||
|
||||
def test_no_auto_charge_without_payment_method(self) -> None:
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id=None,
|
||||
),
|
||||
)
|
||||
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
|
||||
self.assertEqual(r.get("reason"), "no_payment_method")
|
||||
|
||||
def test_meta_json_no_secrets(self) -> None:
|
||||
eid = insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=1.0,
|
||||
meta={"note": "ok", "api_secret_key": "must_not_store", "default_payment_method_id": "pm_x"},
|
||||
)
|
||||
row = self.con.execute("SELECT meta_json FROM ai_credit_ledger WHERE id=?", (eid,)).fetchone()
|
||||
meta = json.loads(row[0])
|
||||
self.assertNotIn("api_secret_key", meta)
|
||||
self.assertNotIn("default_payment_method_id", meta)
|
||||
|
||||
def test_usage_events_unchanged_schema(self) -> None:
|
||||
cols = [r[1] for r in self.con.execute("PRAGMA table_info(ai_usage_events)").fetchall()]
|
||||
self.assertIn("estimated_cost_usd", cols)
|
||||
self.assertIn("subscription_id", cols)
|
||||
|
||||
def test_ledger_exists_for_checkout(self) -> None:
|
||||
self.assertFalse(ledger_exists_for_checkout(self.con, "cs_new"))
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=1.0,
|
||||
stripe_checkout_session_id="cs_new",
|
||||
status="succeeded",
|
||||
)
|
||||
self.assertTrue(ledger_exists_for_checkout(self.con, "cs_new"))
|
||||
|
||||
def test_setup_webhook_enables_auto_with_defaults(self) -> None:
|
||||
session = {
|
||||
"id": "cs_setup_1",
|
||||
"customer": "cus_setup",
|
||||
"setup_intent": None,
|
||||
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
|
||||
}
|
||||
r = process_setup_checkout_completed(self.con, session=session)
|
||||
self.assertTrue(r.get("handled"))
|
||||
self.assertTrue(r.get("auto_topup_enabled"))
|
||||
loaded = get_topup_settings(self.con, "prac_top")
|
||||
self.assertTrue(loaded.auto_topup_enabled)
|
||||
self.assertEqual(loaded.stripe_customer_id, "cus_setup")
|
||||
self.assertEqual(loaded.topup_amount_chf, 30.0)
|
||||
self.assertEqual(loaded.internal_credit_usd, 20.0)
|
||||
self.assertEqual(loaded.trigger_below_percent, 10)
|
||||
self.assertEqual(loaded.monthly_limit_chf, 90.0)
|
||||
ledger_count = self.con.execute("SELECT COUNT(*) FROM ai_credit_ledger").fetchone()[0]
|
||||
self.assertEqual(ledger_count, 0)
|
||||
|
||||
def test_setup_intent_succeeded_saves_payment_method(self) -> None:
|
||||
setup_intent = {
|
||||
"id": "seti_test_1",
|
||||
"customer": "cus_setup2",
|
||||
"payment_method": "pm_setup_test",
|
||||
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
|
||||
}
|
||||
r = process_setup_intent_succeeded(self.con, setup_intent=setup_intent)
|
||||
self.assertTrue(r.get("handled"))
|
||||
loaded = get_topup_settings(self.con, "prac_top")
|
||||
self.assertTrue(loaded.auto_topup_enabled)
|
||||
self.assertEqual(loaded.stripe_customer_id, "cus_setup2")
|
||||
self.assertEqual(loaded.default_payment_method_id, "pm_setup_test")
|
||||
|
||||
def test_setup_checkout_with_embedded_setup_intent_pm(self) -> None:
|
||||
session = {
|
||||
"id": "cs_setup_2",
|
||||
"customer": "cus_setup3",
|
||||
"setup_intent": {
|
||||
"id": "seti_embedded",
|
||||
"payment_method": {"id": "pm_embedded"},
|
||||
},
|
||||
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
|
||||
}
|
||||
r = process_setup_checkout_completed(self.con, session=session)
|
||||
self.assertTrue(r.get("handled"))
|
||||
loaded = get_topup_settings(self.con, "prac_top")
|
||||
self.assertEqual(loaded.default_payment_method_id, "pm_embedded")
|
||||
|
||||
def test_setup_session_response_has_checkout_url_field(self) -> None:
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
|
||||
r = create_setup_checkout_session(
|
||||
practice_id="prac_top",
|
||||
customer_email="cli@example.test",
|
||||
success_url="https://example/s",
|
||||
cancel_url="https://example/c",
|
||||
)
|
||||
self.assertIn("checkout_url", r)
|
||||
self.assertTrue(r.get("ok"))
|
||||
|
||||
def test_setup_url_field_aliases_accepted(self) -> None:
|
||||
for key in ("checkout_url", "setup_url", "url"):
|
||||
payload = {key: "https://checkout.stripe.test/setup"}
|
||||
picked = (
|
||||
payload.get("checkout_url")
|
||||
or payload.get("setup_url")
|
||||
or payload.get("url")
|
||||
or ""
|
||||
).strip()
|
||||
self.assertEqual(picked, "https://checkout.stripe.test/setup")
|
||||
|
||||
def test_24h_cooldown_blocks_second_auto_topup(self) -> None:
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
now = int(__import__("time").time())
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="auto_topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_payment_intent_id="pi_recent",
|
||||
status="succeeded",
|
||||
)
|
||||
self.con.execute(
|
||||
"UPDATE ai_credit_ledger SET created_at=? WHERE stripe_payment_intent_id='pi_recent'",
|
||||
(now - 3600,),
|
||||
)
|
||||
self.con.commit()
|
||||
self.assertLess(_now_delta(last_successful_auto_topup_ts(self.con, "prac_top")), AUTO_TOPUP_COOLDOWN_SEC)
|
||||
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
|
||||
self.assertEqual(r.get("reason"), "cooldown_24h")
|
||||
|
||||
def test_pending_auto_topup_blocks_parallel(self) -> None:
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="auto_topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
status="pending",
|
||||
)
|
||||
self.assertTrue(has_pending_auto_topup(self.con, "prac_top"))
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
|
||||
self.assertEqual(r.get("reason"), "pending_auto_topup")
|
||||
|
||||
def test_double_payment_intent_no_double_credit(self) -> None:
|
||||
pi = {
|
||||
"id": "pi_manual_1",
|
||||
"customer": "cus_x",
|
||||
"metadata": {
|
||||
"aza_purpose": "ai_topup",
|
||||
"practice_id": "prac_top",
|
||||
"subscription_id": "sub_top",
|
||||
"internal_credit_usd": "20",
|
||||
"paid_chf": "30",
|
||||
},
|
||||
}
|
||||
r1 = process_topup_payment_intent_succeeded(self.con, payment_intent=pi)
|
||||
r2 = process_topup_payment_intent_succeeded(self.con, payment_intent=pi)
|
||||
self.assertTrue(r1.get("credited"))
|
||||
self.assertTrue(r2.get("duplicate"))
|
||||
self.assertTrue(ledger_exists_for_payment_intent(self.con, "pi_manual_1"))
|
||||
cnt = self.con.execute(
|
||||
"SELECT COUNT(*) FROM ai_credit_ledger WHERE stripe_payment_intent_id='pi_manual_1'"
|
||||
).fetchone()[0]
|
||||
self.assertEqual(cnt, 1)
|
||||
|
||||
def test_auto_topup_disabled_when_env_off(self) -> None:
|
||||
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "0"
|
||||
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
|
||||
self.assertTrue(r.get("dry_run"))
|
||||
|
||||
def test_no_live_topup_when_allow_live_off(self) -> None:
|
||||
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "0"
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
|
||||
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
|
||||
r = create_topup_checkout_session(
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email="cli@example.test",
|
||||
paid_chf=30.0,
|
||||
internal_usd=20.0,
|
||||
success_url="https://example/s",
|
||||
cancel_url="https://example/c",
|
||||
)
|
||||
self.assertFalse(r.get("ok", True))
|
||||
self.assertEqual(r.get("error_code"), "LIVE_TOPUP_BLOCKED")
|
||||
|
||||
def test_gate_retries_after_mock_auto_topup_credit(self) -> None:
|
||||
from unittest.mock import patch
|
||||
|
||||
lic = _lic()
|
||||
ps, pe = lic.period_start, lic.period_end
|
||||
insert_usage_event(
|
||||
self.con,
|
||||
lic=lic,
|
||||
device_id=None,
|
||||
period_start=ps,
|
||||
period_end=pe,
|
||||
operation_type="chat",
|
||||
model="gpt-4o-mini",
|
||||
input_tokens=1,
|
||||
output_tokens=1,
|
||||
total_tokens=2,
|
||||
audio_seconds=0.0,
|
||||
estimated_cost_usd=20.0,
|
||||
request_id="gate_block",
|
||||
status="success",
|
||||
)
|
||||
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
|
||||
|
||||
def _fake_auto(*_a, **_k):
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="auto_topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_payment_intent_id="pi_gate",
|
||||
status="succeeded",
|
||||
)
|
||||
return {"ok": True, "charged": True}
|
||||
|
||||
with patch("aza_ai_credit.maybe_run_auto_topup_for_practice", side_effect=_fake_auto):
|
||||
blocked = budget_gate_blocked_payload_or_none(
|
||||
self.con,
|
||||
lic,
|
||||
device_id=None,
|
||||
request_id="req_gate",
|
||||
operation_type="chat",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
self.assertIsNone(blocked)
|
||||
|
||||
def test_failed_auto_topup_pauses_settings(self) -> None:
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
|
||||
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
|
||||
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
mock_pi = MagicMock()
|
||||
mock_pi.id = "pi_fail"
|
||||
mock_pi.status = "requires_action"
|
||||
with patch("stripe.PaymentIntent.create", return_value=mock_pi):
|
||||
r = maybe_run_auto_topup_for_practice(
|
||||
self.con, practice_id="prac_top", available_percent=5, dry_run=False
|
||||
)
|
||||
self.assertFalse(r.get("charged", True))
|
||||
self.assertTrue(r.get("paused"))
|
||||
loaded = get_topup_settings(self.con, "prac_top")
|
||||
self.assertFalse(loaded.auto_topup_enabled)
|
||||
failed_cnt = self.con.execute(
|
||||
"SELECT COUNT(*) FROM ai_credit_ledger WHERE event_type='failed_auto_topup'"
|
||||
).fetchone()[0]
|
||||
self.assertEqual(failed_cnt, 1)
|
||||
|
||||
def test_live_auto_topup_mock_success(self) -> None:
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
|
||||
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
|
||||
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
|
||||
save_topup_settings(
|
||||
self.con,
|
||||
TopupSettings(
|
||||
practice_id="prac_top",
|
||||
auto_topup_enabled=True,
|
||||
topup_amount_chf=30.0,
|
||||
internal_credit_usd=20.0,
|
||||
trigger_below_percent=10,
|
||||
monthly_limit_chf=90.0,
|
||||
stripe_customer_id="cus_x",
|
||||
default_payment_method_id="pm_test",
|
||||
),
|
||||
)
|
||||
mock_pi = MagicMock()
|
||||
mock_pi.id = "pi_ok"
|
||||
mock_pi.status = "succeeded"
|
||||
with patch("stripe.PaymentIntent.create", return_value=mock_pi):
|
||||
r = maybe_run_auto_topup_for_practice(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
available_percent=5,
|
||||
dry_run=False,
|
||||
subscription_id="sub_top",
|
||||
)
|
||||
self.assertTrue(r.get("charged"))
|
||||
row = self.con.execute(
|
||||
"SELECT status, amount_internal_usd FROM ai_credit_ledger WHERE stripe_payment_intent_id='pi_ok'"
|
||||
).fetchone()
|
||||
self.assertEqual(row[0], "succeeded")
|
||||
self.assertEqual(row[1], 20.0)
|
||||
|
||||
def test_auto_pi_webhook_idempotent(self) -> None:
|
||||
pi = {
|
||||
"id": "pi_auto_wh",
|
||||
"customer": "cus_x",
|
||||
"metadata": {
|
||||
"aza_purpose": "ai_auto_topup",
|
||||
"practice_id": "prac_top",
|
||||
"subscription_id": "sub_top",
|
||||
"internal_credit_usd": "20",
|
||||
"paid_chf": "30",
|
||||
},
|
||||
}
|
||||
r1 = process_auto_topup_payment_intent_succeeded(self.con, payment_intent=pi)
|
||||
r2 = process_auto_topup_payment_intent_succeeded(self.con, payment_intent=pi)
|
||||
self.assertTrue(r1.get("credited"))
|
||||
self.assertTrue(r2.get("duplicate"))
|
||||
|
||||
def test_topup_success_url_is_succes2(self) -> None:
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
|
||||
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
|
||||
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
|
||||
mock_session = MagicMock(url="https://checkout.stripe.com/test", id="cs_test")
|
||||
with patch("stripe.checkout.Session.create", return_value=mock_session) as mock_create:
|
||||
create_topup_checkout_session(
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email="cli@example.test",
|
||||
paid_chf=30.0,
|
||||
internal_usd=20.0,
|
||||
save_payment_method=False,
|
||||
success_url=DEFAULT_TOPUP_SUCCESS_URL,
|
||||
cancel_url="https://aza-medwork.ch",
|
||||
)
|
||||
self.assertEqual(mock_create.call_args.kwargs["success_url"], "https://aza-medwork.ch/succes2")
|
||||
|
||||
def test_abo_checkout_success_url_unchanged(self) -> None:
|
||||
import stripe_routes
|
||||
|
||||
self.assertNotEqual(
|
||||
stripe_routes.STRIPE_SUCCESS_URL or "",
|
||||
DEFAULT_TOPUP_SUCCESS_URL,
|
||||
)
|
||||
|
||||
def test_history_no_token_401(self) -> None:
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from aza_ai_credit_routes import router
|
||||
|
||||
os.environ["AZA_API_TOKEN"] = "test-history-token"
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
with TestClient(app) as client:
|
||||
resp = client.get(
|
||||
"/v1/ai-credit/history",
|
||||
headers={"X-Device-Id": "dev1", "X-Practice-Id": "prac_top"},
|
||||
)
|
||||
self.assertEqual(resp.status_code, 401)
|
||||
|
||||
def test_history_only_own_practice(self) -> None:
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_payment_intent_id="pi_prac_top_secret",
|
||||
status="succeeded",
|
||||
)
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_other",
|
||||
subscription_id="sub_other",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_payment_intent_id="pi_other_secret",
|
||||
status="succeeded",
|
||||
)
|
||||
items = list_user_credit_history(self.con, practice_id="prac_top")
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0]["amount_paid_chf"], 30.0)
|
||||
self.assertNotIn("pi_other_secret", json.dumps(items))
|
||||
|
||||
def test_history_masks_stripe_ids(self) -> None:
|
||||
masked = mask_stripe_reference("pi_1234567890abcdef")
|
||||
self.assertIn("…", masked or "")
|
||||
self.assertNotIn("abcdef", masked or "abcdef")
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=20.0,
|
||||
amount_paid_chf=30.0,
|
||||
stripe_payment_intent_id="pi_1234567890abcdef",
|
||||
status="succeeded",
|
||||
)
|
||||
items = list_user_credit_history(self.con, practice_id="prac_top")
|
||||
self.assertEqual(items[0]["stripe_payment_reference"], masked)
|
||||
|
||||
def test_history_shows_failed_and_refund(self) -> None:
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=0.0,
|
||||
amount_paid_chf=30.0,
|
||||
status="failed",
|
||||
)
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="refund",
|
||||
amount_internal_usd=-10.0,
|
||||
amount_paid_chf=-15.0,
|
||||
status="succeeded",
|
||||
)
|
||||
items = list_user_credit_history(self.con, practice_id="prac_top")
|
||||
statuses = {i["status"] for i in items}
|
||||
types = {i["event_type"] for i in items}
|
||||
self.assertIn("failed", statuses)
|
||||
self.assertIn("refund", types)
|
||||
|
||||
def test_admin_ledger_helper_unchanged(self) -> None:
|
||||
insert_ledger_event(
|
||||
self.con,
|
||||
practice_id="prac_top",
|
||||
subscription_id="sub_top",
|
||||
customer_email=None,
|
||||
event_type="topup_purchase",
|
||||
amount_internal_usd=1.0,
|
||||
stripe_checkout_session_id="cs_admin_check",
|
||||
status="succeeded",
|
||||
)
|
||||
entries = list_ledger_for_practice(self.con, "prac_top")
|
||||
self.assertEqual(len(entries), 1)
|
||||
self.assertIn("checkout_session_masked", entries[0])
|
||||
|
||||
|
||||
def _now_delta(ts: int | None) -> int:
|
||||
if not ts:
|
||||
return 999999
|
||||
return int(__import__("time").time()) - ts
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user