254 lines
10 KiB
Python
254 lines
10 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:
|
|
# Create base invoice object
|
|
invoice = 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
|
|
)
|
|
|
|
# Add Stripe-specific URLs as custom attributes
|
|
invoice.invoice_pdf = stripe_invoice.invoice_pdf if hasattr(stripe_invoice, 'invoice_pdf') else None
|
|
invoice.hosted_invoice_url = stripe_invoice.hosted_invoice_url if hasattr(stripe_invoice, 'hosted_invoice_url') else None
|
|
|
|
invoices.append(invoice)
|
|
|
|
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
|