fix(billing): unify quota counters, fix Stripe webhooks, tier/plan sync
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m16s

This commit is contained in:
2026-06-14 17:39:34 +02:00
parent fa637abff0
commit 45e44dd7b2
9 changed files with 546 additions and 256 deletions

View File

@@ -93,34 +93,45 @@ class UserRepository:
self.db.commit()
return True
def increment_usage(
self, user_id: str, docs: int = 0, pages: int = 0, api_calls: int = 0
) -> Optional[User]:
"""Increment usage counters"""
def reset_usage_if_needed(self, user_id: str) -> Optional[User]:
"""Reset monthly counters if usage_reset_date is in a previous month."""
user = self.get_by_id(user_id)
if not user:
return None
# Check if usage needs to be reset (monthly)
if user.usage_reset_date:
now = datetime.now(timezone.utc)
if (
now.month != user.usage_reset_date.month
or now.year != user.usage_reset_date.year
):
user.docs_translated_this_month = 0
user.pages_translated_this_month = 0
user.api_calls_this_month = 0
user.usage_reset_date = now
user.docs_translated_this_month += docs
user.pages_translated_this_month += pages
user.api_calls_this_month += api_calls
self.db.commit()
self.db.refresh(user)
now = datetime.now(timezone.utc)
reset_date = user.usage_reset_date
if reset_date is None or reset_date.month != now.month or reset_date.year != now.year:
user.docs_translated_this_month = 0
user.pages_translated_this_month = 0
user.api_calls_this_month = 0
user.usage_reset_date = now
user.updated_at = now
self.db.commit()
self.db.refresh(user)
return user
def increment_usage(
self, user_id: str, docs: int = 0, pages: int = 0, api_calls: int = 0
) -> Optional[User]:
"""Increment usage counters atomically via SQL UPDATE."""
from sqlalchemy import update
now = datetime.now(timezone.utc)
self.db.execute(
update(User)
.where(User.id == user_id)
.values(
docs_translated_this_month=User.docs_translated_this_month + docs,
pages_translated_this_month=User.pages_translated_this_month + pages,
api_calls_this_month=User.api_calls_this_month + api_calls,
updated_at=now,
)
.execution_options(synchronize_session=False)
)
self.db.commit()
return self.get_by_id(user_id)
def add_credits(self, user_id: str, credits: int) -> Optional[User]:
"""Add extra credits to user"""
user = self.get_by_id(user_id)
@@ -133,14 +144,21 @@ class UserRepository:
return user
def use_credits(self, user_id: str, credits: int) -> bool:
"""Use credits from user balance"""
user = self.get_by_id(user_id)
if not user or user.extra_credits < credits:
return False
"""Use credits from user balance atomically (prevents overdraft)."""
from sqlalchemy import update
user.extra_credits -= credits
now = datetime.now(timezone.utc)
result = self.db.execute(
update(User)
.where(User.id == user_id, User.extra_credits >= credits)
.values(
extra_credits=User.extra_credits - credits,
updated_at=now,
)
.execution_options(synchronize_session=False)
)
self.db.commit()
return True
return result.rowcount > 0
def get_all_users(
self, skip: int = 0, limit: int = 100, plan: Optional[PlanType] = None