Initial commit
This commit is contained in:
442
skills/stripe-integration/SKILL.md
Normal file
442
skills/stripe-integration/SKILL.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user