Files
bakery-ia/shared/clients/stripe_client.py
2025-09-25 14:30:47 +02:00

247 lines
9.8 KiB
Python

"""
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