443 lines
13 KiB
Markdown
443 lines
13 KiB
Markdown
---
|
|
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 Session (Hosted)**
|
|
- Stripe-hosted payment page
|
|
- Minimal PCI compliance burden
|
|
- Fastest implementation
|
|
- Supports one-time and recurring payments
|
|
|
|
**Payment Intents (Custom UI)**
|
|
- Full control over payment UI
|
|
- Requires Stripe.js for PCI compliance
|
|
- More complex implementation
|
|
- Better customization options
|
|
|
|
**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(
|
|
payment_method_types=['card'],
|
|
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(
|
|
payment_method_types=['card'],
|
|
line_items=[{
|
|
'price_data': {
|
|
'currency': currency,
|
|
'product_data': {
|
|
'name': 'Purchase',
|
|
'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: Custom Payment Intent Flow
|
|
```python
|
|
def create_payment_intent(amount, currency='usd', customer_id=None):
|
|
"""Create a payment intent for custom checkout UI."""
|
|
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
|
|
|
|
# Frontend (JavaScript)
|
|
"""
|
|
const stripe = Stripe('pk_test_...');
|
|
const elements = stripe.elements();
|
|
const cardElement = elements.create('card');
|
|
cardElement.mount('#card-element');
|
|
|
|
const {error, paymentIntent} = await stripe.confirmCardPayment(
|
|
clientSecret,
|
|
{
|
|
payment_method: {
|
|
card: cardElement,
|
|
billing_details: {
|
|
name: 'Customer Name'
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
// Handle error
|
|
} else if (paymentIntent.status === 'succeeded') {
|
|
// Payment successful
|
|
}
|
|
"""
|
|
```
|
|
|
|
### Pattern 3: 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 4: 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
|
|
|
|
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'),
|
|
}
|
|
)
|
|
```
|
|
|
|
## 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,
|
|
currency='usd',
|
|
customer=customer.id,
|
|
payment_method_types=['card']
|
|
)
|
|
|
|
# Confirm with test card
|
|
confirmed = stripe.PaymentIntent.confirm(
|
|
intent.id,
|
|
payment_method='pm_card_visa' # Test payment method
|
|
)
|
|
|
|
assert confirmed.status == 'succeeded'
|
|
```
|
|
|
|
## Resources
|
|
|
|
- **references/checkout-flows.md**: Detailed checkout implementation
|
|
- **references/webhook-handling.md**: Webhook security and processing
|
|
- **references/subscription-management.md**: Subscription lifecycle
|
|
- **references/customer-management.md**: Customer and payment method handling
|
|
- **references/invoice-generation.md**: Invoicing and billing
|
|
- **assets/stripe-client.py**: Production-ready Stripe client wrapper
|
|
- **assets/webhook-handler.py**: Complete webhook processor
|
|
- **assets/checkout-config.json**: Checkout configuration templates
|
|
|
|
## Best Practices
|
|
|
|
1. **Always Use Webhooks**: Don't rely solely on client-side confirmation
|
|
2. **Idempotency**: Handle webhook events idempotently
|
|
3. **Error Handling**: Gracefully handle all Stripe errors
|
|
4. **Test Mode**: Thoroughly test with test keys before production
|
|
5. **Metadata**: Use metadata to link Stripe objects to your database
|
|
6. **Monitoring**: Track payment success rates and errors
|
|
7. **PCI Compliance**: Never handle raw card data on your server
|
|
8. **SCA Ready**: Implement 3D Secure for European payments
|
|
|
|
## Common Pitfalls
|
|
|
|
- **Not Verifying Webhooks**: Always verify webhook signatures
|
|
- **Missing Webhook Events**: Handle all relevant webhook events
|
|
- **Hardcoded Amounts**: Use cents/smallest currency unit
|
|
- **No Retry Logic**: Implement retries for API calls
|
|
- **Ignoring Test Mode**: Test all edge cases with test cards
|