| name | stripe-agent |
| description | Comprehensive Stripe integration agent for payments, subscriptions, billing, and marketplace management. Use when Claude needs to work with Stripe API for creating customers, managing subscriptions, processing payments, handling checkout sessions, setting up products/prices, managing webhooks, Connect marketplaces, metered billing, tax calculation, fraud prevention, or any payment-related task. Triggers on mentions of Stripe, payments, subscriptions, billing, checkout, invoices, payment intents, recurring payments, Connect, marketplace, SCA, 3D Secure, or disputes. |
Stripe Agent
This skill enables Claude to interact with Stripe's API for complete payment and subscription management.
Prerequisites
Ensure STRIPE_SECRET_KEY environment variable is set. For webhook handling, also set STRIPE_WEBHOOK_SECRET.
export STRIPE_SECRET_KEY="sk_test_..."
export STRIPE_WEBHOOK_SECRET="whsec_..."
Install the Stripe SDK:
pip install stripe --break-system-packages
Core Workflows
1. Customer Management
Create and manage customers before any payment operation.
import stripe
import os
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
# Create customer
customer = stripe.Customer.create(
email="user@example.com",
name="John Doe",
metadata={"user_id": "your_app_user_id"}
)
# Retrieve customer
customer = stripe.Customer.retrieve("cus_xxx")
# Update customer
stripe.Customer.modify("cus_xxx", metadata={"plan": "premium"})
# List customers
customers = stripe.Customer.list(limit=10, email="user@example.com")
2. Products and Prices
Always create Products first, then attach Prices. Use lookup_key for easy price retrieval.
# Create product
product = stripe.Product.create(
name="Pro Plan",
description="Full access to all features",
metadata={"tier": "pro"}
)
# Create recurring price (subscription)
price = stripe.Price.create(
product=product.id,
unit_amount=1999, # Amount in cents (€19.99)
currency="eur",
recurring={"interval": "month"},
lookup_key="pro_monthly"
)
# Create one-time price
one_time_price = stripe.Price.create(
product=product.id,
unit_amount=9999,
currency="eur",
lookup_key="pro_lifetime"
)
# Retrieve price by lookup_key
prices = stripe.Price.list(lookup_keys=["pro_monthly"])
3. Checkout Sessions (Recommended for Web)
Use Checkout Sessions for secure, hosted payment pages.
# Subscription checkout
session = stripe.checkout.Session.create(
customer="cus_xxx", # Optional: attach to existing customer
mode="subscription",
line_items=[{
"price": "price_xxx",
"quantity": 1
}],
success_url="https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url="https://yourapp.com/cancel",
metadata={"user_id": "123"}
)
# Redirect user to: session.url
# One-time payment checkout
session = stripe.checkout.Session.create(
mode="payment",
line_items=[{"price": "price_xxx", "quantity": 1}],
success_url="https://yourapp.com/success",
cancel_url="https://yourapp.com/cancel"
)
4. Subscription Management
# Create subscription directly (when you have payment method)
subscription = stripe.Subscription.create(
customer="cus_xxx",
items=[{"price": "price_xxx"}],
payment_behavior="default_incomplete",
expand=["latest_invoice.payment_intent"]
)
# Retrieve subscription
sub = stripe.Subscription.retrieve("sub_xxx")
# Update subscription (change plan)
stripe.Subscription.modify(
"sub_xxx",
items=[{
"id": sub["items"]["data"][0].id,
"price": "price_new_xxx"
}],
proration_behavior="create_prorations"
)
# Cancel subscription
stripe.Subscription.cancel("sub_xxx") # Immediate
# Or cancel at period end:
stripe.Subscription.modify("sub_xxx", cancel_at_period_end=True)
5. Payment Intents (Custom Integration)
Use when you need full control over the payment flow.
# Create payment intent
intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
customer="cus_xxx",
metadata={"order_id": "order_123"}
)
# Return intent.client_secret to frontend
# Confirm payment (server-side)
stripe.PaymentIntent.confirm(
"pi_xxx",
payment_method="pm_xxx"
)
6. Webhook Handling
Critical for subscription lifecycle. See scripts/webhook_handler.py for complete implementation.
Key events to handle:
checkout.session.completed- Payment successfulcustomer.subscription.created- New subscriptioncustomer.subscription.updated- Plan changescustomer.subscription.deleted- Cancellationinvoice.paid- Successful renewalinvoice.payment_failed- Failed payment
import stripe
def handle_webhook(payload, sig_header):
endpoint_secret = os.environ.get("STRIPE_WEBHOOK_SECRET")
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Fulfill order, activate subscription
elif event["type"] == "invoice.payment_failed":
invoice = event["data"]["object"]
# Notify user, handle dunning
return {"status": "success"}
Firebase Integration Pattern
For Firebase + Stripe integration, see references/firebase-integration.md.
Quick setup:
- Store Stripe customer_id in Firestore user document
- Sync subscription status via webhooks to Firestore
- Use Firebase Security Rules to check subscription status
Common Operations Quick Reference
| Task | Method |
|---|---|
| Create customer | stripe.Customer.create() |
| Start subscription | stripe.checkout.Session.create(mode="subscription") |
| Cancel subscription | stripe.Subscription.cancel() |
| Change plan | stripe.Subscription.modify() |
| Refund payment | stripe.Refund.create(payment_intent="pi_xxx") |
| Get invoices | stripe.Invoice.list(customer="cus_xxx") |
| Create portal session | stripe.billing_portal.Session.create() |
Customer Portal (Self-Service)
Let customers manage their own subscriptions:
portal_session = stripe.billing_portal.Session.create(
customer="cus_xxx",
return_url="https://yourapp.com/account"
)
# Redirect to: portal_session.url
Testing
Use test mode keys (sk_test_...) and test card numbers:
4242424242424242- Successful payment4000000000000002- Declined4000002500003155- Requires 3D Secure
Error Handling
try:
# Stripe operation
except stripe.error.CardError as e:
# Card declined
print(f"Card error: {e.user_message}")
except stripe.error.InvalidRequestError as e:
# Invalid parameters
print(f"Invalid request: {e}")
except stripe.error.AuthenticationError:
# Invalid API key
pass
except stripe.error.StripeError as e:
# Generic Stripe error
pass
Payment Links (No-Code Payments)
Create shareable payment links without code:
# Create a payment link
payment_link = stripe.PaymentLink.create(
line_items=[{"price": "price_xxx", "quantity": 1}],
after_completion={"type": "redirect", "redirect": {"url": "https://yourapp.com/thanks"}}
)
# Share: payment_link.url
# Create reusable link with adjustable quantity
payment_link = stripe.PaymentLink.create(
line_items=[{"price": "price_xxx", "adjustable_quantity": {"enabled": True, "minimum": 1, "maximum": 10}}]
)
Metered & Usage-Based Billing
For API calls, seats, or consumption-based pricing:
# Create metered price
metered_price = stripe.Price.create(
product="prod_xxx",
currency="eur",
recurring={"interval": "month", "usage_type": "metered"},
billing_scheme="per_unit",
unit_amount=10, # €0.10 per unit
lookup_key="api_calls"
)
# Report usage (do this periodically)
stripe.SubscriptionItem.create_usage_record(
"si_xxx", # subscription item id
quantity=150,
timestamp=int(datetime.now().timestamp()),
action="increment" # or "set" to override
)
# Tiered pricing
tiered_price = stripe.Price.create(
product="prod_xxx",
currency="eur",
recurring={"interval": "month", "usage_type": "metered"},
billing_scheme="tiered",
tiers_mode="graduated", # or "volume"
tiers=[
{"up_to": 100, "unit_amount": 50}, # First 100: €0.50 each
{"up_to": 1000, "unit_amount": 30}, # 101-1000: €0.30 each
{"up_to": "inf", "unit_amount": 10} # 1001+: €0.10 each
]
)
Stripe Connect (Marketplaces)
Build platforms where you facilitate payments between buyers and sellers:
# Create connected account (Express - recommended)
account = stripe.Account.create(
type="express",
country="US",
email="seller@example.com",
capabilities={"card_payments": {"requested": True}, "transfers": {"requested": True}}
)
# Generate onboarding link
account_link = stripe.AccountLink.create(
account=account.id,
refresh_url="https://yourapp.com/reauth",
return_url="https://yourapp.com/return",
type="account_onboarding"
)
# Redirect seller to: account_link.url
# Create payment with platform fee (destination charge)
payment_intent = stripe.PaymentIntent.create(
amount=10000,
currency="eur",
application_fee_amount=1000, # Platform takes €10
transfer_data={"destination": "acct_xxx"} # Seller receives €90
)
# Direct charge (charge on connected account)
payment_intent = stripe.PaymentIntent.create(
amount=10000,
currency="eur",
stripe_account="acct_xxx", # Charge on seller's account
application_fee_amount=1000
)
# Transfer funds to connected account
transfer = stripe.Transfer.create(
amount=5000,
currency="eur",
destination="acct_xxx"
)
Tax Calculation (Stripe Tax)
Automatic tax calculation and collection:
# Enable automatic tax in checkout
session = stripe.checkout.Session.create(
mode="payment",
line_items=[{"price": "price_xxx", "quantity": 1}],
automatic_tax={"enabled": True},
success_url="https://yourapp.com/success",
cancel_url="https://yourapp.com/cancel"
)
# Calculate tax for payment intent
payment_intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
automatic_payment_methods={"enabled": True},
# Tax calculated based on customer location
)
# Tax calculation API (preview)
calculation = stripe.tax.Calculation.create(
currency="eur",
line_items=[{"amount": 1000, "reference": "L1"}],
customer_details={"address": {"country": "DE"}, "address_source": "billing"}
)
3D Secure & SCA Compliance
Handle Strong Customer Authentication (required in EU/UK):
# Payment intent with 3DS when required
payment_intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
payment_method="pm_xxx",
confirmation_method="manual",
confirm=True,
return_url="https://yourapp.com/return" # For 3DS redirect
)
# Check if authentication required
if payment_intent.status == "requires_action":
# Redirect customer to: payment_intent.next_action.redirect_to_url.url
pass
# Force 3DS (for high-risk transactions)
payment_intent = stripe.PaymentIntent.create(
amount=50000,
currency="eur",
payment_method_options={
"card": {"request_three_d_secure": "any"} # or "automatic"
}
)
# Webhook: handle authentication
# Event: payment_intent.requires_action
Test cards for 3DS:
4000002500003155- Requires authentication4000002760003184- Always authenticates4000008260003178- Authentication fails
Fraud Prevention (Stripe Radar)
Built-in fraud protection with Radar:
# Payment with Radar rules
payment_intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
payment_method="pm_xxx",
# Radar evaluates automatically
)
# Check radar outcome after payment
charge = stripe.Charge.retrieve("ch_xxx")
radar_outcome = charge.outcome
# radar_outcome.risk_level: "normal", "elevated", "highest"
# radar_outcome.risk_score: 0-100
# Custom metadata for Radar rules
payment_intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
metadata={
"customer_account_age": "30", # days
"order_count": "5"
}
)
# Block high-risk in Radar Dashboard:
# Rule: "Block if :risk_level: = 'highest'"
# Rule: "Review if ::customer_account_age:: < 7"
Dispute Handling
Manage chargebacks and disputes:
# List disputes
disputes = stripe.Dispute.list(limit=10)
# Retrieve dispute details
dispute = stripe.Dispute.retrieve("dp_xxx")
# dispute.reason: "fraudulent", "duplicate", "product_not_received", etc.
# dispute.status: "needs_response", "under_review", "won", "lost"
# Submit evidence
stripe.Dispute.modify(
"dp_xxx",
evidence={
"customer_name": "John Doe",
"customer_email_address": "john@example.com",
"shipping_tracking_number": "1Z999AA10123456784",
"uncategorized_text": "Customer confirmed receipt via email on..."
},
submit=True # Submit evidence
)
# Webhook events for disputes
# charge.dispute.created - New dispute opened
# charge.dispute.updated - Evidence submitted or status changed
# charge.dispute.closed - Dispute resolved
Idempotency & Best Practices
Prevent duplicate operations:
import uuid
# Idempotent request (safe to retry)
payment_intent = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
idempotency_key=f"order_{order_id}" # Unique per operation
)
# For retries, use same key
try:
payment = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
idempotency_key="order_123"
)
except stripe.error.StripeError:
# Safe to retry with same idempotency_key
payment = stripe.PaymentIntent.create(
amount=2000,
currency="eur",
idempotency_key="order_123"
)
# Generate unique keys
def idempotency_key(prefix: str) -> str:
return f"{prefix}_{uuid.uuid4().hex}"
Best Practices:
- Always use idempotency keys for create/update operations
- Store payment intent ID before confirming
- Use webhooks as source of truth (not API responses)
- Handle
requires_actionstatus for 3DS - Never log full card numbers or CVV
- Use test mode for development (
sk_test_...)
Scripts Reference
scripts/setup_products.py- Create products and pricesscripts/webhook_handler.py- Flask webhook endpointscripts/sync_subscriptions.py- Sync subscriptions to databasescripts/stripe_utils.py- Common utility functions
Additional Resources
references/firebase-integration.md- Firebase + Firestore integrationreferences/api-cheatsheet.md- Quick API reference