/ subscription_routes.py
subscription_routes.py
1 from fastapi import APIRouter, Depends, HTTPException, status, Request, BackgroundTasks 2 from fastapi.responses import JSONResponse 3 from typing import Dict, Any, Optional 4 import stripe 5 import json 6 import logging 7 from datetime import datetime 8 import os 9 10 from auth import get_current_active_user 11 from auth_models import User, ApiKeyLevel 12 from database import update_user, record_subscription_event 13 14 # Configuration de Stripe 15 stripe.api_key = os.environ.get("STRIPE_SECRET_KEY", "sk_test_51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") 16 webhook_secret = os.environ.get("STRIPE_WEBHOOK_SECRET", "whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") 17 18 # Configuration du logging 19 logger = logging.getLogger("subscription_routes") 20 21 # Configuration des produits et plans Stripe 22 STRIPE_PLANS = { 23 "basic": { 24 "price_id": "price_1XXXXXXXXXXXXXXXXXXXbasic", 25 "api_level": ApiKeyLevel.BASIC, 26 "name": "Basic Plan" 27 }, 28 "premium": { 29 "price_id": "price_1XXXXXXXXXXXXXXXXXXXpremium", 30 "api_level": ApiKeyLevel.PREMIUM, 31 "name": "Premium Plan" 32 }, 33 "enterprise": { 34 "price_id": "price_1XXXXXXXXXXXXXXXXXXXenterprise", 35 "api_level": ApiKeyLevel.ENTERPRISE, 36 "name": "Enterprise Plan" 37 } 38 } 39 40 # Mapper les price_id aux plans 41 PRICE_ID_TO_PLAN = {plan["price_id"]: {"id": plan_id, **plan} for plan_id, plan in STRIPE_PLANS.items()} 42 43 router = APIRouter( 44 prefix="/api/subscriptions", 45 tags=["subscriptions"], 46 responses={401: {"description": "Non autorisé"}}, 47 ) 48 49 @router.get("/current") 50 async def get_current_subscription(current_user: User = Depends(get_current_active_user)): 51 """ 52 Récupère l'abonnement actuel de l'utilisateur. 53 """ 54 try: 55 # Si l'utilisateur a un ID client Stripe, récupérer les informations d'abonnement 56 if hasattr(current_user, "stripe_customer_id") and current_user.stripe_customer_id: 57 # Récupérer les abonnements Stripe 58 subscriptions = stripe.Subscription.list( 59 customer=current_user.stripe_customer_id, 60 status="active", 61 limit=1 62 ) 63 64 if subscriptions and subscriptions.data: 65 subscription = subscriptions.data[0] 66 67 # Récupérer les détails du plan 68 price_id = subscription.items.data[0].price.id 69 plan_details = PRICE_ID_TO_PLAN.get(price_id, {}) 70 71 return { 72 "subscription": { 73 "id": subscription.id, 74 "status": subscription.status, 75 "current_period_end": datetime.fromtimestamp(subscription.current_period_end).isoformat(), 76 "cancel_at_period_end": subscription.cancel_at_period_end, 77 "planId": plan_details.get("id", "unknown"), 78 "planName": plan_details.get("name", "Unknown Plan") 79 } 80 } 81 82 # Si aucun abonnement actif n'est trouvé 83 return { 84 "subscription": None 85 } 86 87 except stripe.error.StripeError as e: 88 logger.error(f"Erreur Stripe lors de la récupération de l'abonnement: {e}") 89 raise HTTPException( 90 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 91 detail=f"Erreur lors de la récupération de l'abonnement: {str(e)}" 92 ) 93 except Exception as e: 94 logger.error(f"Erreur lors de la récupération de l'abonnement: {e}") 95 raise HTTPException( 96 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 97 detail="Erreur interne du serveur" 98 ) 99 100 @router.post("/create-subscription") 101 async def create_subscription( 102 data: Dict[str, Any], 103 current_user: User = Depends(get_current_active_user) 104 ): 105 """ 106 Crée un nouvel abonnement Stripe pour l'utilisateur. 107 """ 108 try: 109 price_id = data.get("priceId") 110 if not price_id: 111 raise HTTPException( 112 status_code=status.HTTP_400_BAD_REQUEST, 113 detail="ID de prix manquant" 114 ) 115 116 # Vérifier si l'utilisateur a déjà un ID client Stripe 117 customer_id = None 118 if hasattr(current_user, "stripe_customer_id") and current_user.stripe_customer_id: 119 customer_id = current_user.stripe_customer_id 120 121 # Si l'utilisateur n'a pas d'ID client, en créer un 122 if not customer_id: 123 customer = stripe.Customer.create( 124 email=current_user.email, 125 name=current_user.full_name or current_user.username, 126 metadata={ 127 "user_id": current_user.id 128 } 129 ) 130 customer_id = customer.id 131 132 # Mettre à jour l'utilisateur avec l'ID client Stripe 133 current_user.stripe_customer_id = customer_id 134 await update_user(current_user) 135 136 # Vérifier les abonnements existants 137 existing_subscriptions = stripe.Subscription.list( 138 customer=customer_id, 139 status="active" 140 ) 141 142 # Si l'utilisateur a déjà un abonnement actif, le mettre à jour 143 if existing_subscriptions and existing_subscriptions.data: 144 existing_sub = existing_subscriptions.data[0] 145 146 # Mettre à jour l'abonnement existant 147 updated_subscription = stripe.Subscription.modify( 148 existing_sub.id, 149 items=[{ 150 'id': existing_sub['items']['data'][0].id, 151 'price': price_id, 152 }], 153 payment_behavior='allow_incomplete', 154 proration_behavior='create_prorations' 155 ) 156 157 return { 158 "clientSecret": updated_subscription.latest_invoice.payment_intent.client_secret, 159 "subscription": { 160 "id": updated_subscription.id, 161 "status": updated_subscription.status 162 } 163 } 164 165 # Créer un nouvel abonnement 166 subscription = stripe.Subscription.create( 167 customer=customer_id, 168 items=[ 169 { 170 "price": price_id 171 } 172 ], 173 payment_behavior='default_incomplete', 174 expand=["latest_invoice.payment_intent"] 175 ) 176 177 return { 178 "clientSecret": subscription.latest_invoice.payment_intent.client_secret, 179 "subscription": { 180 "id": subscription.id, 181 "status": subscription.status 182 } 183 } 184 185 except stripe.error.StripeError as e: 186 logger.error(f"Erreur Stripe lors de la création de l'abonnement: {e}") 187 raise HTTPException( 188 status_code=status.HTTP_400_BAD_REQUEST, 189 detail=f"Erreur Stripe: {str(e)}" 190 ) 191 except Exception as e: 192 logger.error(f"Erreur lors de la création de l'abonnement: {e}") 193 raise HTTPException( 194 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 195 detail="Erreur interne du serveur" 196 ) 197 198 @router.post("/cancel") 199 async def cancel_subscription( 200 data: Dict[str, Any], 201 current_user: User = Depends(get_current_active_user) 202 ): 203 """ 204 Annule l'abonnement de l'utilisateur à la fin de la période actuelle. 205 """ 206 try: 207 subscription_id = data.get("subscriptionId") 208 if not subscription_id: 209 raise HTTPException( 210 status_code=status.HTTP_400_BAD_REQUEST, 211 detail="ID d'abonnement manquant" 212 ) 213 214 # Récupérer l'abonnement 215 subscription = stripe.Subscription.retrieve(subscription_id) 216 217 # Vérifier que l'utilisateur est bien le propriétaire de l'abonnement 218 if not hasattr(current_user, "stripe_customer_id") or subscription.customer != current_user.stripe_customer_id: 219 raise HTTPException( 220 status_code=status.HTTP_403_FORBIDDEN, 221 detail="Vous n'êtes pas autorisé à annuler cet abonnement" 222 ) 223 224 # Annuler l'abonnement à la fin de la période 225 canceled_subscription = stripe.Subscription.modify( 226 subscription_id, 227 cancel_at_period_end=True 228 ) 229 230 return { 231 "subscription": { 232 "id": canceled_subscription.id, 233 "status": canceled_subscription.status, 234 "cancel_at_period_end": canceled_subscription.cancel_at_period_end, 235 "current_period_end": datetime.fromtimestamp(canceled_subscription.current_period_end).isoformat() 236 } 237 } 238 239 except stripe.error.StripeError as e: 240 logger.error(f"Erreur Stripe lors de l'annulation de l'abonnement: {e}") 241 raise HTTPException( 242 status_code=status.HTTP_400_BAD_REQUEST, 243 detail=f"Erreur Stripe: {str(e)}" 244 ) 245 except Exception as e: 246 logger.error(f"Erreur lors de l'annulation de l'abonnement: {e}") 247 raise HTTPException( 248 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 249 detail="Erreur interne du serveur" 250 ) 251 252 @router.post("/reactivate") 253 async def reactivate_subscription( 254 data: Dict[str, Any], 255 current_user: User = Depends(get_current_active_user) 256 ): 257 """ 258 Réactive un abonnement annulé. 259 """ 260 try: 261 subscription_id = data.get("subscriptionId") 262 if not subscription_id: 263 raise HTTPException( 264 status_code=status.HTTP_400_BAD_REQUEST, 265 detail="ID d'abonnement manquant" 266 ) 267 268 # Récupérer l'abonnement 269 subscription = stripe.Subscription.retrieve(subscription_id) 270 271 # Vérifier que l'utilisateur est bien le propriétaire de l'abonnement 272 if not hasattr(current_user, "stripe_customer_id") or subscription.customer != current_user.stripe_customer_id: 273 raise HTTPException( 274 status_code=status.HTTP_403_FORBIDDEN, 275 detail="Vous n'êtes pas autorisé à réactiver cet abonnement" 276 ) 277 278 # Réactiver l'abonnement 279 reactivated_subscription = stripe.Subscription.modify( 280 subscription_id, 281 cancel_at_period_end=False 282 ) 283 284 return { 285 "subscription": { 286 "id": reactivated_subscription.id, 287 "status": reactivated_subscription.status, 288 "cancel_at_period_end": reactivated_subscription.cancel_at_period_end, 289 "current_period_end": datetime.fromtimestamp(reactivated_subscription.current_period_end).isoformat() 290 } 291 } 292 293 except stripe.error.StripeError as e: 294 logger.error(f"Erreur Stripe lors de la réactivation de l'abonnement: {e}") 295 raise HTTPException( 296 status_code=status.HTTP_400_BAD_REQUEST, 297 detail=f"Erreur Stripe: {str(e)}" 298 ) 299 except Exception as e: 300 logger.error(f"Erreur lors de la réactivation de l'abonnement: {e}") 301 raise HTTPException( 302 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 303 detail="Erreur interne du serveur" 304 ) 305 306 @router.post("/webhook") 307 async def stripe_webhook( 308 request: Request, 309 background_tasks: BackgroundTasks 310 ): 311 """ 312 Webhook pour recevoir les événements Stripe. 313 """ 314 # Obtenir la charge utile de la requête 315 payload = await request.body() 316 sig_header = request.headers.get("Stripe-Signature") 317 318 try: 319 # Vérifier la signature Stripe 320 event = stripe.Webhook.construct_event( 321 payload, sig_header, webhook_secret 322 ) 323 except ValueError as e: 324 logger.error(f"Erreur de décodage de la charge utile: {e}") 325 raise HTTPException(status_code=400, detail="Charge utile non valide") 326 except stripe.error.SignatureVerificationError as e: 327 logger.error(f"Erreur de vérification de signature: {e}") 328 raise HTTPException(status_code=400, detail="Signature non valide") 329 330 # Traiter l'événement 331 background_tasks.add_task(handle_stripe_event, event) 332 333 return {"status": "success"} 334 335 async def handle_stripe_event(event): 336 """ 337 Traite un événement Stripe en arrière-plan. 338 """ 339 try: 340 # Log de l'événement 341 logger.info(f"Événement Stripe reçu: {event.type}") 342 343 # Traiter différents types d'événements 344 if event.type == "invoice.payment_succeeded": 345 await handle_payment_succeeded(event.data.object) 346 elif event.type == "invoice.payment_failed": 347 await handle_payment_failed(event.data.object) 348 elif event.type == "customer.subscription.created": 349 await handle_subscription_created(event.data.object) 350 elif event.type == "customer.subscription.updated": 351 await handle_subscription_updated(event.data.object) 352 elif event.type == "customer.subscription.deleted": 353 await handle_subscription_deleted(event.data.object) 354 except Exception as e: 355 logger.error(f"Erreur lors du traitement de l'événement Stripe: {e}") 356 357 async def handle_payment_succeeded(invoice): 358 """ 359 Traite un paiement réussi. 360 """ 361 try: 362 # Récupérer les informations client et abonnement 363 subscription_id = invoice.subscription 364 customer_id = invoice.customer 365 366 # Récupérer l'abonnement 367 subscription = stripe.Subscription.retrieve(subscription_id) 368 369 # Récupérer les détails du plan 370 price_id = subscription.items.data[0].price.id 371 plan_details = PRICE_ID_TO_PLAN.get(price_id, {}) 372 373 # Récupérer l'utilisateur à partir des métadonnées du client 374 from database import get_user_by_stripe_id 375 user = await get_user_by_stripe_id(customer_id) 376 377 if user: 378 # Mettre à jour le niveau d'API de l'utilisateur si l'abonnement est actif 379 if subscription.status == "active": 380 api_level = plan_details.get("api_level", ApiKeyLevel.FREE) 381 user.subscription = api_level 382 await update_user(user) 383 384 # Enregistrer l'événement d'abonnement 385 await record_subscription_event( 386 user_id=user.id, 387 event_type="subscription_created", 388 subscription_id=subscription.id, 389 plan_id=plan_details.get("id", "unknown"), 390 plan_name=plan_details.get("name", "Unknown Plan"), 391 status=subscription.status 392 ) 393 394 logger.info(f"Abonnement créé pour l'utilisateur {user.id}: {subscription.id}") 395 except Exception as e: 396 logger.error(f"Erreur lors du traitement de la création d'abonnement: {e}") 397 398 async def handle_subscription_updated(subscription): 399 """ 400 Traite la mise à jour d'un abonnement. 401 """ 402 try: 403 # Récupérer les informations client 404 customer_id = subscription.customer 405 406 # Récupérer les détails du plan 407 price_id = subscription.items.data[0].price.id 408 plan_details = PRICE_ID_TO_PLAN.get(price_id, {}) 409 410 # Récupérer l'utilisateur à partir des métadonnées du client 411 from database import get_user_by_stripe_id 412 user = await get_user_by_stripe_id(customer_id) 413 414 if user: 415 # Mettre à jour le niveau d'API de l'utilisateur en fonction du statut de l'abonnement 416 if subscription.status == "active": 417 api_level = plan_details.get("api_level", ApiKeyLevel.FREE) 418 user.subscription = api_level 419 elif subscription.status in ["past_due", "unpaid", "canceled", "incomplete_expired"]: 420 # Rétrograder au niveau gratuit si l'abonnement est inactif 421 user.subscription = ApiKeyLevel.FREE 422 423 await update_user(user) 424 425 # Enregistrer l'événement d'abonnement 426 await record_subscription_event( 427 user_id=user.id, 428 event_type="subscription_updated", 429 subscription_id=subscription.id, 430 plan_id=plan_details.get("id", "unknown"), 431 plan_name=plan_details.get("name", "Unknown Plan"), 432 status=subscription.status, 433 cancel_at_period_end=subscription.cancel_at_period_end 434 ) 435 436 logger.info(f"Abonnement mis à jour pour l'utilisateur {user.id}: {subscription.id}, statut: {subscription.status}") 437 except Exception as e: 438 logger.error(f"Erreur lors du traitement de la mise à jour d'abonnement: {e}") 439 440 async def handle_subscription_deleted(subscription): 441 """ 442 Traite la suppression d'un abonnement. 443 """ 444 try: 445 # Récupérer les informations client 446 customer_id = subscription.customer 447 448 # Récupérer l'utilisateur à partir des métadonnées du client 449 from database import get_user_by_stripe_id 450 user = await get_user_by_stripe_id(customer_id) 451 452 if user: 453 # Rétrograder au niveau gratuit 454 user.subscription = ApiKeyLevel.FREE 455 await update_user(user) 456 457 # Enregistrer l'événement d'abonnement 458 await record_subscription_event( 459 user_id=user.id, 460 event_type="subscription_deleted", 461 subscription_id=subscription.id, 462 status="canceled" 463 ) 464 465 logger.info(f"Abonnement supprimé pour l'utilisateur {user.id}: {subscription.id}") 466 except Exception as e: 467 logger.error(f"Erreur lors du traitement de la suppression d'abonnement: {e}") 468 469 @router.get("/plans") 470 async def get_plans(): 471 """ 472 Récupère la liste des plans disponibles. 473 """ 474 try: 475 # Formatage des plans pour l'interface utilisateur 476 formatted_plans = [] 477 478 for plan_id, plan_details in STRIPE_PLANS.items(): 479 # Récupérer les détails du prix Stripe 480 price = stripe.Price.retrieve(plan_details["price_id"], expand=["product"]) 481 482 formatted_plans.append({ 483 "id": plan_id, 484 "name": plan_details["name"], 485 "priceId": plan_details["price_id"], 486 "price": price.unit_amount / 100, # Convertir les centimes en dollars/euros 487 "currency": price.currency, 488 "interval": price.recurring.interval, 489 "description": price.product.description if hasattr(price.product, "description") else None, 490 "features": price.product.metadata.get("features", "").split(",") if hasattr(price.product, "metadata") else [] 491 }) 492 493 return { 494 "plans": formatted_plans 495 } 496 497 except stripe.error.StripeError as e: 498 logger.error(f"Erreur Stripe lors de la récupération des plans: {e}") 499 raise HTTPException( 500 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 501 detail=f"Erreur Stripe: {str(e)}" 502 ) 503 except Exception as e: 504 logger.error(f"Erreur lors de la récupération des plans: {e}") 505 raise HTTPException( 506 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 507 detail="Erreur interne du serveur" 508 ) 509 510 @router.get("/invoices") 511 async def get_invoices(current_user: User = Depends(get_current_active_user)): 512 """ 513 Récupère les factures de l'utilisateur. 514 """ 515 try: 516 # Vérifier si l'utilisateur a un ID client Stripe 517 if not hasattr(current_user, "stripe_customer_id") or not current_user.stripe_customer_id: 518 return { 519 "invoices": [] 520 } 521 522 # Récupérer les factures 523 invoices = stripe.Invoice.list( 524 customer=current_user.stripe_customer_id, 525 limit=20 526 ) 527 528 # Formatage des factures pour l'interface utilisateur 529 formatted_invoices = [] 530 531 for invoice in invoices.data: 532 formatted_invoice = { 533 "id": invoice.id, 534 "number": invoice.number, 535 "amount": invoice.total / 100, # Convertir les centimes en dollars/euros 536 "currency": invoice.currency, 537 "status": invoice.status, 538 "date": datetime.fromtimestamp(invoice.created).isoformat(), 539 "due_date": datetime.fromtimestamp(invoice.due_date).isoformat() if invoice.due_date else None, 540 "pdf": invoice.invoice_pdf 541 } 542 543 formatted_invoices.append(formatted_invoice) 544 545 return { 546 "invoices": formatted_invoices 547 } 548 549 except stripe.error.StripeError as e: 550 logger.error(f"Erreur Stripe lors de la récupération des factures: {e}") 551 raise HTTPException( 552 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 553 detail=f"Erreur Stripe: {str(e)}" 554 ) 555 except Exception as e: 556 logger.error(f"Erreur lors de la récupération des factures: {e}") 557 raise HTTPException( 558 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 559 detail="Erreur interne du serveur" 560 ) 561 562 @router.post("/update-payment-method") 563 async def update_payment_method( 564 data: Dict[str, Any], 565 current_user: User = Depends(get_current_active_user) 566 ): 567 """ 568 Met à jour la méthode de paiement de l'utilisateur. 569 """ 570 try: 571 # Vérifier si l'utilisateur a un ID client Stripe 572 if not hasattr(current_user, "stripe_customer_id") or not current_user.stripe_customer_id: 573 raise HTTPException( 574 status_code=status.HTTP_400_BAD_REQUEST, 575 detail="Aucun client Stripe associé à cet utilisateur" 576 ) 577 578 # Récupérer le payment_method_id 579 payment_method_id = data.get("paymentMethodId") 580 if not payment_method_id: 581 raise HTTPException( 582 status_code=status.HTTP_400_BAD_REQUEST, 583 detail="ID de méthode de paiement manquant" 584 ) 585 586 # Attacher la méthode de paiement au client 587 payment_method = stripe.PaymentMethod.attach( 588 payment_method_id, 589 customer=current_user.stripe_customer_id 590 ) 591 592 # Définir comme méthode de paiement par défaut 593 stripe.Customer.modify( 594 current_user.stripe_customer_id, 595 invoice_settings={ 596 "default_payment_method": payment_method_id 597 } 598 ) 599 600 return { 601 "success": True, 602 "payment_method": { 603 "id": payment_method.id, 604 "type": payment_method.type, 605 "last4": payment_method.card.last4 if payment_method.type == "card" else None, 606 "brand": payment_method.card.brand if payment_method.type == "card" else None, 607 "exp_month": payment_method.card.exp_month if payment_method.type == "card" else None, 608 "exp_year": payment_method.card.exp_year if payment_method.type == "card" else None 609 } 610 } 611 612 except stripe.error.StripeError as e: 613 logger.error(f"Erreur Stripe lors de la mise à jour de la méthode de paiement: {e}") 614 raise HTTPException( 615 status_code=status.HTTP_400_BAD_REQUEST, 616 detail=f"Erreur Stripe: {str(e)}" 617 ) 618 except Exception as e: 619 logger.error(f"Erreur lors de la mise à jour de la méthode de paiement: {e}") 620 raise HTTPException( 621 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 622 detail="Erreur interne du serveur" 623 ) 624 625 @router.get("/payment-methods") 626 async def get_payment_methods(current_user: User = Depends(get_current_active_user)): 627 """ 628 Récupère les méthodes de paiement de l'utilisateur. 629 """ 630 try: 631 # Vérifier si l'utilisateur a un ID client Stripe 632 if not hasattr(current_user, "stripe_customer_id") or not current_user.stripe_customer_id: 633 return { 634 "payment_methods": [] 635 } 636 637 # Récupérer les méthodes de paiement 638 payment_methods = stripe.PaymentMethod.list( 639 customer=current_user.stripe_customer_id, 640 type="card" 641 ) 642 643 # Récupérer la méthode de paiement par défaut 644 customer = stripe.Customer.retrieve(current_user.stripe_customer_id) 645 default_payment_method_id = customer.invoice_settings.default_payment_method 646 647 # Formatage des méthodes de paiement pour l'interface utilisateur 648 formatted_payment_methods = [] 649 650 for pm in payment_methods.data: 651 formatted_pm = { 652 "id": pm.id, 653 "type": pm.type, 654 "last4": pm.card.last4 if pm.type == "card" else None, 655 "brand": pm.card.brand if pm.type == "card" else None, 656 "exp_month": pm.card.exp_month if pm.type == "card" else None, 657 "exp_year": pm.card.exp_year if pm.type == "card" else None, 658 "is_default": pm.id == default_payment_method_id 659 } 660 661 formatted_payment_methods.append(formatted_pm) 662 663 return { 664 "payment_methods": formatted_payment_methods 665 } 666 667 except stripe.error.StripeError as e: 668 logger.error(f"Erreur Stripe lors de la récupération des méthodes de paiement: {e}") 669 raise HTTPException( 670 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 671 detail=f"Erreur Stripe: {str(e)}" 672 ) 673 except Exception as e: 674 logger.error(f"Erreur lors de la récupération des méthodes de paiement: {e}") 675 raise HTTPException( 676 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 677 detail="Erreur interne du serveur" 678 ) 679 680 @router.delete("/payment-methods/{payment_method_id}") 681 async def delete_payment_method( 682 payment_method_id: str, 683 current_user: User = Depends(get_current_active_user) 684 ): 685 """ 686 Supprime une méthode de paiement. 687 """ 688 try: 689 # Vérifier si l'utilisateur a un ID client Stripe 690 if not hasattr(current_user, "stripe_customer_id") or not current_user.stripe_customer_id: 691 raise HTTPException( 692 status_code=status.HTTP_400_BAD_REQUEST, 693 detail="Aucun client Stripe associé à cet utilisateur" 694 ) 695 696 # Vérifier que la méthode de paiement appartient à l'utilisateur 697 payment_methods = stripe.PaymentMethod.list( 698 customer=current_user.stripe_customer_id, 699 type="card" 700 ) 701 702 payment_method_belongs_to_user = False 703 for pm in payment_methods.data: 704 if pm.id == payment_method_id: 705 payment_method_belongs_to_user = True 706 break 707 708 if not payment_method_belongs_to_user: 709 raise HTTPException( 710 status_code=status.HTTP_403_FORBIDDEN, 711 detail="Cette méthode de paiement n'appartient pas à cet utilisateur" 712 ) 713 714 # Vérifier si c'est la méthode de paiement par défaut 715 customer = stripe.Customer.retrieve(current_user.stripe_customer_id) 716 is_default = customer.invoice_settings.default_payment_method == payment_method_id 717 718 # Si c'est la méthode par défaut et qu'il y a d'autres méthodes, en définir une autre comme défaut 719 if is_default and len(payment_methods.data) > 1: 720 new_default = next(pm for pm in payment_methods.data if pm.id != payment_method_id) 721 stripe.Customer.modify( 722 current_user.stripe_customer_id, 723 invoice_settings={ 724 "default_payment_method": new_default.id 725 } 726 ) 727 728 # Détacher la méthode de paiement 729 stripe.PaymentMethod.detach(payment_method_id) 730 731 return {"success": True} 732 733 except stripe.error.StripeError as e: 734 logger.error(f"Erreur Stripe lors de la suppression de la méthode de paiement: {e}") 735 raise HTTPException( 736 status_code=status.HTTP_400_BAD_REQUEST, 737 detail=f"Erreur Stripe: {str(e)}" 738 ) 739 except Exception as e: 740 logger.error(f"Erreur lors de la suppression de la méthode de paiement: {e}") 741 raise HTTPException( 742 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 743 detail="Erreur interne du serveur" 744 )_id) 745 746 if user: 747 # Mettre à jour le niveau d'API de l'utilisateur 748 api_level = plan_details.get("api_level", ApiKeyLevel.FREE) 749 user.subscription = api_level 750 await update_user(user) 751 752 # Enregistrer l'événement d'abonnement 753 await record_subscription_event( 754 user_id=user.id, 755 event_type="payment_succeeded", 756 subscription_id=subscription_id, 757 invoice_id=invoice.id, 758 amount=invoice.amount_paid / 100, # Convertir les centimes en dollars/euros 759 plan_id=plan_details.get("id", "unknown"), 760 plan_name=plan_details.get("name", "Unknown Plan") 761 ) 762 763 logger.info(f"Niveau d'API mis à jour pour l'utilisateur {user.id}: {api_level}") 764 except Exception as e: 765 logger.error(f"Erreur lors du traitement du paiement réussi: {e}") 766 767 async def handle_payment_failed(invoice): 768 """ 769 Traite un paiement échoué. 770 """ 771 try: 772 # Récupérer les informations client et abonnement 773 subscription_id = invoice.subscription 774 customer_id = invoice.customer 775 776 # Récupérer l'utilisateur à partir des métadonnées du client 777 from database import get_user_by_stripe_id 778 user = await get_user_by_stripe_id(customer_id) 779 780 if user: 781 # Enregistrer l'événement d'abonnement 782 await record_subscription_event( 783 user_id=user.id, 784 event_type="payment_failed", 785 subscription_id=subscription_id, 786 invoice_id=invoice.id, 787 amount=invoice.amount_due / 100, # Convertir les centimes en dollars/euros 788 error_message=invoice.last_payment_error.message if invoice.last_payment_error else "Unknown error" 789 ) 790 791 logger.warning(f"Paiement échoué pour l'utilisateur {user.id}, abonnement {subscription_id}") 792 except Exception as e: 793 logger.error(f"Erreur lors du traitement du paiement échoué: {e}") 794 795 async def handle_subscription_created(subscription): 796 """ 797 Traite la création d'un abonnement. 798 """ 799 try: 800 # Récupérer les informations client 801 customer_id = subscription.customer 802 803 # Récupérer les détails du plan 804 price_id = subscription.items.data[0].price.id 805 plan_details = PRICE_ID_TO_PLAN.get(price_id, {}) 806 807 # Récupérer l'utilisateur à partir des métadonnées du client 808 from database import get_user_by_stripe_id 809 user = await get_user_by_stripe_id(customer_id) 810 if user: 811 # Mettre à jour le niveau d'API de l'utilisateur 812 api_level = plan_details.get("api_level", ApiKeyLevel.FREE) 813 user.subscription = api_level 814 await update_user(user) 815 816 # Enregistrer l'événement d'abonnement 817 await record_subscription_event( 818 user_id=user.id, 819 event_type="subscription_created", 820 subscription_id=subscription.id, 821 plan_id=plan_details.get("id", "unknown"), 822 plan_name=plan_details.get("name", "Unknown Plan"), 823 status=subscription.status 824 ) 825 826 logger.info(f"Abonnement créé pour l'utilisateur {user.id}: {subscription.id}") 827 except Exception as e: 828 logger.error(f"Erreur lors du traitement de la création d'abonnement: {e}") 829 830 async def handle_subscription_updated(subscription): 831 """ 832 Traite la mise à jour d'un abonnement. 833 """ 834 try: 835 # Récupérer les informations client 836 customer_id = subscription.customer 837 838 # Récupérer les détails du plan 839 price_id = subscription.items.data[0].price.id 840 plan_details = PRICE_ID_TO_PLAN.get(price_id, {}) 841 842 # Récupérer l'utilisateur à partir des métadonnées du client 843 from database import get_user_by_stripe_id 844 user = await get_user_by_stripe_id(customer_id) 845 if user: 846 # Mettre à jour le niveau d'API de l'utilisateur 847 api_level = plan_details.get("api_level", ApiKeyLevel.FREE) 848 user.subscription = api_level 849 await update_user(user) 850 851 # Enregistrer l'événement d'abonnement 852 await record_subscription_event( 853 user_id=user.id, 854 event_type="subscription_updated", 855 subscription_id=subscription.id, 856 plan_id=plan_details.get("id", "unknown"), 857 plan_name=plan_details.get("name", "Unknown Plan"), 858 status=subscription.status 859 ) 860 861 logger.info(f"Abonnement mis à jour pour l'utilisateur {user.id}: {subscription.id}") 862 except Exception as e: 863 logger.error(f"Erreur lors du traitement de la mise à jour d'abonnement: {e}") 864 async def handle_subscription_deleted(subscription): 865 """ 866 Traite la suppression d'un abonnement. 867 """ 868 try: 869 # Récupérer les informations client 870 customer_id = subscription.customer 871 872 # Récupérer l'utilisateur à partir des métadonnées du client 873 from database import get_user_by_stripe_id 874 user = await get_user_by_stripe_id(customer_id) 875 876 if user: 877 # Mettre à jour le niveau d'API de l'utilisateur 878 user.subscription = ApiKeyLevel.FREE 879 await update_user(user) 880 881 # Enregistrer l'événement d'abonnement 882 await record_subscription_event( 883 user_id=user.id, 884 event_type="subscription_deleted", 885 subscription_id=subscription.id, 886 status=subscription.status 887 ) 888 889 logger.info(f"Abonnement supprimé pour l'utilisateur {user.id}: {subscription.id}") 890 except Exception as e: 891 logger.error(f"Erreur lors du traitement de la suppression d'abonnement: {e}")