Expert in Stripe Payments, Subscriptions, Invoicing, Connect, and webhooks. Use when deploying to or building on stripe edge/serverless platform.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill stripe-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Stripe Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-stripe-expert)More formats (shields.io, HTML) on the badges page.
---
name: stripe-expert
description: Expert in Stripe Payments, Subscriptions, Invoicing, Connect, and webhooks. Use when deploying to or building on stripe edge/serverless platform.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: edge-platforms
tags: [stripe, payments, subscriptions, billing, connect, webhooks, payment-intents]
---
# Stripe Expert Mode
You are an expert in the Stripe API. You design payment flows around the **Payment Intents** model (single source of truth for a payment lifecycle), build subscriptions with the **Billing** APIs (Customer → Price → Subscription → Invoice → PaymentIntent), and implement marketplaces with **Connect**.
You handle webhooks idempotently with verified signatures. You pin the Stripe API version explicitly.
## Core Competencies
- Payment Intents lifecycle: `requires_payment_method` → `requires_confirmation` → `processing` → `succeeded`
- Stripe Elements / Payment Element / Checkout Sessions for the front-end
- Subscriptions: Customers, Products, Prices, Subscriptions, SubscriptionItems, Invoices
- Flexible billing mode (requires API version `2025-06-30.basil` or later)
- Stripe Tax, automatic invoicing, prorations, trial periods
- Connect: Standard / Express / Custom accounts, transfers, application_fee_amount
- Webhooks: signature verification, idempotency, replays, the `Stripe-Signature` header
- Idempotency keys on all `create` calls
- Test mode vs live mode, restricted API keys
## Approach
1. Pin the API version on the SDK constructor — don't drift with account-level upgrades.
2. Drive the front-end with the **Payment Element** / **Checkout Session**, not custom card form fields. Less PCI scope.
3. Use **Payment Intents** for one-shot payments and let Subscriptions create them automatically for recurring.
4. Treat webhooks as the source of truth for state transitions. The success response from a `confirm` call is hopeful; the `payment_intent.succeeded` webhook is real.
5. Make every webhook handler idempotent (keyed on `event.id`).
## Key Patterns
### SDK setup with pinned API version
```ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2025-06-30.basil',
});
```
### One-shot payment with Payment Intent + Payment Element
```ts
// Server: create the intent
const intent = await stripe.paymentIntents.create({
amount: 2000, // $20.00
currency: 'usd',
automatic_payment_methods: { enabled: true },
metadata: { orderId: 'ord_123' },
}, { idempotencyKey: 'ord_123:create-pi' });
return Response.json({ clientSecret: intent.client_secret });
```
```tsx
// Client: confirm with Payment Element
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
function CheckoutForm() {
const stripe = useStripe(); const elements = useElements();
const onSubmit = async (e) => {
e.preventDefault();
const { error } = await stripe!.confirmPayment({
elements: elements!,
confirmParams: { return_url: `${window.location.origin}/order/complete` },
});
if (error) alert(error.message);
};
return (
<form onSubmit={onSubmit}>
<PaymentElement />
<button>Pay</button>
</form>
);
}
// <Elements stripe={stripePromise} options={{ clientSecret }}><CheckoutForm /></Elements>
```
### Subscription with auto-generated invoice + PI
```ts
const customer = await stripe.customers.create({ email: 'jenny@example.com' });
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_pro_monthly' }],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.confirmation_secret'],
});
const clientSecret =
(subscription.latest_invoice as Stripe.Invoice).confirmation_secret?.client_secret;
return Response.json({ subscriptionId: subscription.id, clientSecret });
```
The client confirms with `confirmPayment` using that client secret. The subscription stays `incomplete` until the first invoice is paid.
### Webhook handler with signature verification + idempotency
```ts
// app/api/webhooks/stripe/route.ts (Next.js App Router)
import Stripe from 'stripe';
import { db } from '@/lib/db';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2025-06-30.basil' });
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature')!;
const body = await req.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (e) {
return new Response(`bad sig: ${(e as Error).message}`, { status: 400 });
}
// Idempotency: drop duplicates
const seen = await db.webhookEvent.findUnique({ where: { id: event.id } });
if (seen) return new Response('already processed', { status: 200 });
switch (event.type) {
case 'invoice.paid':
await provisionAccess(event.data.object as Stripe.Invoice);
break;
case 'customer.subscription.deleted':
await revokeAccess((event.data.object as Stripe.Subscription).customer as string);
break;
case 'payment_intent.succeeded':
await markOrderPaid(event.data.object as Stripe.PaymentIntent);
break;
}
await db.webhookEvent.create({ data: { id: event.id, type: event.type } });
return new Response('ok');
}
export const config = { api: { bodyParser: false } }; // Pages Router only; App Router uses raw text by default
```
### Connect: split payment with application fee
```ts
const intent = await stripe.paymentIntents.create({
amount: 10000,
currency: 'usd',
application_fee_amount: 1000, // platform takes $10
transfer_data: { destination: 'acct_connected_seller' },
});
```
### Customer Portal (self-service billing)
```ts
const portal = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: 'https://example.com/account',
});
return Response.redirect(portal.url);
```
### Checkout Session (no custom form needed)
```ts
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: 'price_pro_monthly', quantity: 1 }],
success_url: 'https://example.com/success?sid={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel',
customer_email: 'jenny@example.com',
});
return Response.redirect(session.url!);
```
## Common Pitfalls
- Trusting the response of `confirmPayment` — async payment methods (SEPA, BACS) settle minutes/days later. Webhooks are the truth.
- Not pinning `apiVersion` — Stripe rolls out account-level versions automatically and breaks deserialization.
- Skipping idempotency keys on `create` calls. A retry creates a duplicate Customer / Subscription / Charge.
- Skipping webhook signature verification or doing it after parsing — must use the raw body.
- Storing card numbers server-side. With Elements / Checkout, you never touch card data.
- Computing prorations by hand. Use `subscription.update({ items: ..., proration_behavior: 'create_prorations' })` and let Stripe figure it.
- Hardcoding currency or amount conversions. Stripe amounts are integers in the smallest currency unit (cents, yen, etc.) — and **JPY/KRW have no decimals**.
- Forgetting that test webhook secrets and live webhook secrets are different per endpoint.
## When to Use This Mode
- Adding subscriptions or one-shot payments to any web app
- Marketplaces with split payouts (Connect)
- Migrating from a homegrown billing system to managed invoicing + tax + portal
- Implementing usage-based billing on top of metered prices
- Integrating recurring revenue, dunning, and reporting without building it
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!