Add subcription feature 9

This commit is contained in:
Urtzi Alfaro
2026-01-16 20:25:45 +01:00
parent fa7b62bd6c
commit 3a7d57ef90
19 changed files with 1833 additions and 985 deletions

View File

@@ -1324,7 +1324,9 @@ class StripeClient(PaymentProvider):
async def update_subscription(
self,
subscription_id: str,
new_price_id: str
new_price_id: str,
proration_behavior: str = 'create_prorations',
preserve_trial: bool = False
) -> Dict[str, Any]:
"""
Update subscription price (plan upgrade/downgrade)
@@ -1332,35 +1334,69 @@ class StripeClient(PaymentProvider):
Args:
subscription_id: Subscription ID
new_price_id: New price ID
proration_behavior: How to handle proration ('create_prorations', 'none', 'always_invoice')
- 'none': No proration charges (useful during trial)
- 'create_prorations': Create prorated charges (default)
- 'always_invoice': Always create an invoice
preserve_trial: If True, preserves the trial period after upgrade
Returns:
Subscription update result
Subscription update result including trial info
"""
try:
subscription = stripe.Subscription.retrieve(subscription_id)
updated_subscription = stripe.Subscription.modify(
subscription_id,
items=[{
# Check if subscription is currently trialing
is_trialing = subscription.status == 'trialing'
trial_end = subscription.trial_end
# Build the modification params
modify_params = {
'items': [{
'id': subscription['items']['data'][0].id,
'price': new_price_id,
}],
proration_behavior='create_prorations',
idempotency_key=f"update_sub_{uuid.uuid4()}"
'proration_behavior': proration_behavior,
'idempotency_key': f"update_sub_{uuid.uuid4()}"
}
# Preserve trial period if requested and currently trialing
# When changing plans during trial, Stripe will by default end the trial
# By explicitly setting trial_end, we preserve it
if preserve_trial and is_trialing and trial_end:
modify_params['trial_end'] = trial_end
logger.info(
"Preserving trial period during upgrade",
extra={
"subscription_id": subscription_id,
"trial_end": trial_end
}
)
updated_subscription = stripe.Subscription.modify(
subscription_id,
**modify_params
)
logger.info(
"Subscription updated",
extra={
"subscription_id": subscription_id,
"new_price_id": new_price_id
"new_price_id": new_price_id,
"proration_behavior": proration_behavior,
"was_trialing": is_trialing,
"new_status": updated_subscription.status
}
)
return {
'subscription_id': updated_subscription.id,
'status': updated_subscription.status,
'current_period_end': updated_subscription.current_period_end
'current_period_start': updated_subscription.current_period_start,
'current_period_end': updated_subscription.current_period_end,
'trial_end': updated_subscription.trial_end,
'is_trialing': updated_subscription.status == 'trialing',
'customer_id': updated_subscription.customer
}
except stripe.error.StripeError as e:
@@ -1537,6 +1573,13 @@ class StripeClient(PaymentProvider):
try:
invoices = stripe.Invoice.list(customer=customer_id, limit=10)
# Debug: Log all invoice IDs and amounts to see what Stripe returns
logger.info("stripe_invoices_retrieved",
customer_id=customer_id,
invoice_count=len(invoices.data),
invoice_ids=[inv.id for inv in invoices.data],
invoice_amounts=[inv.amount_due for inv in invoices.data])
return {
'invoices': [
{
@@ -1545,7 +1588,12 @@ class StripeClient(PaymentProvider):
'amount_paid': inv.amount_paid / 100,
'status': inv.status,
'created': inv.created,
'invoice_pdf': inv.invoice_pdf
'invoice_pdf': inv.invoice_pdf,
'hosted_invoice_url': inv.hosted_invoice_url,
'number': inv.number,
'currency': inv.currency,
'description': inv.description,
'trial': inv.amount_due == 0 # Mark trial invoices
}
for inv in invoices.data
]

View File

@@ -48,6 +48,10 @@ class BaseFastAPIService:
database_manager: Optional[DatabaseManager] = None,
expected_tables: Optional[List[str]] = None,
custom_health_checks: Optional[Dict[str, Callable[[], Any]]] = None,
redis_enabled: bool = False,
redis_url: Optional[str] = None,
redis_db: int = 0,
redis_max_connections: int = 50,
enable_metrics: bool = True,
enable_health_checks: bool = True,
enable_cors: bool = True,
@@ -80,6 +84,14 @@ class BaseFastAPIService:
setup_logging(service_name, log_level)
self.logger = structlog.get_logger()
# Initialize Redis client if enabled
self.redis_enabled = redis_enabled
self.redis_client = None
self.redis_initialized = False
if redis_enabled:
self._initialize_redis(redis_url, redis_db, redis_max_connections)
# Will be set during app creation
self.app: Optional[FastAPI] = None
self.health_manager = None