Coverage for backend / app / payments / customer.py: 100%
40 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-17 21:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-17 21:34 +0000
1"""Stripe Customer API"""
3import time
5from fastapi import HTTPException
6from sqlalchemy.exc import SQLAlchemyError
7from sqlalchemy.orm import Session
9from app.config import settings
10from app.models import User
11from app.payments import stripe, logger
14async def create_customer(user: User) -> stripe.Customer:
15 """Create a Stripe customer, with test clock if in test mode.
16 :param user: User object
17 :return: Customer object"""
19 customer_params = {"email": user.email, "metadata": {"user_id": str(user.id)}}
21 if settings.test_mode:
22 test_clock = await stripe.test_helpers.TestClock.create_async(
23 frozen_time=int(time.time()),
24 name=f"Test clock for user {user.id}",
25 )
26 customer_params["test_clock"] = test_clock.id
27 logger.info(f"Created test clock {test_clock.id} for user {user.id}")
29 return await stripe.Customer.create_async(**customer_params)
32async def get_or_create_stripe_customer(
33 user: User,
34 db: Session,
35) -> str:
36 """Get or create Stripe customer for the user.
37 :param user: User object
38 :param db: Database session
39 :return: Customer ID string
40 :raises HTTPException: On validation or Stripe errors"""
42 try:
43 # If the user as a customer id, retrieve the existing customer
44 if user.stripe_details.customer_id:
45 customer = await stripe.Customer.retrieve_async(user.stripe_details.customer_id)
47 # If a non-deleted Stripe customer was found
48 if customer and not customer.get("deleted", False): # noqa
49 # Update Stripe email if it doesn't match our database
50 if customer["email"] != user.email:
51 customer = await stripe.Customer.modify_async(user.stripe_details.customer_id, email=user.email)
52 logger.info(f"Updated Stripe customer {customer.id} email to {user.email}")
53 else:
54 logger.info(f"Retrieved existing Stripe customer {customer.id} for user {user.id}")
55 else:
56 customer = await create_customer(user)
57 user.stripe_details.customer_id = customer.id
58 db.commit()
59 logger.info(f"Created new Stripe customer {customer.id} for user {user.id}")
61 # If no customer, create one
62 else:
63 customer = await create_customer(user)
64 user.stripe_details.customer_id = customer.id
65 db.commit()
66 logger.info(f"Created new Stripe customer {customer.id} for user {user.id}")
68 return user.stripe_details.customer_id
70 except stripe.error.StripeError as e:
71 logger.error(f"Stripe API error for user {user.id}: {str(e)}", exc_info=True)
72 db.rollback()
73 raise HTTPException(status_code=503, detail="Payment service temporarily unavailable. Please try again.")
74 except SQLAlchemyError as e:
75 logger.error(f"Database error for user {user.id}: {str(e)}", exc_info=True)
76 db.rollback()
77 raise HTTPException(status_code=500, detail="An error occurred. Please try again.")