""" Stripe payment integration for subscriptions and credits """ import os from typing import Optional, Dict, Any from datetime import datetime # Try to import stripe try: import stripe STRIPE_AVAILABLE = True except ImportError: STRIPE_AVAILABLE = False stripe = None from models.subscription import PlanType, PLANS, CREDIT_PACKAGES, SubscriptionStatus from services.auth_service import get_user_by_id, update_user, add_credits # Stripe configuration STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY", "") STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET", "") STRIPE_PUBLISHABLE_KEY = os.getenv("STRIPE_PUBLISHABLE_KEY", "") if STRIPE_AVAILABLE and STRIPE_SECRET_KEY: stripe.api_key = STRIPE_SECRET_KEY def is_stripe_configured() -> bool: """Check if Stripe is properly configured""" return STRIPE_AVAILABLE and bool(STRIPE_SECRET_KEY) async def create_checkout_session( user_id: str, plan: PlanType, billing_period: str = "monthly", # monthly or yearly success_url: str = "", cancel_url: str = "", ) -> Optional[Dict[str, Any]]: """Create a Stripe checkout session for subscription""" if not is_stripe_configured(): return {"error": "Stripe not configured", "demo_mode": True} user = get_user_by_id(user_id) if not user: return {"error": "User not found"} plan_config = PLANS[plan] price_id = plan_config[f"stripe_price_id_{billing_period}"] if not price_id: return {"error": "Plan not available for purchase"} try: # Create or get Stripe customer if user.stripe_customer_id: customer_id = user.stripe_customer_id else: customer = stripe.Customer.create( email=user.email, name=user.name, metadata={"user_id": user_id} ) customer_id = customer.id update_user(user_id, {"stripe_customer_id": customer_id}) # Create checkout session session = stripe.checkout.Session.create( customer=customer_id, mode="subscription", payment_method_types=["card"], line_items=[{"price": price_id, "quantity": 1}], success_url=success_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/dashboard?session_id={{CHECKOUT_SESSION_ID}}", cancel_url=cancel_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/pricing", metadata={"user_id": user_id, "plan": plan.value}, subscription_data={ "metadata": {"user_id": user_id, "plan": plan.value} } ) return { "session_id": session.id, "url": session.url } except Exception as e: return {"error": str(e)} async def create_credits_checkout( user_id: str, package_index: int, success_url: str = "", cancel_url: str = "", ) -> Optional[Dict[str, Any]]: """Create a Stripe checkout session for credit purchase""" if not is_stripe_configured(): return {"error": "Stripe not configured", "demo_mode": True} if package_index < 0 or package_index >= len(CREDIT_PACKAGES): return {"error": "Invalid package"} user = get_user_by_id(user_id) if not user: return {"error": "User not found"} package = CREDIT_PACKAGES[package_index] try: # Create or get Stripe customer if user.stripe_customer_id: customer_id = user.stripe_customer_id else: customer = stripe.Customer.create( email=user.email, name=user.name, metadata={"user_id": user_id} ) customer_id = customer.id update_user(user_id, {"stripe_customer_id": customer_id}) # Create checkout session session = stripe.checkout.Session.create( customer=customer_id, mode="payment", payment_method_types=["card"], line_items=[{"price": package["stripe_price_id"], "quantity": 1}], success_url=success_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/dashboard?credits=purchased", cancel_url=cancel_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/pricing", metadata={ "user_id": user_id, "credits": package["credits"], "type": "credits" } ) return { "session_id": session.id, "url": session.url } except Exception as e: return {"error": str(e)} async def handle_webhook(payload: bytes, sig_header: str) -> Dict[str, Any]: """Handle Stripe webhook events""" if not is_stripe_configured(): return {"error": "Stripe not configured"} try: event = stripe.Webhook.construct_event( payload, sig_header, STRIPE_WEBHOOK_SECRET ) except ValueError: return {"error": "Invalid payload"} except stripe.error.SignatureVerificationError: return {"error": "Invalid signature"} # Handle the event if event["type"] == "checkout.session.completed": session = event["data"]["object"] await handle_checkout_completed(session) elif event["type"] == "customer.subscription.updated": subscription = event["data"]["object"] await handle_subscription_updated(subscription) elif event["type"] == "customer.subscription.deleted": subscription = event["data"]["object"] await handle_subscription_deleted(subscription) elif event["type"] == "invoice.payment_failed": invoice = event["data"]["object"] await handle_payment_failed(invoice) return {"status": "success"} async def handle_checkout_completed(session: Dict): """Handle successful checkout""" metadata = session.get("metadata", {}) user_id = metadata.get("user_id") if not user_id: return # Check if it's a credit purchase if metadata.get("type") == "credits": credits = int(metadata.get("credits", 0)) add_credits(user_id, credits) return # It's a subscription plan = metadata.get("plan") if plan: subscription_id = session.get("subscription") update_user(user_id, { "plan": plan, "subscription_status": SubscriptionStatus.ACTIVE.value, "stripe_subscription_id": subscription_id, "docs_translated_this_month": 0, # Reset on new subscription "pages_translated_this_month": 0, }) async def handle_subscription_updated(subscription: Dict): """Handle subscription updates""" metadata = subscription.get("metadata", {}) user_id = metadata.get("user_id") if not user_id: return status_map = { "active": SubscriptionStatus.ACTIVE, "past_due": SubscriptionStatus.PAST_DUE, "canceled": SubscriptionStatus.CANCELED, "trialing": SubscriptionStatus.TRIALING, "paused": SubscriptionStatus.PAUSED, } stripe_status = subscription.get("status", "active") status = status_map.get(stripe_status, SubscriptionStatus.ACTIVE) update_user(user_id, { "subscription_status": status.value, "subscription_ends_at": datetime.fromtimestamp( subscription.get("current_period_end", 0) ).isoformat() if subscription.get("current_period_end") else None }) async def handle_subscription_deleted(subscription: Dict): """Handle subscription cancellation""" metadata = subscription.get("metadata", {}) user_id = metadata.get("user_id") if not user_id: return update_user(user_id, { "plan": PlanType.FREE.value, "subscription_status": SubscriptionStatus.CANCELED.value, "stripe_subscription_id": None, }) async def handle_payment_failed(invoice: Dict): """Handle failed payment""" customer_id = invoice.get("customer") if not customer_id: return # Find user by customer ID and update status # In production, query database by stripe_customer_id async def cancel_subscription(user_id: str) -> Dict[str, Any]: """Cancel a user's subscription""" if not is_stripe_configured(): return {"error": "Stripe not configured"} user = get_user_by_id(user_id) if not user or not user.stripe_subscription_id: return {"error": "No active subscription"} try: # Cancel at period end subscription = stripe.Subscription.modify( user.stripe_subscription_id, cancel_at_period_end=True ) return { "status": "canceling", "cancel_at": datetime.fromtimestamp(subscription.cancel_at).isoformat() if subscription.cancel_at else None } except Exception as e: return {"error": str(e)} async def get_billing_portal_url(user_id: str) -> Optional[str]: """Get Stripe billing portal URL for customer""" if not is_stripe_configured(): return None user = get_user_by_id(user_id) if not user or not user.stripe_customer_id: return None try: session = stripe.billing_portal.Session.create( customer=user.stripe_customer_id, return_url=f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/dashboard" ) return session.url except: return None