Integrate Stripe payments into any application. Framework-agnostic patterns for Node.js, Python, and other backends. Use this skill when: (1) Setting up Stripe Checkout, Payment Intents, or embedded payment forms (2) Implementing subscription billing with plans, trials, upgrades, or downgrades (3) Building usage-based or metered billing (pay-per-use, credit/token systems) (4) Handling Stripe webhooks and event processing (5) Setting up Stripe Customer Portal for self-service billing (6) Managing invoices, refunds, or payment disputes (7) Implementing a credit/token purchase and consumption system (8) Configuring Stripe API keys, webhook secrets, or test mode (9) Any task involving Stripe payment processing, billing, or subscription management
What payment flow do you need?
├── Redirect to Stripe-hosted page → Checkout Sessions
├── Embedded form in your UI → Payment Intents + Stripe Elements
├── Recurring billing → Subscriptions (via Checkout or API)
├── Pay-per-use / metered → Usage-Based Billing
├── Credit/token system → Credits System (custom + Stripe)
└── Self-service billing management → Customer Portal
# Environment variables (NEVER hardcode)
STRIPE_SECRET_KEY=sk_test_... # Server-side only
STRIPE_PUBLISHABLE_KEY=pk_test_... # Client-side safe
STRIPE_WEBHOOK_SECRET=whsec_... # Webhook signature verification
sk_): Server-side only. Never expose to client.pk_): Safe for client-side. Used in Stripe.js/Elements.whsec_): Verify webhook signatures. Per-endpoint.| Object | Purpose | Created Via |
|---|---|---|
Customer | Represents a user. Stores payment methods, subscriptions. | stripe.customers.create() |
Product | What you sell (e.g., "Pro Plan"). | Dashboard or API |
Price | How much and how often (e.g., $20/month). | Dashboard or API |
Checkout Session | Hosted payment page. | stripe.checkout.sessions.create() |
PaymentIntent | Tracks a single payment lifecycle. | stripe.paymentIntents.create() |
Subscription | Recurring billing relationship. | stripe.subscriptions.create() |
Invoice | Bill for subscription period or one-off. | Auto-generated or API |
Webhook Event | Async notification of state changes. | Stripe sends to your endpoint |
Customer
├── PaymentMethod (card, bank, etc.)
├── Subscription
│ ├── Price → Product
│ ├── Invoice (per billing period)
│ │ └── PaymentIntent (charge attempt)
│ └── Usage Records (if metered)
└── Checkout Session (creates Subscription or PaymentIntent)
STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET)stripe for Python, stripe npm package for Node.js)Always create a Stripe Customer for each user. Store stripe_customer_id in your database:
# Python
customer = stripe.Customer.create(
email=user.email,
metadata={"user_id": str(user.id)}
)
# Save customer.id to your database
// Node.js
const customer = await stripe.customers.create({
email: user.email,
metadata: { user_id: user.id },
});
// Save customer.id to your database
| Need | Reference |
|---|---|
| Accept payments (one-time or first subscription) | checkout-and-payments.md |
| Recurring billing with plan management | subscriptions.md |
| Handle Stripe events server-side | webhooks.md |
| Metered billing or credit/token systems | usage-and-credits.md |
| Let users manage their own billing | customer-portal.md |
| Security, testing, and error handling | security-and-testing.md |
Webhooks are not optional. Stripe is async - payments confirm, subscriptions renew, and cards fail asynchronously. Always implement webhook handling. See webhooks.md.
-- Users table (add these columns)
stripe_customer_id TEXT UNIQUE
stripe_subscription_id TEXT
subscription_status TEXT -- 'active', 'past_due', 'canceled', etc.
plan_tier TEXT -- 'free', 'basic', 'pro'
credits_balance INTEGER DEFAULT 0
# Python pseudocode
def require_active_subscription(user):
if user.subscription_status not in ("active", "trialing"):
raise HTTPException(403, "Active subscription required")
// Node.js pseudocode
function requireSubscription(req, res, next) {
if (!["active", "trialing"].includes(req.user.subscriptionStatus)) {
return res.status(403).json({ error: "Active subscription required" });
}
next();
}
Always use idempotency keys for create operations to prevent duplicate charges:
stripe.PaymentIntent.create(
amount=2000,
currency="usd",
idempotency_key=f"payment_{order_id}"
)