stripe — Claude Code skill: Stripe payments and subscriptions
Integration rules for Stripe in TypeScript, React and Next.js: server-side payments, subscriptions, webhooks. Useful when your agent writes checkout code.
npx skills add Mindrally/skills --skill stripe -a claude-codeWhat stripe does
The skill sets boundaries for how the agent handles Stripe: payments run on the server, secret keys stay out of client code, webhooks get signature verification. It ships TypeScript examples — client setup, a Payment Intent route in app/api/create-payment-intent/route.ts, Stripe Elements wired through loadStripe and Elements, and a checkout form using PaymentElement with confirmPayment.
Subscriptions and customers get their own sections: creating a subscription with payment_behavior: 'default_incomplete', cancelling one, and looking up an existing customer by email before creating a duplicate. The webhook handler in app/api/webhooks/stripe/route.ts switches on payment_intent.succeeded, customer.subscription.created, customer.subscription.deleted and invoice.payment_failed.
You need STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY in your environment. Leak the secret key to the browser and you have a real incident, not a style issue. The examples pin API version 2023-10-16, so behaviour can shift if you upgrade Stripe.
What it helps with
- accept card payments in Next.js without exposing the secret key
- create a Payment Intent and return the clientSecret
- wire up Stripe Elements and a checkout form
- start a subscription with an incomplete payment and save the card
- verify a webhook signature and route events by type
Example prompts for the agent
- add Stripe checkout to this Next.js app: a server route for the Payment Intent and a PaymentElement form
- create a Stripe subscription with payment_behavior default_incomplete and save the payment method
- write a Stripe webhook that verifies the signature and handles payment_intent.succeeded
How to install stripe
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill stripe -a claude-code2Manually: put the files in place
~/.claude/skills/stripe/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/stripe/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill stripe -a codex2Manually: put the files in place
.agents/skills/stripe/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill stripe -a cursor2Manually: put the files in place
.cursor/skills/stripe/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Mindrally/skills stripe2Manually: put the files in place
.github/skills/stripe/SKILL.md1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill stripe -a gemini-cli2Platform CLI
gemini skills install https://github.com/Mindrally/skills --path stripeManually: put the files in place
.devin/skills/stripe/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Mindrally/skills --skill stripe -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about stripe
How do I install the stripe skill in Claude?
Run npx skills add Mindrally/skills --skill stripe -a claude-code. The skill file lands at ~/.claude/skills/stripe/SKILL.md.
What does the stripe skill do?
It gives your agent Stripe integration rules for TypeScript, React and Next.js: server-side payments, subscriptions, customer handling and webhooks. Code examples cover each step.
What do I need before using it?
Environment variables STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY. The examples target the Next.js App Router with TypeScript.
Source text
SKILL.md · Apache-2.0
---
name: stripe
description: Stripe payment integration guidelines for TypeScript, React, Next.js with secure payment processing and subscription management
metadata:
maintainer: Mindrally
source: https://github.com/Mindrally/skills
---
# Stripe Integration
You are an expert in Stripe payment integration, TypeScript, React, and Next.js for building secure payment solutions.
## Core Principles
- Always handle payments on the server side
- Use Stripe's latest API version
- Implement proper error handling
- Follow PCI compliance best practices
- Use webhooks for reliable event handling
## Server-Side Setup
### Stripe Client Configuration
```typescript
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true,
});
```
### Create Payment Intent
```typescript
// app/api/create-payment-intent/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
try {
const { amount, currency = 'usd' } = await request.json();
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
automatic_payment_methods: { enabled: true },
});
return NextResponse.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
return NextResponse.json(
{ error: 'Payment intent creation failed' },
{ status: 500 }
);
}
}
```
## Client-Side Integration
### Stripe Elements Setup
```typescript
'use client';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export function StripeProvider({ children }: { children: React.ReactNode }) {
return (
<Elements stripe={stripePromise}>
{children}
</Elements>
);
}
```
### Payment Form Component
```typescript
'use client';
import { useState } from 'react';
import {
PaymentElement,
useStripe,
useElements,
} from '@stripe/react-stripe-js';
export function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setIsLoading(true);
setError(null);
const { error: submitError } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/payment/success`,
},
});
if (submitError) {
setError(submitError.message ?? 'Payment failed');
}
setIsLoading(false);
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{error && <div className="error">{error}</div>}
<button type="submit" disabled={!stripe || isLoading}>
{isLoading ? 'Processing...' : 'Pay Now'}
</button>
</form>
);
}
```
## Subscription Management
### Create Subscription
```typescript
export async function createSubscription(
customerId: string,
priceId: string
): Promise<Stripe.Subscription> {
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
payment_settings: {
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
});
return subscription;
}
```
### Cancel Subscription
```typescript
export async function cancelSubscription(
subscriptionId: string
): Promise<Stripe.Subscription> {
return stripe.subscriptions.cancel(subscriptionId);
}
```
## Webhook Handling
```typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: Request) {
const body = await request.text();
const signature = headers().get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (error) {
return new Response('Webhook signature verification failed', {
status: 400,
});
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handlePaymentSuccess(paymentIntent);
break;
case 'customer.subscription.created':
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionCreated(subscription);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event.data.object);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object);
break;
}
return new Response('Webhook processed', { status: 200 });
}
```
## Customer Management
```typescript
export async function createOrRetrieveCustomer(
email: string,
userId: string
): Promise<Stripe.Customer> {
const existingCustomers = await stripe.customers.list({
email,
limit: 1,
});
if (existingCustomers.data.length > 0) {
return existingCustomers.data[0];
}
return stripe.customers.create({
email,
metadata: { userId },
});
}
```
## Security Best Practices
- Never expose secret keys on the client
- Always verify webhook signatures
- Use idempotency keys for critical operations
- Implement proper error handling
- Log payment events for debugging
- Use Stripe's test mode for development
## Error Handling
```typescript
try {
const paymentIntent = await stripe.paymentIntents.create({...});
} catch (error) {
if (error instanceof Stripe.errors.StripeCardError) {
// Card was declined
console.error('Card declined:', error.message);
} else if (error instanceof Stripe.errors.StripeInvalidRequestError) {
// Invalid parameters
console.error('Invalid request:', error.message);
} else {
// Other errors
console.error('Stripe error:', error);
}
}
```
## Testing
- Use Stripe test mode and test card numbers
- Test webhook events with Stripe CLI
- Implement proper error scenarios
- Test subscription lifecycle events
© Mindrally · Apache-2.0 · Mindrally/skills