Thank you! Your payment has been processed successfully.
--- name: stripe_processing description: Stripe processing skill for onetime payment and subscription with email notification. license: Stripe context: fork --- ## Overview Core Stripe payment integration in Python supporting one-time payments and subscriptions with webhook handling and email notifications. ## Use Cases - Accept one-time payments - Create subscription-based billing - Handle payment webhooks - Send payment confirmation emails - Manage payment lifecycle --- ## Phase 1: Setup & Configuration ### 1.1 Install Dependencies ```bash pip install stripe python-decouple ``` ### 1.2 Environment Variables ```env # .env STRIPE_SECRET_KEY=sk_test_51... STRIPE_PUBLISHABLE_KEY=pk_test_51... STRIPE_WEBHOOK_SECRET=whsec_... # Email settings EMAIL_HOST=smtp.gmail.com EMAIL_PORT=587 EMAIL_HOST_USER=your-email@example.com EMAIL_HOST_PASSWORD=your-app-password EMAIL_USE_TLS=True DEFAULT_FROM_EMAIL=noreply@yourcompany.com ``` ### 1.3 Django Settings (if using Django) ```python # settings.py from decouple import config STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY') STRIPE_PUBLISHABLE_KEY = config('STRIPE_PUBLISHABLE_KEY') STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET') # Email configuration EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = config('EMAIL_HOST') EMAIL_PORT = config('EMAIL_PORT', cast=int) EMAIL_HOST_USER = config('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD') EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool) DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL') ``` --- ## Phase 2: Stripe Service Layer ### 2.1 Payment Service ```python # services/stripe_service.py import stripe from decimal import Decimal import logging from typing import Dict, Optional logger = logging.getLogger(__name__) class StripePaymentService: """ Core Stripe payment service for one-time payments and subscriptions. """ def __init__(self, api_key: str): stripe.api_key = api_key self.stripe = stripe # ============= ONE-TIME PAYMENTS ============= def create_payment_intent( self, amount: Decimal, currency: str = 'usd', customer_email: Optional[str] = None, metadata: Optional[Dict] = None, description: Optional[str] = None, ) -> Dict: """ Create a payment intent for one-time payment. Args: amount: Amount in dollars (will be converted to cents) currency: Currency code (default: 'usd') customer_email: Customer's email for receipt metadata: Additional metadata (max 50 keys) description: Payment description Returns: dict: { 'payment_intent_id': 'pi_...', 'client_secret': 'pi_..._secret_...', 'amount': 2500, 'status': 'requires_payment_method' } Raises: stripe.error.StripeError: If payment intent creation fails """ try: amount_in_cents = int(amount * 100) intent = self.stripe.PaymentIntent.create( amount=amount_in_cents, currency=currency, metadata=metadata or {}, description=description, receipt_email=customer_email, automatic_payment_methods={'enabled': True}, ) logger.info(f"Payment intent created: {intent.id}") return { 'payment_intent_id': intent.id, 'client_secret': intent.client_secret, 'amount': intent.amount, 'status': intent.status, } except stripe.error.StripeError as e: logger.error(f"Stripe error: {str(e)}") raise def retrieve_payment_intent(self, payment_intent_id: str) -> Dict: """ Retrieve payment intent details. Args: payment_intent_id: Payment intent ID Returns: dict: Payment intent data """ try: intent = self.stripe.PaymentIntent.retrieve(payment_intent_id) return { 'id': intent.id, 'amount': intent.amount, 'currency': intent.currency, 'status': intent.status, 'customer_email': intent.receipt_email, 'metadata': intent.metadata, } except stripe.error.StripeError as e: logger.error(f"Error retrieving payment intent: {str(e)}") raise def cancel_payment_intent(self, payment_intent_id: str) -> Dict: """ Cancel a payment intent. Args: payment_intent_id: Payment intent ID Returns: dict: Cancelled payment intent data """ try: intent = self.stripe.PaymentIntent.cancel(payment_intent_id) logger.info(f"Payment intent cancelled: {intent.id}") return {'id': intent.id, 'status': intent.status} except stripe.error.StripeError as e: logger.error(f"Error cancelling payment intent: {str(e)}") raise # ============= SUBSCRIPTIONS ============= def create_customer( self, email: str, name: Optional[str] = None, metadata: Optional[Dict] = None, ) -> Dict: """ Create a Stripe customer for subscriptions. Args: email: Customer email name: Customer name metadata: Additional metadata Returns: dict: { 'customer_id': 'cus_...', 'email': 'customer@example.com', 'name': 'John Doe' } """ try: customer = self.stripe.Customer.create( email=email, name=name, metadata=metadata or {}, ) logger.info(f"Customer created: {customer.id}") return { 'customer_id': customer.id, 'email': customer.email, 'name': customer.name, } except stripe.error.StripeError as e: logger.error(f"Error creating customer: {str(e)}") raise def create_subscription( self, customer_id: str, price_id: str, metadata: Optional[Dict] = None, trial_period_days: Optional[int] = None, ) -> Dict: """ Create a subscription for a customer. Args: customer_id: Stripe customer ID price_id: Stripe price ID (e.g., 'price_...') metadata: Additional metadata trial_period_days: Number of trial days (optional) Returns: dict: { 'subscription_id': 'sub_...', 'client_secret': 'seti_..._secret_...', 'status': 'active', 'current_period_end': 1234567890 } """ try: subscription_params = { 'customer': customer_id, 'items': [{'price': price_id}], 'metadata': metadata or {}, 'payment_behavior': 'default_incomplete', 'payment_settings': { 'save_default_payment_method': 'on_subscription' }, 'expand': ['latest_invoice.payment_intent'], } if trial_period_days: subscription_params['trial_period_days'] = trial_period_days subscription = self.stripe.Subscription.create(**subscription_params) logger.info(f"Subscription created: {subscription.id}") return { 'subscription_id': subscription.id, 'client_secret': subscription.latest_invoice.payment_intent.client_secret, 'status': subscription.status, 'current_period_end': subscription.current_period_end, } except stripe.error.StripeError as e: logger.error(f"Error creating subscription: {str(e)}") raise def cancel_subscription( self, subscription_id: str, at_period_end: bool = True ) -> Dict: """ Cancel a subscription. Args: subscription_id: Subscription ID at_period_end: If True, cancel at end of billing period Returns: dict: Cancelled subscription data """ try: if at_period_end: subscription = self.stripe.Subscription.modify( subscription_id, cancel_at_period_end=True ) else: subscription = self.stripe.Subscription.delete(subscription_id) logger.info(f"Subscription cancelled: {subscription_id}") return { 'subscription_id': subscription.id, 'status': subscription.status, 'cancel_at_period_end': subscription.cancel_at_period_end, } except stripe.error.StripeError as e: logger.error(f"Error cancelling subscription: {str(e)}") raise def retrieve_subscription(self, subscription_id: str) -> Dict: """ Retrieve subscription details. Args: subscription_id: Subscription ID Returns: dict: Subscription data """ try: subscription = self.stripe.Subscription.retrieve(subscription_id) return { 'id': subscription.id, 'customer': subscription.customer, 'status': subscription.status, 'current_period_start': subscription.current_period_start, 'current_period_end': subscription.current_period_end, 'cancel_at_period_end': subscription.cancel_at_period_end, } except stripe.error.StripeError as e: logger.error(f"Error retrieving subscription: {str(e)}") raise # ============= WEBHOOKS ============= def verify_webhook_signature( self, payload: bytes, sig_header: str, webhook_secret: str ) -> Dict: """ Verify webhook signature and construct event. Args: payload: Raw request body as bytes sig_header: Stripe-Signature header value webhook_secret: Webhook signing secret Returns: dict: Stripe event object Raises: stripe.error.SignatureVerificationError: If signature invalid """ try: event = self.stripe.Webhook.construct_event( payload, sig_header, webhook_secret ) logger.info(f"Webhook verified: {event['type']}") return event except stripe.error.SignatureVerificationError as e: logger.error(f"Webhook signature verification failed: {str(e)}") raise def handle_webhook_event(self, event: Dict) -> None: """ Route webhook events to appropriate handlers. Args: event: Stripe event object """ event_type = event['type'] event_data = event['data']['object'] handlers = { 'payment_intent.succeeded': self._handle_payment_succeeded, 'payment_intent.payment_failed': self._handle_payment_failed, 'customer.subscription.created': self._handle_subscription_created, 'customer.subscription.updated': self._handle_subscription_updated, 'customer.subscription.deleted': self._handle_subscription_deleted, 'invoice.payment_succeeded': self._handle_invoice_paid, 'invoice.payment_failed': self._handle_invoice_failed, } handler = handlers.get(event_type) if handler: handler(event_data) else: logger.info(f"Unhandled webhook event: {event_type}") # ============= WEBHOOK HANDLERS ============= def _handle_payment_succeeded(self, payment_intent: Dict) -> None: """Handle successful payment intent.""" payment_intent_id = payment_intent['id'] amount = Decimal(payment_intent['amount']) / 100 customer_email = payment_intent.get('receipt_email') metadata = payment_intent.get('metadata', {}) logger.info(f"Payment succeeded: {payment_intent_id}, Amount: ${amount}") # TODO: Update your database # Example: Mark transaction as paid, fulfill order, etc. # Send confirmation email if customer_email: from .email_service import send_payment_confirmation_email send_payment_confirmation_email( email=customer_email, amount=amount, payment_id=payment_intent_id, metadata=metadata ) def _handle_payment_failed(self, payment_intent: Dict) -> None: """Handle failed payment intent.""" payment_intent_id = payment_intent['id'] logger.warning(f"Payment failed: {payment_intent_id}") # TODO: Update your database # Example: Mark transaction as failed, notify customer, etc. def _handle_subscription_created(self, subscription: Dict) -> None: """Handle new subscription creation.""" subscription_id = subscription['id'] customer_id = subscription['customer'] logger.info(f"Subscription created: {subscription_id} for customer {customer_id}") # TODO: Update your database # Example: Store subscription details, activate features, etc. def _handle_subscription_updated(self, subscription: Dict) -> None: """Handle subscription updates.""" subscription_id = subscription['id'] status = subscription['status'] logger.info(f"Subscription updated: {subscription_id}, Status: {status}") # TODO: Update your database # Example: Update subscription status, adjust features, etc. def _handle_subscription_deleted(self, subscription: Dict) -> None: """Handle subscription cancellation.""" subscription_id = subscription['id'] logger.info(f"Subscription deleted: {subscription_id}") # TODO: Update your database # Example: Deactivate features, archive data, etc. def _handle_invoice_paid(self, invoice: Dict) -> None: """Handle successful invoice payment.""" invoice_id = invoice['id'] subscription_id = invoice.get('subscription') customer_email = invoice.get('customer_email') amount_paid = Decimal(invoice['amount_paid']) / 100 logger.info(f"Invoice paid: {invoice_id}, Amount: ${amount_paid}") # Send subscription confirmation email if customer_email: from .email_service import send_subscription_confirmation_email send_subscription_confirmation_email( email=customer_email, amount=amount_paid, invoice_id=invoice_id, subscription_id=subscription_id ) def _handle_invoice_failed(self, invoice: Dict) -> None: """Handle failed invoice payment.""" invoice_id = invoice['id'] subscription_id = invoice.get('subscription') logger.warning(f"Invoice payment failed: {invoice_id}") # TODO: Handle failed payment # Example: Notify customer, retry payment, suspend subscription, etc. # ============= REFUNDS ============= def create_refund( self, payment_intent_id: str, amount: Optional[Decimal] = None, reason: Optional[str] = None ) -> Dict: """ Create a refund for a payment. Args: payment_intent_id: Payment intent ID amount: Amount to refund in dollars (None for full refund) reason: Refund reason ('duplicate', 'fraudulent', 'requested_by_customer') Returns: dict: Refund data """ try: refund_params = {'payment_intent': payment_intent_id} if amount: refund_params['amount'] = int(amount * 100) if reason: refund_params['reason'] = reason refund = self.stripe.Refund.create(**refund_params) logger.info(f"Refund created: {refund.id} for {payment_intent_id}") return { 'refund_id': refund.id, 'amount': Decimal(refund.amount) / 100, 'status': refund.status, } except stripe.error.StripeError as e: logger.error(f"Error creating refund: {str(e)}") raise ``` --- ## Phase 3: Email Notification Service ### 3.1 Email Service ```python # services/email_service.py from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.conf import settings from decimal import Decimal from typing import Dict, Optional import logging logger = logging.getLogger(__name__) def send_payment_confirmation_email( email: str, amount: Decimal, payment_id: str, metadata: Optional[Dict] = None ) -> None: """ Send payment confirmation email for one-time payments. Args: email: Customer email amount: Payment amount payment_id: Stripe payment intent ID metadata: Additional payment metadata """ try: subject = 'Payment Confirmation' from_email = settings.DEFAULT_FROM_EMAIL context = { 'amount': amount, 'payment_id': payment_id, 'metadata': metadata or {}, } html_content = render_to_string('emails/payment_confirmation.html', context) text_content = f"Your payment of ${amount} has been successfully processed. Payment ID: {payment_id}" email_message = EmailMultiAlternatives( subject=subject, body=text_content, from_email=from_email, to=[email], ) email_message.attach_alternative(html_content, "text/html") email_message.send() logger.info(f"Payment confirmation email sent to {email}") except Exception as e: logger.error(f"Failed to send payment confirmation email: {str(e)}") def send_subscription_confirmation_email( email: str, amount: Decimal, invoice_id: str, subscription_id: Optional[str] = None ) -> None: """ Send subscription confirmation email. Args: email: Customer email amount: Subscription amount invoice_id: Stripe invoice ID subscription_id: Stripe subscription ID """ try: subject = 'Subscription Confirmation' from_email = settings.DEFAULT_FROM_EMAIL context = { 'amount': amount, 'invoice_id': invoice_id, 'subscription_id': subscription_id, } html_content = render_to_string('emails/subscription_confirmation.html', context) text_content = f"Your subscription payment of ${amount} has been processed. Invoice ID: {invoice_id}" email_message = EmailMultiAlternatives( subject=subject, body=text_content, from_email=from_email, to=[email], ) email_message.attach_alternative(html_content, "text/html") email_message.send() logger.info(f"Subscription confirmation email sent to {email}") except Exception as e: logger.error(f"Failed to send subscription confirmation email: {str(e)}") ``` ### 3.2 Payment Confirmation Email Template ```html
Thank you! Your payment has been processed successfully.
Welcome! Your subscription is now active and ready to use.