skills/stripe-expert/SKILL.md
ALWAYS trigger for ANY task involving Stripe, payments, subscriptions, checkout sessions, Stripe webhooks, Stripe Connect, payment intents, invoicing, billing portals, pricing tables, metered billing, SaaS billing, payment processing, credit card handling, or Stripe Elements. This includes both one-time payments and recurring subscriptions.
npx skillsauth add thesaifalitai/claude-setup stripe-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a senior payments engineer specializing in Stripe integrations. You build secure, production-grade payment flows with proper webhook handling, idempotency, and error recovery.
sk_test_* keys before going live.npm install stripe @stripe/stripe-js @stripe/react-stripe-js
# .env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
typescript: true,
});
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(req: NextRequest) {
const { priceId, userId } = await req.json();
const session = await stripe.checkout.sessions.create({
mode: 'payment',
payment_method_types: ['card'],
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${req.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.nextUrl.origin}/pricing`,
metadata: { userId },
client_reference_id: userId,
});
return NextResponse.json({ url: session.url });
}
// app/api/subscribe/route.ts
import { stripe } from '@/lib/stripe';
export async function POST(req: NextRequest) {
const { email, priceId, userId } = await req.json();
// Find or create customer
let customer: Stripe.Customer;
const existing = await stripe.customers.list({ email, limit: 1 });
if (existing.data.length > 0) {
customer = existing.data[0];
} else {
customer = await stripe.customers.create({
email,
metadata: { userId },
});
}
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customer.id,
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${req.nextUrl.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.nextUrl.origin}/pricing`,
subscription_data: {
trial_period_days: 14,
metadata: { userId },
},
});
return NextResponse.json({ url: session.url });
}
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
export async function POST(req: NextRequest) {
const body = await req.text();
const headerList = await headers();
const signature = headerList.get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error('Webhook signature verification failed');
return new Response('Invalid signature', { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await db.user.update({
where: { id: session.metadata?.userId },
data: {
stripeCustomerId: session.customer as string,
subscriptionStatus: 'active',
},
});
break;
}
case 'invoice.payment_succeeded': {
const invoice = event.data.object as Stripe.Invoice;
await db.payment.create({
data: {
stripeInvoiceId: invoice.id,
amount: invoice.amount_paid,
currency: invoice.currency,
customerId: invoice.customer as string,
status: 'paid',
},
});
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
await db.user.update({
where: { stripeCustomerId: subscription.customer as string },
data: { subscriptionStatus: 'canceled' },
});
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
// Notify user about failed payment
break;
}
}
return new Response('OK', { status: 200 });
}
// app/api/billing-portal/route.ts
export async function POST(req: NextRequest) {
const { customerId } = await req.json();
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${req.nextUrl.origin}/dashboard`,
});
return NextResponse.json({ url: session.url });
}
// components/CheckoutButton.tsx
'use client';
export function CheckoutButton({ priceId }: { priceId: string }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId, userId: currentUser.id }),
});
const { url } = await res.json();
window.location.href = url;
} finally {
setLoading(false);
}
};
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Redirecting...' : 'Subscribe'}
</button>
);
}
// Create connected account
const account = await stripe.accounts.create({
type: 'express',
email: sellerEmail,
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
});
// Create account link for onboarding
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${origin}/seller/refresh`,
return_url: `${origin}/seller/dashboard`,
type: 'account_onboarding',
});
// Create payment with platform fee
const paymentIntent = await stripe.paymentIntents.create({
amount: 10000, // $100.00
currency: 'usd',
application_fee_amount: 1000, // $10 platform fee
transfer_data: { destination: connectedAccountId },
});
invoice.payment_failed for dunningdevelopment
Use when building Vue 3 applications with Composition API, Nuxt 3, or Quasar. Invoke for Pinia, TypeScript, PWA, Capacitor mobile apps, Vite configuration.
tools
Expert Upwork freelancer skill for writing winning proposals, client communication, and project management. ALWAYS trigger for ANY task involving Upwork job proposals, bid writing, client messages, project scoping, milestone planning, contract setup, client onboarding, status updates, project handoffs, portfolio descriptions, rate negotiation, handling difficult clients, raising rates, dispute resolution, or review requests. Also triggers for: "write a proposal", "reply to client", "Upwork bid", "freelance proposal", "client message", "project quote", "scope of work", "follow up with client", "client is ghosting", "request review", "upwork job".
development
UI component code implementation specialist. Trigger when WRITING or FIXING UI code — Tailwind CSS utilities, shadcn/ui components, Radix UI primitives, CVA component variants, Framer Motion animations, CSS variables for dark mode, WCAG accessibility code (ARIA labels, focus management, keyboard navigation), responsive layouts, skeleton loaders, empty states, Storybook stories, Figma-to-code. Also triggers for: fix this component, add dark mode, make this accessible, write a Button component, style this form, add animations, fix layout shift. For design PLANNING (choosing colors, styles, font pairings for a new project) use ui-ux-pro-max instead.
development
Design PLANNING and DISCOVERY specialist — use before writing any code. Invoke when CHOOSING a design direction: pick a visual style (glassmorphism, brutalism, minimalism, claymorphism, neumorphism, bento grid, skeuomorphism), generate a color palette, select font pairings, plan a design system, choose chart styles, or define the look-and-feel for a new project. Triggers: 'what style should I use', 'suggest a color palette', 'recommend fonts for my app', 'design system for a SaaS', 'what UI style fits a fintech app', 'generate a design brief', 'choose between dark/light theme', 'plan the visual identity'. Covers 50 styles, 97 palettes, 57 font pairings across React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui stacks. For WRITING or FIXING actual UI code (components, Tailwind classes, animations) use uiux-design instead.