gateway/modules/services/serviceBilling/stripeCheckout.py

104 lines
3.1 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Stripe Checkout service for billing credit top-ups.
Creates Checkout Sessions for redirect-based payment flow.
"""
import logging
from typing import Optional
from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Server-side allowed amounts in CHF - never trust client
ALLOWED_AMOUNTS_CHF = [10, 25, 50, 100, 250, 500]
def create_checkout_session(
mandate_id: str,
user_id: Optional[str],
amount_chf: float
) -> str:
"""
Create a Stripe Checkout Session for credit top-up.
Amount and currency are validated server-side. The client-provided amount
must match an allowed preset.
Args:
mandate_id: Target mandate ID
user_id: Target user ID (for PREPAY_USER) or None (for mandate pool)
amount_chf: Amount in CHF (must be in ALLOWED_AMOUNTS_CHF)
Returns:
Stripe Checkout Session URL for redirect
Raises:
ValueError: If amount is invalid
"""
import stripe
# Validate amount server-side
if amount_chf not in ALLOWED_AMOUNTS_CHF:
raise ValueError(
f"Invalid amount {amount_chf} CHF. Allowed: {ALLOWED_AMOUNTS_CHF}"
)
# Pin API version from config (match Stripe Dashboard)
api_version = APP_CONFIG.get("STRIPE_API_VERSION")
if api_version:
stripe.api_version = api_version
# Get secrets
secret_key = APP_CONFIG.get("STRIPE_SECRET_KEY") or APP_CONFIG.get("STRIPE_SECRET_KEY_SECRET")
if not secret_key:
raise ValueError("STRIPE_SECRET_KEY or STRIPE_SECRET_KEY_SECRET not configured")
stripe.api_key = secret_key
frontend_url = APP_CONFIG.get("APP_FRONTEND_URL", "https://nyla-int.poweron-center.net")
base_path = "/admin/billing"
success_url = f"{frontend_url.rstrip('/')}{base_path}?success=true&session_id={{CHECKOUT_SESSION_ID}}"
cancel_url = f"{frontend_url.rstrip('/')}{base_path}?canceled=true"
# Amount in cents for Stripe (CHF uses 2 decimal places)
amount_cents = int(round(amount_chf * 100))
metadata = {
"mandateId": mandate_id,
"amountChf": str(amount_chf),
}
if user_id:
metadata["userId"] = user_id
session = stripe.checkout.Session.create(
mode="payment",
line_items=[
{
"price_data": {
"currency": "chf",
"unit_amount": amount_cents,
"product_data": {
"name": "Guthaben aufladen",
"description": "AI Service Guthaben (CHF)",
},
},
"quantity": 1,
}
],
success_url=success_url,
cancel_url=cancel_url,
metadata=metadata,
)
if not session or not session.url:
raise ValueError("Stripe Checkout Session creation failed")
logger.info(
f"Created Stripe Checkout Session {session.id} for mandate {mandate_id}, "
f"amount {amount_chf} CHF"
)
return session.url