""" Stripe Payment Provider Implementation This module implements the PaymentProvider interface for Stripe """ import stripe import structlog from typing import Dict, Any, Optional from datetime import datetime from .payment_client import PaymentProvider, PaymentCustomer, PaymentMethod, Subscription, Invoice logger = structlog.get_logger() class StripeProvider(PaymentProvider): """ Stripe implementation of the PaymentProvider interface """ def __init__(self, api_key: str, webhook_secret: Optional[str] = None): """ Initialize the Stripe provider with API key """ stripe.api_key = api_key self.webhook_secret = webhook_secret async def create_customer(self, customer_data: Dict[str, Any]) -> PaymentCustomer: """ Create a customer in Stripe """ try: stripe_customer = stripe.Customer.create( email=customer_data.get('email'), name=customer_data.get('name'), phone=customer_data.get('phone'), metadata=customer_data.get('metadata', {}) ) return PaymentCustomer( id=stripe_customer.id, email=stripe_customer.email, name=stripe_customer.name, created_at=datetime.fromtimestamp(stripe_customer.created) ) except stripe.error.StripeError as e: logger.error("Failed to create Stripe customer", error=str(e)) raise e async def create_subscription(self, customer_id: str, plan_id: str, payment_method_id: str, trial_period_days: Optional[int] = None) -> Subscription: """ Create a subscription in Stripe """ try: # Attach payment method to customer stripe.PaymentMethod.attach( payment_method_id, customer=customer_id, ) # Set customer's default payment method stripe.Customer.modify( customer_id, invoice_settings={ 'default_payment_method': payment_method_id } ) # Create subscription with trial period if specified subscription_params = { 'customer': customer_id, 'items': [{'price': plan_id}], 'default_payment_method': payment_method_id, } if trial_period_days: subscription_params['trial_period_days'] = trial_period_days stripe_subscription = stripe.Subscription.create(**subscription_params) return Subscription( id=stripe_subscription.id, customer_id=stripe_subscription.customer, plan_id=plan_id, # Using the price ID as plan_id status=stripe_subscription.status, current_period_start=datetime.fromtimestamp(stripe_subscription.current_period_start), current_period_end=datetime.fromtimestamp(stripe_subscription.current_period_end), created_at=datetime.fromtimestamp(stripe_subscription.created) ) except stripe.error.StripeError as e: logger.error("Failed to create Stripe subscription", error=str(e)) raise e async def update_payment_method(self, customer_id: str, payment_method_id: str) -> PaymentMethod: """ Update the payment method for a customer in Stripe """ try: # Attach payment method to customer stripe.PaymentMethod.attach( payment_method_id, customer=customer_id, ) # Set as default payment method stripe.Customer.modify( customer_id, invoice_settings={ 'default_payment_method': payment_method_id } ) stripe_payment_method = stripe.PaymentMethod.retrieve(payment_method_id) return PaymentMethod( id=stripe_payment_method.id, type=stripe_payment_method.type, brand=getattr(stripe_payment_method, stripe_payment_method.type, {}).get('brand'), last4=getattr(stripe_payment_method, stripe_payment_method.type, {}).get('last4'), exp_month=getattr(stripe_payment_method, stripe_payment_method.type, {}).get('exp_month'), exp_year=getattr(stripe_payment_method, stripe_payment_method.type, {}).get('exp_year'), ) except stripe.error.StripeError as e: logger.error("Failed to update Stripe payment method", error=str(e)) raise e async def cancel_subscription(self, subscription_id: str) -> Subscription: """ Cancel a subscription in Stripe """ try: stripe_subscription = stripe.Subscription.delete(subscription_id) return Subscription( id=stripe_subscription.id, customer_id=stripe_subscription.customer, plan_id=subscription_id, # This would need to be retrieved differently in practice status=stripe_subscription.status, current_period_start=datetime.fromtimestamp(stripe_subscription.current_period_start), current_period_end=datetime.fromtimestamp(stripe_subscription.current_period_end), created_at=datetime.fromtimestamp(stripe_subscription.created) ) except stripe.error.StripeError as e: logger.error("Failed to cancel Stripe subscription", error=str(e)) raise e async def get_invoices(self, customer_id: str) -> list[Invoice]: """ Get invoices for a customer from Stripe """ try: stripe_invoices = stripe.Invoice.list(customer=customer_id, limit=100) invoices = [] for stripe_invoice in stripe_invoices: invoices.append(Invoice( id=stripe_invoice.id, customer_id=stripe_invoice.customer, subscription_id=stripe_invoice.subscription, amount=stripe_invoice.amount_paid / 100.0, # Convert from cents currency=stripe_invoice.currency, status=stripe_invoice.status, created_at=datetime.fromtimestamp(stripe_invoice.created), due_date=datetime.fromtimestamp(stripe_invoice.due_date) if stripe_invoice.due_date else None, description=stripe_invoice.description )) return invoices except stripe.error.StripeError as e: logger.error("Failed to retrieve Stripe invoices", error=str(e)) raise e async def get_subscription(self, subscription_id: str) -> Subscription: """ Get subscription details from Stripe """ try: stripe_subscription = stripe.Subscription.retrieve(subscription_id) return Subscription( id=stripe_subscription.id, customer_id=stripe_subscription.customer, plan_id=subscription_id, # This would need to be retrieved differently in practice status=stripe_subscription.status, current_period_start=datetime.fromtimestamp(stripe_subscription.current_period_start), current_period_end=datetime.fromtimestamp(stripe_subscription.current_period_end), created_at=datetime.fromtimestamp(stripe_subscription.created) ) except stripe.error.StripeError as e: logger.error("Failed to retrieve Stripe subscription", error=str(e)) raise e async def get_customer(self, customer_id: str) -> PaymentCustomer: """ Get customer details from Stripe """ try: stripe_customer = stripe.Customer.retrieve(customer_id) return PaymentCustomer( id=stripe_customer.id, email=stripe_customer.email, name=stripe_customer.name, created_at=datetime.fromtimestamp(stripe_customer.created) ) except stripe.error.StripeError as e: logger.error("Failed to retrieve Stripe customer", error=str(e)) raise e async def create_setup_intent(self) -> Dict[str, Any]: """ Create a setup intent for saving payment methods in Stripe """ try: setup_intent = stripe.SetupIntent.create() return { 'client_secret': setup_intent.client_secret, 'id': setup_intent.id } except stripe.error.StripeError as e: logger.error("Failed to create Stripe setup intent", error=str(e)) raise e async def create_payment_intent(self, amount: float, currency: str, customer_id: str, payment_method_id: str) -> Dict[str, Any]: """ Create a payment intent for one-time payments in Stripe """ try: payment_intent = stripe.PaymentIntent.create( amount=int(amount * 100), # Convert to cents currency=currency, customer=customer_id, payment_method=payment_method_id, confirm=True ) return { 'id': payment_intent.id, 'client_secret': payment_intent.client_secret, 'status': payment_intent.status } except stripe.error.StripeError as e: logger.error("Failed to create Stripe payment intent", error=str(e)) raise e