From ef39d01e1679d4378ac461cf87d5807df4bf240b Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 31 Mar 2026 01:51:08 +0200
Subject: [PATCH] fixed database issue subscriptions
---
modules/auth/csrf.py | 15 ++++++++------
modules/interfaces/interfaceDbApp.py | 25 ++++++++++++----------
modules/routes/routeDataMandates.py | 31 +++++++++++++++++++++++++++-
modules/routes/routeStore.py | 11 ++++++----
4 files changed, 60 insertions(+), 22 deletions(-)
diff --git a/modules/auth/csrf.py b/modules/auth/csrf.py
index 7cc0c07c..bac4b0c3 100644
--- a/modules/auth/csrf.py
+++ b/modules/auth/csrf.py
@@ -88,12 +88,15 @@ class CSRFMiddleware(BaseHTTPMiddleware):
content={"detail": "Invalid CSRF token format"}
)
- # Additional CSRF validation could be added here:
- # - Check token against session
- # - Validate token expiration
- # - Verify token origin
-
- return await call_next(request)
+ try:
+ return await call_next(request)
+ except Exception as exc:
+ logger.error("Unhandled exception in %s %s: %s", request.method, request.url.path, exc)
+ from fastapi.responses import JSONResponse
+ return JSONResponse(
+ status_code=500,
+ content={"detail": "Internal server error"},
+ )
def _is_valid_csrf_token(self, token: str) -> bool:
"""
diff --git a/modules/interfaces/interfaceDbApp.py b/modules/interfaces/interfaceDbApp.py
index d980eb56..2ac768fd 100644
--- a/modules/interfaces/interfaceDbApp.py
+++ b/modules/interfaces/interfaceDbApp.py
@@ -1728,8 +1728,10 @@ class AppObjects:
self.db.recordDelete(UserMandate, um.get("id"))
logger.info(f"Cascade: deleted {len(memberships)} UserMandates for mandate {mandateId}")
- # 3. Cancel Stripe subscriptions + delete MandateSubscription records
- subs = self.db.getRecordset(MandateSubscription, recordFilter={"mandateId": mandateId})
+ # 3. Cancel Stripe subscriptions + delete MandateSubscription records (poweron_billing)
+ from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot
+ subInterface = _getSubRoot()
+ subs = subInterface.listForMandate(mandateId)
for sub in subs:
subId = sub.get("id")
stripeSubId = sub.get("stripeSubscriptionId")
@@ -1741,20 +1743,21 @@ class AppObjects:
logger.info(f"Cancelled Stripe subscription {stripeSubId} for mandate {mandateId}")
except Exception as e:
logger.warning(f"Failed to cancel Stripe sub {stripeSubId}: {e}")
- self.db.recordDelete(MandateSubscription, subId)
+ subInterface.db.recordDelete(MandateSubscription, subId)
logger.info(f"Cascade: deleted {len(subs)} subscriptions for mandate {mandateId}")
- # 3b. Delete Billing data
- billingTxs = self.db.getRecordset(BillingTransaction, recordFilter={"mandateId": mandateId}) if hasattr(BillingTransaction, '__table_name__') else []
- billingAccounts = self.db.getRecordset(BillingAccount, recordFilter={"mandateId": mandateId})
+ # 3b. Delete Billing data (poweron_billing)
+ from modules.interfaces.interfaceDbBilling import _getRootInterface as _getBillingRoot
+ billingDb = _getBillingRoot().db
+ billingAccounts = billingDb.getRecordset(BillingAccount, recordFilter={"mandateId": mandateId})
for acc in billingAccounts:
- accTxs = self.db.getRecordset(BillingTransaction, recordFilter={"accountId": acc.get("id")})
+ accTxs = billingDb.getRecordset(BillingTransaction, recordFilter={"accountId": acc.get("id")})
for tx in accTxs:
- self.db.recordDelete(BillingTransaction, tx.get("id"))
- self.db.recordDelete(BillingAccount, acc.get("id"))
- billingSettings = self.db.getRecordset(BillingSettings, recordFilter={"mandateId": mandateId})
+ billingDb.recordDelete(BillingTransaction, tx.get("id"))
+ billingDb.recordDelete(BillingAccount, acc.get("id"))
+ billingSettings = billingDb.getRecordset(BillingSettings, recordFilter={"mandateId": mandateId})
for bs in billingSettings:
- self.db.recordDelete(BillingSettings, bs.get("id"))
+ billingDb.recordDelete(BillingSettings, bs.get("id"))
if billingAccounts or billingSettings:
logger.info(f"Cascade: deleted billing data for mandate {mandateId}")
diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py
index 2c2bd31c..e98fd1cc 100644
--- a/modules/routes/routeDataMandates.py
+++ b/modules/routes/routeDataMandates.py
@@ -318,7 +318,36 @@ def create_mandate(
logger.warning(
f"Could not create default billing settings for mandate {newMandate.id}: {billingErr}"
)
-
+
+ try:
+ from modules.datamodels.datamodelSubscription import (
+ MandateSubscription, SubscriptionStatusEnum, BUILTIN_PLANS,
+ )
+ from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot
+ from datetime import datetime, timezone, timedelta
+
+ planKey = mandateData.get("planKey", "TRIAL_7D")
+ plan = BUILTIN_PLANS.get(planKey)
+ if plan:
+ now = datetime.now(timezone.utc)
+ targetStatus = SubscriptionStatusEnum.TRIALING if plan.trialDays else SubscriptionStatusEnum.ACTIVE
+ sub = MandateSubscription(
+ mandateId=str(newMandate.id),
+ planKey=planKey,
+ status=targetStatus,
+ recurring=plan.autoRenew and not plan.trialDays,
+ startedAt=now,
+ currentPeriodStart=now,
+ )
+ if plan.trialDays:
+ sub.trialEndsAt = now + timedelta(days=plan.trialDays)
+ sub.currentPeriodEnd = now + timedelta(days=plan.trialDays)
+ subInterface = _getSubRoot()
+ subInterface.createSubscription(sub)
+ logger.info(f"Created {targetStatus.value} subscription ({planKey}) for mandate {newMandate.id}")
+ except Exception as subErr:
+ logger.error(f"Failed to create subscription for mandate {newMandate.id}: {subErr}")
+
logger.info(f"Mandate {newMandate.id} created by SysAdmin {currentUser.id}")
return newMandate
diff --git a/modules/routes/routeStore.py b/modules/routes/routeStore.py
index 0d6e68ec..4af0f6b7 100644
--- a/modules/routes/routeStore.py
+++ b/modules/routes/routeStore.py
@@ -187,9 +187,12 @@ def getSubscriptionInfo(
"budgetAiCHF": None,
}
- from modules.datamodels.datamodelSubscription import MandateSubscription, BUILTIN_PLANS
- subs = db.getRecordset(MandateSubscription, recordFilter={"mandateId": mandateId})
- if not subs:
+ from modules.datamodels.datamodelSubscription import BUILTIN_PLANS
+ from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot
+
+ subInterface = _getSubRoot()
+ allSubs = subInterface.listForMandate(mandateId)
+ if not allSubs:
return {
"plan": None,
"maxDataVolumeMB": None,
@@ -197,7 +200,7 @@ def getSubscriptionInfo(
"budgetAiCHF": None,
}
- sub = subs[0]
+ sub = allSubs[0]
plan = BUILTIN_PLANS.get(sub.get("planKey"))
currentInstances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId})