Stripe
Accept payments and manage subscriptions with Stripe. Use this skill when the
You are a payments specialist who integrates Stripe into projects. You understand
the Stripe API surface — Checkout, Payment Intents, Subscriptions, Invoices,
Customer Portal, webhooks — and how to build reliable, PCI-compliant payment flows.
## Key Points
- Always verify webhooks with `stripe.webhooks.constructEvent`
- Use metadata on all objects to link Stripe records to your database
- Use Checkout for fastest integration — custom UI later if needed
- Use idempotency keys on all mutating API calls
- Store the Stripe customer ID in your user database
- Use `cancel_at_period_end: true` instead of immediate cancellation
- Test with Stripe CLI: `stripe listen --forward-to localhost:3000/api/webhooks/stripe`
- Use Stripe Tax for automatic tax calculation
- Granting access on client-side success redirect without webhook confirmation
- Not storing Stripe customer IDs — makes customer management impossible
- Not handling `invoice.payment_failed` — users lose access silently
- Using test keys in production or vice versa
## Quick Example
```bash
npm install stripe
```
```typescript
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-12-18.acacia',
});
```skilldb get payment-services-skills/StripeFull skill: 261 linesStripe Payment Integration
You are a payments specialist who integrates Stripe into projects. You understand the Stripe API surface — Checkout, Payment Intents, Subscriptions, Invoices, Customer Portal, webhooks — and how to build reliable, PCI-compliant payment flows.
Core Philosophy
Webhooks are the source of truth
Never trust client-side payment confirmation. Always verify payment state through
webhooks. The checkout.session.completed or invoice.paid event is when you grant
access — not when the user returns to your success URL.
Checkout for speed, Elements for control
Stripe Checkout is a hosted payment page that handles all UI, validation, and compliance. Use it when you want to ship fast. Stripe Elements gives you embeddable UI components for custom payment forms. Use it when design control matters.
Idempotent everything
Stripe supports idempotency keys on all mutating API calls. Use them. Webhook events can be delivered multiple times. Your handlers must be idempotent.
Setup
Install
npm install stripe
Initialize
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-12-18.acacia',
});
Key Techniques
Checkout Session (fastest integration)
const session = await stripe.checkout.sessions.create({
mode: 'subscription', // or 'payment' for one-time
line_items: [{
price: 'price_xxx', // Price ID from Stripe dashboard
quantity: 1,
}],
success_url: 'https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yourdomain.com/pricing',
customer_email: user.email,
metadata: { userId: user.id },
subscription_data: {
trial_period_days: 14,
metadata: { userId: user.id },
},
});
// Redirect user to session.url
Payment Intent (custom form with Elements)
// Server: create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2900, // $29.00 in cents
currency: 'usd',
customer: customerId,
metadata: { userId: user.id, plan: 'pro' },
automatic_payment_methods: { enabled: true },
});
// Client: confirm with Stripe.js
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: 'https://yourdomain.com/success',
},
});
Subscriptions
// Create subscription directly
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: 'price_xxx' }],
trial_period_days: 14,
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent'],
metadata: { userId: user.id },
});
// Update subscription (change plan)
await stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscription.items.data[0].id,
price: 'price_new_xxx',
}],
proration_behavior: 'create_prorations',
});
// Cancel subscription
await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true, // Cancel at end of billing period
});
Customer management
// Create customer
const customer = await stripe.customers.create({
email: user.email,
name: user.name,
metadata: { userId: user.id },
});
// Attach payment method
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customer.id,
});
// Set default payment method
await stripe.customers.update(customer.id, {
invoice_settings: { default_payment_method: paymentMethodId },
});
Customer billing portal
const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: 'https://yourdomain.com/account',
});
// Redirect to portalSession.url
Metered billing
// Report usage for metered subscriptions
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
quantity: 150, // API calls this period
timestamp: Math.floor(Date.now() / 1000),
action: 'set', // 'set' replaces, 'increment' adds
});
Invoices
// List invoices for a customer
const invoices = await stripe.invoices.list({
customer: customerId,
limit: 10,
});
// Create one-off invoice
const invoice = await stripe.invoices.create({
customer: customerId,
auto_advance: true,
});
await stripe.invoiceItems.create({
customer: customerId,
invoice: invoice.id,
amount: 5000,
currency: 'usd',
description: 'Custom skill development',
});
await stripe.invoices.finalizeInvoice(invoice.id);
Webhook Processing
Critical webhooks to handle:
| Event | Action |
|---|---|
checkout.session.completed | Grant access, create user record |
invoice.paid | Renew access, send receipt |
invoice.payment_failed | Warn user, retry or restrict access |
customer.subscription.updated | Update plan in database |
customer.subscription.deleted | Revoke access |
customer.subscription.trial_will_end | Send trial ending email (3 days before) |
import Stripe from 'stripe';
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get('stripe-signature');
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, sig, endpointSecret);
} catch (err) {
return new Response('Invalid signature', { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
const userId = session.metadata?.userId;
await grantAccess(userId, session.subscription as string);
break;
}
case 'invoice.paid': {
const invoice = event.data.object as Stripe.Invoice;
await renewAccess(invoice.subscription as string);
await sendReceipt(invoice);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await notifyPaymentFailed(invoice);
break;
}
case 'customer.subscription.deleted': {
const sub = event.data.object as Stripe.Subscription;
await revokeAccess(sub.metadata?.userId);
break;
}
}
return new Response('OK');
}
Best Practices
- Always verify webhooks with
stripe.webhooks.constructEvent - Use metadata on all objects to link Stripe records to your database
- Use Checkout for fastest integration — custom UI later if needed
- Use idempotency keys on all mutating API calls
- Store the Stripe customer ID in your user database
- Use
cancel_at_period_end: trueinstead of immediate cancellation - Test with Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Use Stripe Tax for automatic tax calculation
Anti-Patterns
- Granting access on client-side success redirect without webhook confirmation
- Not storing Stripe customer IDs — makes customer management impossible
- Not handling
invoice.payment_failed— users lose access silently - Using test keys in production or vice versa
- Not using idempotency keys — causes duplicate charges
- Building custom billing portal instead of using Stripe's
- Hardcoding prices instead of using Stripe Price objects
Install this skill directly: skilldb add payment-services-skills
Related Skills
Adyen
Accept payments with Adyen. Use this skill when the project needs to integrate
Braintree
Accept payments with Braintree (PayPal). Use this skill when the project needs
Checkout Com
Accept payments with Checkout.com. Use this skill when the project needs to
Coinbase Commerce
Accept cryptocurrency payments with Coinbase Commerce. Use this skill when the
Creem
Accept payments with Creem as merchant of record. Use this skill when the project
Klarna
Accept payments with Klarna. Use this skill when the project needs to integrate