import os import stripe from fastapi import APIRouter, Request, HTTPException, Depends from pydantic import BaseModel from typing import Optional from core.subscription.middleware import verify_token from core.subscription.db import SessionLocal from core.subscription.models import User from clerk_backend_api import Clerk stripe_router = APIRouter() stripe.api_key = os.getenv("STRIPE_SECRET_KEY") endpoint_secret = os.getenv("STRIPE_WEBHOOK_SECRET") clerk_secret = os.getenv("CLERK_SECRET_KEY") clerk = Clerk(bearer_auth=clerk_secret) if clerk_secret else None FRONTEND_URL = os.getenv("FRONTEND_URL", "https://grantforge-frontend.onrender.com") # ─── Checkout session ─────────────────────────────────────────────────────── class CheckoutRequest(BaseModel): plan: Optional[str] = "pro" # "pro" | "business" @stripe_router.post("/subscription/checkout") async def create_checkout_session( payload: CheckoutRequest, token_data: dict = Depends(verify_token), ): """Tworzy sesję Stripe Checkout i zwraca URL do płatności.""" user_id = token_data.get("sub") if not user_id: raise HTTPException(status_code=401, detail="Brak autoryzacji") price_id = os.getenv("STRIPE_PRICE_ID_PRO") if not price_id: raise HTTPException( status_code=503, detail="Stripe price ID nie skonfigurowany." ) if not stripe.api_key: raise HTTPException( status_code=503, detail="Stripe nie jest skonfigurowany. Skontaktuj się z supportem.", ) success_url = f"{FRONTEND_URL}/projects?upgraded=1&tier={payload.plan}&session_id={{CHECKOUT_SESSION_ID}}" cancel_url = f"{FRONTEND_URL}/cennik?cancelled=1" try: session = stripe.checkout.Session.create( mode="subscription", payment_method_types=["card"], line_items=[{"price": price_id, "quantity": 1}], success_url=success_url, cancel_url=cancel_url, client_reference_id=user_id, metadata={"tier": payload.plan or "pro"}, locale="pl", billing_address_collection="auto", tax_id_collection={"enabled": True}, invoice_creation={"enabled": True}, ) return {"checkout_url": session.url, "session_id": session.id} except stripe.error.InvalidRequestError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Błąd Stripe: {str(e)}") # ─── Stripe Webhook ─────────────────────────────────────────────────────── @stripe_router.post("/webhook/stripe") async def stripe_webhook_endpoint(request: Request): payload = await request.body() sig_header = request.headers.get("stripe-signature") if not endpoint_secret: return {"status": "ignored", "reason": "No webhook secret configured"} try: event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret) except ValueError: raise HTTPException(status_code=400, detail="Invalid payload") except stripe.error.SignatureVerificationError: raise HTTPException(status_code=400, detail="Invalid signature") if event["type"] == "checkout.session.completed": session = event["data"]["object"] user_id = session.get("client_reference_id") customer_id = session.get("customer") subscription_id = session.get("subscription") tier = session.get("metadata", {}).get("tier", "pro").lower() if user_id: # 1. Aktualizacja Clerk public_metadata if clerk: try: clerk.users.update_user( user_id=user_id, public_metadata={"stripe_subscription": tier} ) except Exception as e: print(f"[Webhook] Błąd Clerk update {user_id}: {e}") # 2. Aktualizacja user.tier w PostgreSQL db = SessionLocal() try: user = db.query(User).filter(User.clerk_id == user_id).first() if not user: user = User(clerk_id=user_id) db.add(user) user.tier = tier user.stripe_customer_id = customer_id user.stripe_subscription_id = subscription_id db.commit() print(f"[Webhook] ✅ User {user_id} upgrade -> {tier}") except Exception as e: db.rollback() print(f"[Webhook] ❌ DB error {user_id}: {e}") finally: db.close() elif event["type"] == "customer.subscription.deleted": subscription = event["data"]["object"] sub_id = subscription.get("id") db = SessionLocal() try: user = db.query(User).filter(User.stripe_subscription_id == sub_id).first() if user: user.tier = "free" if clerk: try: clerk.users.update_user( user_id=user.clerk_id, public_metadata={"stripe_subscription": "free"}, ) except Exception: pass db.commit() print(f"[Webhook] Downgrade user {user.clerk_id} -> free") except Exception: db.rollback() finally: db.close() return {"status": "success"}