Stripe Integration Dynamic
Streamline your workflow with this paid, payments, subscriptions, billing. Includes structured workflows, validation checks, and reusable patterns for development.
Stripe Integration Dynamic
A comprehensive skill for integrating Stripe payment processing into web applications. Covers checkout flows, subscriptions, webhooks, customer management, and production-ready payment patterns with proper error handling and security.
When to Use This Skill
Choose this skill when:
- Implementing one-time payments with Stripe Checkout or Payment Intents
- Building subscription billing with trials, upgrades, and cancellations
- Setting up webhook handlers for payment events and lifecycle management
- Managing Stripe customers, payment methods, and invoices
- Handling edge cases: declined cards, disputes, refunds, and currency
Consider alternatives when:
- Integrating a different payment provider → use that provider's skill
- Building a general e-commerce platform → use an e-commerce skill
- Working on financial reporting → use an analytics skill
- Need PCI compliance guidance → use a security compliance skill
Quick Start
npm install stripe @stripe/stripe-js @stripe/react-stripe-js
// Backend: Create checkout session import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); app.post('/api/create-checkout', async (req, res) => { const { priceId, customerId } = req.body; const session = await stripe.checkout.sessions.create({ customer: customerId, mode: 'subscription', line_items: [{ price: priceId, quantity: 1 }], success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.APP_URL}/pricing`, subscription_data: { trial_period_days: 14, metadata: { userId: req.user.id }, }, }); res.json({ url: session.url }); });
Core Concepts
Stripe Payment Flow Architecture
| Flow | Use Case | Complexity |
|---|---|---|
| Checkout Session | One-time or subscription, hosted page | Low |
| Payment Intent | Custom UI, full control | Medium |
| Setup Intent | Save payment method for later | Medium |
| Invoice | Recurring billing, metered usage | Medium-High |
| Connect | Marketplace, split payments | High |
Webhook Handler Pattern
// Production-ready webhook handler import { buffer } from 'micro'; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!; app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature']!; let event: Stripe.Event; try { event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); } catch (err: any) { console.error('Webhook signature failed:', err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } // Idempotent processing — check if already handled const processed = await db.processedEvents.findUnique({ where: { stripeEventId: event.id }, }); if (processed) return res.json({ received: true }); try { switch (event.type) { case 'checkout.session.completed': { const session = event.data.object as Stripe.Checkout.Session; await handleCheckoutComplete(session); break; } case 'invoice.payment_succeeded': { const invoice = event.data.object as Stripe.Invoice; await handlePaymentSuccess(invoice); break; } case 'invoice.payment_failed': { const invoice = event.data.object as Stripe.Invoice; await handlePaymentFailure(invoice); break; } case 'customer.subscription.deleted': { const sub = event.data.object as Stripe.Subscription; await handleSubscriptionCanceled(sub); break; } } await db.processedEvents.create({ data: { stripeEventId: event.id } }); } catch (err) { console.error('Webhook handler error:', err); return res.status(500).json({ error: 'Handler failed' }); } res.json({ received: true }); });
Subscription Management
// Subscription lifecycle operations class SubscriptionService { async upgrade(subscriptionId: string, newPriceId: string) { const sub = await stripe.subscriptions.retrieve(subscriptionId); return stripe.subscriptions.update(subscriptionId, { items: [{ id: sub.items.data[0].id, price: newPriceId, }], proration_behavior: 'create_prorations', }); } async cancel(subscriptionId: string, immediate = false) { if (immediate) { return stripe.subscriptions.cancel(subscriptionId); } return stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true, }); } async reactivate(subscriptionId: string) { return stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: false, }); } async applyDiscount(subscriptionId: string, couponId: string) { return stripe.subscriptions.update(subscriptionId, { coupon: couponId, }); } }
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
paymentMode | string | 'checkout' | Mode: checkout (hosted), payment-intent (custom) |
subscriptionProration | string | 'create_prorations' | Proration on plan changes |
trialDays | number | 14 | Default trial period for subscriptions |
webhookTolerance | number | 300 | Webhook signature tolerance (seconds) |
currency | string | 'usd' | Default currency for payments |
idempotencyEnabled | boolean | true | Deduplicate webhook events |
Best Practices
-
Always verify webhook signatures before processing — Never trust incoming webhook data without verifying the Stripe signature. Use
stripe.webhooks.constructEvent()with your endpoint secret to validate authenticity. -
Process webhooks idempotently — Stripe retries webhooks on failure and may send the same event multiple times. Store processed event IDs and skip duplicates. Return 200 immediately after signature verification, then process asynchronously.
-
Use Stripe Checkout for the simplest integration — Checkout handles PCI compliance, payment method collection, and SCA (Strong Customer Authentication) for you. Only use Payment Intents when you need a fully custom UI.
-
Store Stripe IDs alongside your user records — Save
customerId,subscriptionId, andpriceIdin your database. This enables direct lookups without searching Stripe, and provides resilience if webhook delivery is delayed. -
Handle all subscription lifecycle events — Don't just handle
checkout.session.completed. Also handleinvoice.payment_failed(dunning),customer.subscription.deleted(cancellation), andcustomer.subscription.updated(plan changes) for a complete implementation.
Common Issues
Webhook endpoint returns 400 on valid events — The raw request body must be passed to constructEvent(), not a parsed JSON body. If Express's JSON middleware parses the body before the webhook handler, the signature verification fails. Use express.raw() for the webhook route.
Subscription created but user not granted access — Relying only on the checkout redirect for provisioning is fragile (user can close the tab). Always provision access via the checkout.session.completed webhook, which is reliable and retried by Stripe.
Duplicate charges from webhook retries — If your webhook handler throws an error after charging the user but before returning 200, Stripe retries and you process the event again. Make all webhook handlers idempotent by checking event IDs before processing.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.