stripe-integration ยท diff
git:20260307.47a5dbc to git:20260522.be57c0b
2 added, 348 removed. Audit A to A.
---
name: stripe-integration
description: Implement Stripe payment processing for robust, PCI-compliant payment flows including checkout, subscriptions, and webhooks. Use when integrating Stripe payments, building subscription systems, or implementing secure checkout flows.
---
# Stripe Integration
Master Stripe payment processing integration for robust, PCI-compliant payment flows including checkout, subscriptions, webhooks, and refunds.
## When to Use This Skill
- Implementing payment processing in web/mobile applications
- Setting up subscription billing systems
- Handling one-time payments and recurring charges
- Processing refunds and disputes
- Managing customer payment methods
- Implementing SCA (Strong Customer Authentication) for European payments
- Building marketplace payment flows with Stripe Connect
## Core Concepts
### 1. Payment Flows
**Checkout Sessions**
- Recommended for most integrations
- Supports all UI paths:
- Stripe-hosted checkout page
- Embedded checkout form
- Custom UI with Elements (Payment Element, Express Checkout Element) using `ui_mode='custom'`
- Provides built-in checkout capabilities (line items, discounts, tax, shipping, address collection, saved payment methods, and checkout lifecycle events)
- Lower integration and maintenance burden than Payment Intents
**Payment Intents (Bespoke control)**
- You calculate the final amount with taxes, discounts, subscriptions, and currency conversion yourself.
- More complex implementation and long-term maintenance burden
- Requires Stripe.js for PCI compliance
**Setup Intents (Save Payment Methods)**
- Collect payment method without charging
- Used for subscriptions and future payments
- Requires customer confirmation
### 2. Webhooks
**Critical Events:**
- `payment_intent.succeeded`: Payment completed
- `payment_intent.payment_failed`: Payment failed
- `customer.subscription.updated`: Subscription changed
- `customer.subscription.deleted`: Subscription canceled
- `charge.refunded`: Refund processed
- `invoice.payment_succeeded`: Subscription payment successful
### 3. Subscriptions
**Components:**
- **Product**: What you're selling
- **Price**: How much and how often
- **Subscription**: Customer's recurring payment
- **Invoice**: Generated for each billing cycle
### 4. Customer Management
- Create and manage customer records
- Store multiple payment methods
- Track customer metadata
- Manage billing details
## Quick Start
```python
import stripe
stripe.api_key = "sk_test_..."
# Create a checkout session
session = stripe.checkout.Session.create(
line_items=[{
'price_data': {
'currency': 'usd',
'product_data': {
'name': 'Premium Subscription',
},
'unit_amount': 2000, # $20.00
'recurring': {
'interval': 'month',
},
},
'quantity': 1,
}],
mode='subscription',
success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url='https://yourdomain.com/cancel'
)
# Redirect user to session.url
print(session.url)
```
- ## Payment Implementation Patterns
-
- ### Pattern 1: One-Time Payment (Hosted Checkout)
-
- ```python
- def create_checkout_session(amount, currency='usd'):
- """Create a one-time payment checkout session."""
- try:
- session = stripe.checkout.Session.create(
- line_items=[{
- 'price_data': {
- 'currency': currency,
- 'product_data': {
- 'name': 'Blue T-shirt',
- 'images': ['https://example.com/product.jpg'],
- },
- 'unit_amount': amount, # Amount in cents
- },
- 'quantity': 1,
- }],
- mode='payment',
- success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
- cancel_url='https://yourdomain.com/cancel',
- metadata={
- 'order_id': 'order_123',
- 'user_id': 'user_456'
- }
- )
- return session
- except stripe.error.StripeError as e:
- # Handle error
- print(f"Stripe error: {e.user_message}")
- raise
- ```
-
- ### Pattern 2: Elements with Checkout Sessions
-
- ```python
- def create_checkout_session_for_elements(amount, currency='usd'):
- """Create a checkout session configured for Payment Element."""
- session = stripe.checkout.Session.create(
- mode='payment',
- ui_mode='custom',
- line_items=[{
- 'price_data': {
- 'currency': currency,
- 'product_data': {'name': 'Blue T-shirt'},
- 'unit_amount': amount,
- },
- 'quantity': 1,
- }],
- return_url='https://yourdomain.com/complete?session_id={CHECKOUT_SESSION_ID}'
- )
- return session.client_secret # Send to frontend
- ```
-
- ```javascript
- const stripe = Stripe("pk_test_...");
- const appearance = { theme: "stripe" };
-
- const checkout = stripe.initCheckout({
- clientSecret,
- elementsOptions: { appearance },
- });
- const loadActionsResult = await checkout.loadActions();
-
- if (loadActionsResult.type === "success") {
- const { actions } = loadActionsResult;
- const session = actions.getSession();
-
- const button = document.getElementById("pay-button");
- const checkoutContainer = document.getElementById("checkout-container");
- const emailInput = document.getElementById("email");
- const emailErrors = document.getElementById("email-errors");
- const errors = document.getElementById("confirm-errors");
-
- // Display a formatted string representing the total amount
- checkoutContainer.append(`Total: ${session.total.total.amount}`);
-
- // Mount Payment Element
- const paymentElement = checkout.createPaymentElement();
- paymentElement.mount("#payment-element");
-
- // Store email for submission
- emailInput.addEventListener("blur", () => {
- actions.updateEmail(emailInput.value).then((result) => {
- if (result.error) emailErrors.textContent = result.error.message;
- });
- });
-
- // Handle form submission
- button.addEventListener("click", () => {
- actions.confirm().then((result) => {
- if (result.type === "error") errors.textContent = result.error.message;
- });
- });
- }
- ```
-
- ### Pattern 3: Elements with Payment Intents
-
- Pattern 2 (Elements with Checkout Sessions) is Stripe's recommended approach, but you can also use Payment Intents as an alternative.
-
- ```python
- def create_payment_intent(amount, currency='usd', customer_id=None):
- """Create a payment intent for bespoke checkout UI with Payment Element."""
- intent = stripe.PaymentIntent.create(
- amount=amount,
- currency=currency,
- customer=customer_id,
- automatic_payment_methods={
- 'enabled': True,
- },
- metadata={
- 'integration_check': 'accept_a_payment'
- }
- )
- return intent.client_secret # Send to frontend
- ```
-
- ```javascript
- // Mount Payment Element and confirm via Payment Intents
- const stripe = Stripe("pk_test_...");
- const appearance = { theme: "stripe" };
- const elements = stripe.elements({ appearance, clientSecret });
-
- const paymentElement = elements.create("payment");
- paymentElement.mount("#payment-element");
-
- document.getElementById("pay-button").addEventListener("click", async () => {
- const { error } = await stripe.confirmPayment({
- elements,
- confirmParams: {
- return_url: "https://yourdomain.com/complete",
- },
- });
-
- if (error) {
- document.getElementById("errors").textContent = error.message;
- }
- });
- ```
-
- ### Pattern 4: Subscription Creation
-
- ```python
- def create_subscription(customer_id, price_id):
- """Create a subscription for a customer."""
- try:
- subscription = stripe.Subscription.create(
- customer=customer_id,
- items=[{'price': price_id}],
- payment_behavior='default_incomplete',
- payment_settings={'save_default_payment_method': 'on_subscription'},
- expand=['latest_invoice.payment_intent'],
- )
-
- return {
- 'subscription_id': subscription.id,
- 'client_secret': subscription.latest_invoice.payment_intent.client_secret
- }
- except stripe.error.StripeError as e:
- print(f"Subscription creation failed: {e}")
- raise
- ```
-
- ### Pattern 5: Customer Portal
-
- ```python
- def create_customer_portal_session(customer_id):
- """Create a portal session for customers to manage subscriptions."""
- session = stripe.billing_portal.Session.create(
- customer=customer_id,
- return_url='https://yourdomain.com/account',
- )
- return session.url # Redirect customer here
- ```
-
- ## Webhook Handling
-
- ### Secure Webhook Endpoint
-
- ```python
- from flask import Flask, request
- import stripe
-
- app = Flask(__name__)
-
- endpoint_secret = 'whsec_...'
-
- @app.route('/webhook', methods=['POST'])
- def webhook():
- payload = request.data
- sig_header = request.headers.get('Stripe-Signature')
-
- try:
- event = stripe.Webhook.construct_event(
- payload, sig_header, endpoint_secret
- )
- except ValueError:
- # Invalid payload
- return 'Invalid payload', 400
- except stripe.error.SignatureVerificationError:
- # Invalid signature
- return 'Invalid signature', 400
-
- # Handle the event
- if event['type'] == 'payment_intent.succeeded':
- payment_intent = event['data']['object']
- handle_successful_payment(payment_intent)
- elif event['type'] == 'payment_intent.payment_failed':
- payment_intent = event['data']['object']
- handle_failed_payment(payment_intent)
- elif event['type'] == 'customer.subscription.deleted':
- subscription = event['data']['object']
- handle_subscription_canceled(subscription)
-
- return 'Success', 200
-
- def handle_successful_payment(payment_intent):
- """Process successful payment."""
- customer_id = payment_intent.get('customer')
- amount = payment_intent['amount']
- metadata = payment_intent.get('metadata', {})
-
- # Update your database
- # Send confirmation email
- # Fulfill order
- print(f"Payment succeeded: {payment_intent['id']}")
-
- def handle_failed_payment(payment_intent):
- """Handle failed payment."""
- error = payment_intent.get('last_payment_error', {})
- print(f"Payment failed: {error.get('message')}")
- # Notify customer
- # Update order status
-
- def handle_subscription_canceled(subscription):
- """Handle subscription cancellation."""
- customer_id = subscription['customer']
- # Update user access
- # Send cancellation email
- print(f"Subscription canceled: {subscription['id']}")
- ```
-
- ### Webhook Best Practices
-
- ```python
- import hashlib
- import hmac
-
- def verify_webhook_signature(payload, signature, secret):
- """Manually verify webhook signature."""
- expected_sig = hmac.new(
- secret.encode('utf-8'),
- payload,
- hashlib.sha256
- ).hexdigest()
-
- return hmac.compare_digest(signature, expected_sig)
-
- def handle_webhook_idempotently(event_id, handler):
- """Ensure webhook is processed exactly once."""
- # Check if event already processed
- if is_event_processed(event_id):
- return
-
- # Process event
- try:
- handler()
- mark_event_processed(event_id)
- except Exception as e:
- log_error(e)
- # Stripe will retry failed webhooks
- raise
- ```
-
- ## Customer Management
-
- ```python
- def create_customer(email, name, payment_method_id=None):
- """Create a Stripe customer."""
- customer = stripe.Customer.create(
- email=email,
- name=name,
- payment_method=payment_method_id,
- invoice_settings={
- 'default_payment_method': payment_method_id
- } if payment_method_id else None,
- metadata={
- 'user_id': '12345'
- }
- )
- return customer
-
- def attach_payment_method(customer_id, payment_method_id):
- """Attach a payment method to a customer."""
- stripe.PaymentMethod.attach(
- payment_method_id,
- customer=customer_id
- )
-
- # Set as default
- stripe.Customer.modify(
- customer_id,
- invoice_settings={
- 'default_payment_method': payment_method_id
- }
- )
-
- def list_customer_payment_methods(customer_id):
- """List all payment methods for a customer."""
- payment_methods = stripe.PaymentMethod.list(
- customer=customer_id,
- type='card'
- )
- return payment_methods.data
- ```
-
- ## Refund Handling
-
- ```python
- def create_refund(payment_intent_id, amount=None, reason=None):
- """Create a refund."""
- refund_params = {
- 'payment_intent': payment_intent_id
- }
-
- if amount:
- refund_params['amount'] = amount # Partial refund
-
- if reason:
- refund_params['reason'] = reason # 'duplicate', 'fraudulent', 'requested_by_customer'
-
- refund = stripe.Refund.create(**refund_params)
- return refund
+ ## Detailed patterns and worked examples
- def handle_dispute(charge_id, evidence):
- """Update dispute with evidence."""
- stripe.Dispute.modify(
- charge_id,
- evidence={
- 'customer_name': evidence.get('customer_name'),
- 'customer_email_address': evidence.get('customer_email'),
- 'shipping_documentation': evidence.get('shipping_proof'),
- 'customer_communication': evidence.get('communication'),
- }
- )
- ```
+ Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
## Testing
```python
# Use test mode keys
stripe.api_key = "sk_test_..."
# Test card numbers
TEST_CARDS = {
'success': '4242424242424242',
'declined': '4000000000000002',
'3d_secure': '4000002500003155',
'insufficient_funds': '4000000000009995'
}
def test_payment_flow():
"""Test complete payment flow."""
# Create test customer
customer = stripe.Customer.create(
email="test@example.com"
)
# Create payment intent
intent = stripe.PaymentIntent.create(
amount=1000,
automatic_payment_methods={
'enabled': True
},
currency='usd',
customer=customer.id
)
# Confirm with test card
confirmed = stripe.PaymentIntent.confirm(
intent.id,
payment_method='pm_card_visa' # Test payment method
)
assert confirmed.status == 'succeeded'
```