Comprehensive Stripe API expert with access to 3,253 official documentation files covering all payment processing, billing, subscriptions, webhooks, Connect, Terminal, Radar, Identity, Tax, Climate, and integrations. Invoke when user mentions Stripe, payments, subscriptions, billing, payment processing, checkout, invoices, or any payment-related features.
Provide comprehensive, accurate guidance for integrating Stripe payment infrastructure based on 3,253+ official Stripe documentation files. Cover all aspects of payment processing, billing, subscriptions, webhooks, fraud prevention, and advanced features.
Full access to official Stripe documentation (when available):
docs/stripe/Note: Documentation must be pulled separately:
pipx install docpull
docpull https://docs.stripe.com -o .Codex/skills/stripe/docs
Major Areas:
Invoke when user mentions:
When answering questions:
Search for specific topics:
# Use Grep to find relevant docs
grep -r "payment intent" docs/stripe/ --include="*.md"
Read specific API references:
# API docs are in docs/stripe/api/
cat docs/stripe/api/payment_intents/api-payment_intents-create.md
Find integration guides:
# Guides and tutorials
ls docs/stripe/*.md
// Server-side (Node.js)
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Client-side (safe for browsers)
const stripe = Stripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
Key Types:
pk_test_... (publishable), sk_test_... (secret)pk_live_... (publishable), sk_live_... (secret)Security:
Best for: Quick setup, standard checkout flow, subscriptions
// Server-side API route
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price: 'price_xxx', // Pre-created price ID
quantity: 1,
}],
mode: 'payment', // or 'subscription' or 'setup'
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
});
return Response.json({ url: session.url });
}
// Client-side redirect
window.location.href = checkoutUrl;
Best for: Custom checkout experience, embedded in your site
// Server: Create Payment Intent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000, // $20.00 in cents
currency: 'usd',
automatic_payment_methods: { enabled: true },
});
// Client: Mount Payment Element
import { Elements, PaymentElement } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
function CheckoutForm() {
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<PaymentElement />
<button onClick={handleSubmit}>Pay</button>
</Elements>
);
}
Create subscription:
const subscription = await stripe.subscriptions.create({
customer: 'cus_xxx',
items: [{ price: 'price_xxx' }],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
});
// Return client secret for 3DS
const clientSecret = subscription.latest_invoice.payment_intent.client_secret;
Subscription lifecycle:
incomplete → active → past_due → canceled / unpaidMetered billing:
// Report usage
await stripe.subscriptionItems.createUsageRecord('si_xxx', {
quantity: 100,
timestamp: Math.floor(Date.now() / 1000),
});
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(req: Request) {
const body = await req.text();
const signature = headers().get('stripe-signature')!;
let event: Stripe.Event;
try {
// Verify signature (CRITICAL for security)
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
}
// Handle event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
// Fulfill order, send confirmation email
await fulfillOrder(paymentIntent);
break;
case 'payment_intent.payment_failed':
// Notify customer of failure
await notifyPaymentFailure(event.data.object);
break;
case 'customer.subscription.created':
case 'customer.subscription.updated':
// Update subscription status in database
await updateSubscription(event.data.object);
break;
case 'customer.subscription.deleted':
// Cancel user access
await cancelAccess(event.data.object);
break;
case 'invoice.payment_succeeded':
// Subscription payment succeeded
await confirmSubscriptionPayment(event.data.object);
break;
case 'invoice.payment_failed':
// Notify customer to update payment method
await notifyPaymentMethodUpdate(event.data.object);
break;
case 'charge.dispute.created':
// Handle dispute
await handleDispute(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return new Response(JSON.stringify({ received: true }));
}
Critical webhook events:
payment_intent.succeeded - Payment completedpayment_intent.payment_failed - Payment failedcustomer.subscription.* - Subscription lifecycleinvoice.payment_succeeded - Recurring payment succeededinvoice.payment_failed - Recurring payment failedcharge.dispute.created - Customer disputed chargecheckout.session.completed - Checkout session completedTesting webhooks locally:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/webhooks
# Trigger test events
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.created
// Create customer
const customer = await stripe.customers.create({
email: '[email protected]',
name: 'John Doe',
metadata: { user_id: '12345' },
payment_method: 'pm_xxx',
invoice_settings: {
default_payment_method: 'pm_xxx',
},
});
// Attach payment method
await stripe.paymentMethods.attach('pm_xxx', {
customer: 'cus_xxx',
});
// List customer's payment methods
const paymentMethods = await stripe.paymentMethods.list({
customer: 'cus_xxx',
type: 'card',
});
// Update customer
await stripe.customers.update('cus_xxx', {
metadata: { plan: 'premium' },
});
Create connected account:
const account = await stripe.accounts.create({
type: 'express',
country: 'US',
email: '[email protected]',
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
});
// Create account link for onboarding
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: 'https://example.com/reauth',
return_url: 'https://example.com/return',
type: 'account_onboarding',
});
Split payments:
// Application fee
await stripe.paymentIntents.create({
amount: 10000,
currency: 'usd',
application_fee_amount: 1000, // 10% platform fee
transfer_data: {
destination: 'acct_xxx', // Connected account
},
});
// Or use separate transfers
await stripe.transfers.create({
amount: 9000,
currency: 'usd',
destination: 'acct_xxx',
});
// Rules are configured in Dashboard
// But you can review risk scores:
const paymentIntent = await stripe.paymentIntents.retrieve('pi_xxx', {
expand: ['latest_charge.payment_method_details.card'],
});
const riskScore = paymentIntent.latest_charge?.outcome?.risk_score;
const riskLevel = paymentIntent.latest_charge?.outcome?.risk_level; // 'normal', 'elevated', 'highest'
// Block high-risk payments
if (riskLevel === 'highest') {
await stripe.paymentIntents.cancel('pi_xxx');
}
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
price: 'price_xxx',
quantity: 1,
}],
automatic_tax: { enabled: true }, // Stripe calculates tax automatically
customer_update: {
address: 'auto', // Collect address for tax
},
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
Success:
4242 4242 4242 4242 - Visa5555 5555 5555 4444 - Mastercard3782 822463 10005 - American ExpressRequires 3D Secure:
4000 0025 0000 3155 - Visa (3DS required)4000 0027 6000 3184 - Visa (3DS required, decline)Declined:
4000 0000 0000 9995 - Generic decline4000 0000 0000 9987 - Insufficient funds4000 0000 0000 9979 - Stolen cardOther:
12/34)API Keys:
Webhooks:
Amounts:
PCI Compliance:
Idempotency:
Authentication:
Invalid API Key - Check key format and environmentNo such customer - Customer deleted or wrong IDPayment failures:
card_declined - Generic decline (ask customer to try different card)insufficient_funds - Not enough moneyincorrect_cvc - Wrong security codeexpired_card - Card expiredSubscription errors:
resource_missing - Price or product doesn't existpayment_intent_authentication_failure - 3DS failed// app/api/create-payment-intent/route.ts
import { NextRequest } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: NextRequest) {
const { amount } = await req.json();
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
automatic_payment_methods: { enabled: true },
});
return Response.json({ clientSecret: paymentIntent.client_secret });
}
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
export function useStripeCheckout() {
const [loading, setLoading] = useState(false);
const checkout = async (priceId: string) => {
setLoading(true);
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const { sessionId } = await res.json();
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
await stripe?.redirectToCheckout({ sessionId });
setLoading(false);
};
return { checkout, loading };
}
// types/stripe.ts
import Stripe from 'stripe';
export interface StripeCustomerData {
stripeCustomerId: string;
stripeSubscriptionId?: string;
stripePriceId?: string;
stripeCurrentPeriodEnd?: Date;
userId: string;
}
export interface CreateCheckoutSessionParams {
userId: string;
priceId: string;
successUrl: string;
cancelUrl: string;
metadata?: Record<string, string>;
}
export interface WebhookEventData {
event: Stripe.Event;
customerId?: string;
subscriptionId?: string;
invoiceId?: string;
}
export type StripeSubscriptionStatus =
| 'incomplete'
| 'incomplete_expired'
| 'trialing'
| 'active'
| 'past_due'
| 'canceled'
| 'unpaid';
export interface SubscriptionUpdate {
subscriptionId: string;
status: StripeSubscriptionStatus;
currentPeriodEnd: Date;
cancelAtPeriodEnd: boolean;
priceId: string;
}
// lib/stripe/config.ts
import Stripe from 'stripe';
if (!process.env.STRIPE_SECRET_KEY) {
throw new Error('STRIPE_SECRET_KEY is not set');
}
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-11-20.acacia',
typescript: true,
appInfo: {
name: 'Your App Name',
version: '1.0.0',
},
// Retry logic for network failures
maxNetworkRetries: 2,
timeout: 30000, // 30 seconds
});
// Client-side configuration
export const getStripe = () => {
if (!process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) {
throw new Error('NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is not set');
}
return loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
};
# .env.local
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Production
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
// app/actions/stripe.ts
'use server';
import { stripe } from '@/lib/stripe/config';
import { auth } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
export async function createCheckoutSession(priceId: string) {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
const checkoutSession = await stripe.checkout.sessions.create({
customer_email: session.user.email,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{
price: priceId,
quantity: 1,
}],
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true`,
metadata: {
userId: session.user.id,
},
});
return { url: checkoutSession.url };
}
export async function createPortalSession() {
const session = await auth();
if (!session?.user?.stripeCustomerId) {
throw new Error('No Stripe customer found');
}
const portalSession = await stripe.billingPortal.sessions.create({
customer: session.user.stripeCustomerId,
return_url: `${process.env.NEXT_PUBLIC_URL}/dashboard`,
});
return { url: portalSession.url };
}
export async function cancelSubscription(subscriptionId: string) {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true,
});
revalidatePath('/dashboard');
return { success: true };
}
// app/components/PricingCard.tsx
'use client';
import { useState } from 'react';
import { createCheckoutSession } from '@/app/actions/stripe';
export function PricingCard({ priceId, name, price, features }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
try {
setLoading(true);
const { url } = await createCheckoutSession(priceId);
if (url) window.location.href = url;
} catch (error) {
console.error('Checkout error:', error);
alert('Failed to start checkout');
} finally {
setLoading(false);
}
};
return (
<div className="pricing-card">
<h3>{name}</h3>
<p>${price}/mo</p>
<ul>
{features.map(f => <li key={f}>{f}</li>)}
</ul>
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Loading...' : 'Subscribe'}
</button>
</div>
);
}
// app/api/create-payment-intent/route.ts
import { NextRequest } from 'next/server';
import { stripe } from '@/lib/stripe/config';
import { auth } from '@/lib/auth';
export async function POST(req: NextRequest) {
try {
const session = await auth();
if (!session?.user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const { amount, currency = 'usd' } = await req.json();
// Validate amount server-side (CRITICAL)
if (!amount || amount < 50) { // $0.50 minimum
return Response.json({ error: 'Invalid amount' }, { status: 400 });
}
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
automatic_payment_methods: { enabled: true },
metadata: {
userId: session.user.id,
},
});
return Response.json({
clientSecret: paymentIntent.client_secret
});
} catch (error) {
console.error('Payment intent error:', error);
return Response.json(
{ error: 'Failed to create payment intent' },
{ status: 500 }
);
}
}
// lib/stripe/errors.ts
import Stripe from 'stripe';
export function handleStripeError(error: unknown): {
message: string;
userMessage: string;
code?: string;
} {
if (error instanceof Stripe.errors.StripeError) {
// Card errors
if (error.type === 'StripeCardError') {
return {
message: error.message,
userMessage: 'Your card was declined. Please try a different payment method.',
code: error.code,
};
}
// Rate limit errors
if (error.type === 'StripeRateLimitError') {
return {
message: 'Too many requests',
userMessage: 'Too many requests. Please try again in a moment.',
};
}
// Invalid request errors
if (error.type === 'StripeInvalidRequestError') {
return {
message: error.message,
userMessage: 'Invalid request. Please contact support.',
code: error.code,
};
}
// API errors
if (error.type === 'StripeAPIError') {
return {
message: 'Stripe API error',
userMessage: 'Payment service temporarily unavailable. Please try again.',
};
}
// Connection errors
if (error.type === 'StripeConnectionError') {
return {
message: 'Network error',
userMessage: 'Network error. Please check your connection and try again.',
};
}
// Authentication errors
if (error.type === 'StripeAuthenticationError') {
return {
message: 'Authentication failed',
userMessage: 'Authentication error. Please contact support.',
};
}
}
// Generic error
return {
message: error instanceof Error ? error.message : 'Unknown error',
userMessage: 'An unexpected error occurred. Please try again.',
};
}
// Usage in API routes
export async function POST(req: NextRequest) {
try {
const paymentIntent = await stripe.paymentIntents.create({...});
return Response.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
const { userMessage, message } = handleStripeError(error);
console.error('Stripe error:', message);
return Response.json({ error: userMessage }, { status: 400 });
}
}
// lib/stripe/idempotency.ts
import { v4 as uuidv4 } from 'uuid';
export async function createPaymentWithIdempotency(
amount: number,
userId: string
) {
const idempotencyKey = `payment_${userId}_${Date.now()}_${uuidv4()}`;
try {
const paymentIntent = await stripe.paymentIntents.create(
{
amount,
currency: 'usd',
metadata: { userId },
},
{
idempotencyKey, // Prevents duplicate charges on retry
}
);
return paymentIntent;
} catch (error) {
// If request fails, retry with same idempotency key
// Stripe will return the original result instead of creating duplicate
throw error;
}
}
// For subscriptions
export async function createSubscriptionIdempotent(
customerId: string,
priceId: string
) {
const idempotencyKey = `sub_${customerId}_${priceId}`;
return stripe.subscriptions.create(
{
customer: customerId,
items: [{ price: priceId }],
},
{ idempotencyKey }
);
}
// lib/stripe/retry.ts
export async function retryStripeOperation<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Don't retry certain errors
if (error instanceof Stripe.errors.StripeCardError) {
throw error; // Card declined - don't retry
}
if (error instanceof Stripe.errors.StripeInvalidRequestError) {
throw error; // Invalid params - don't retry
}
// Retry with exponential backoff
const delay = Math.min(1000 * Math.pow(2, i), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
// Usage
const paymentIntent = await retryStripeOperation(() =>
stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
})
);
// lib/stripe/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
});
export async function POST(req: NextRequest) {
const ip = req.ip ?? '127.0.0.1';
const { success, limit, reset, remaining } = await ratelimit.limit(ip);
if (!success) {
return Response.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
);
}
// Process Stripe request
const paymentIntent = await stripe.paymentIntents.create({...});
return Response.json({ clientSecret: paymentIntent.client_secret });
}
// lib/stripe/connect.ts
export async function createConnectedAccount(email: string, country: string) {
const account = await stripe.accounts.create({
type: 'express', // or 'standard' or 'custom'
country,
email,
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
business_type: 'individual', // or 'company'
});
return account;
}
export async function createAccountOnboardingLink(accountId: string) {
const accountLink = await stripe.accountLinks.create({
account: accountId,
refresh_url: `${process.env.NEXT_PUBLIC_URL}/connect/reauth`,
return_url: `${process.env.NEXT_PUBLIC_URL}/connect/return`,
type: 'account_onboarding',
});
return accountLink.url;
}
// Check if account is fully onboarded
export async function isAccountOnboarded(accountId: string): Promise<boolean> {
const account = await stripe.accounts.retrieve(accountId);
return (
account.details_submitted &&
account.charges_enabled &&
account.payouts_enabled
);
}
// Pattern 1: Direct Charge (platform receives, then transfers)
export async function createDirectCharge(
amount: number,
connectedAccountId: string
) {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
application_fee_amount: Math.floor(amount * 0.1), // 10% platform fee
transfer_data: {
destination: connectedAccountId,
},
});
return paymentIntent;
}
// Pattern 2: Destination Charge (connected account receives, platform takes fee)
export async function createDestinationCharge(
amount: number,
connectedAccountId: string
) {
const paymentIntent = await stripe.paymentIntents.create(
{
amount,
currency: 'usd',
application_fee_amount: Math.floor(amount * 0.1),
},
{
stripeAccount: connectedAccountId, // Charge appears on connected account
}
);
return paymentIntent;
}
// Pattern 3: Separate Transfers
export async function createSeparateTransfer(
amount: number,
connectedAccountId: string,
chargeId: string
) {
const platformFee = Math.floor(amount * 0.1);
const transferAmount = amount - platformFee;
const transfer = await stripe.transfers.create({
amount: transferAmount,
currency: 'usd',
destination: connectedAccountId,
source_transaction: chargeId,
});
return transfer;
}
// Split payment between multiple sellers
export async function createMultiPartyPayment(
totalAmount: number,
sellers: Array<{ accountId: string; amount: number }>
) {
// Create charge on platform account
const paymentIntent = await stripe.paymentIntents.create({
amount: totalAmount,
currency: 'usd',
});
// After charge succeeds, create transfers to each seller
const transfers = await Promise.all(
sellers.map(seller =>
stripe.transfers.create({
amount: seller.amount,
currency: 'usd',
destination: seller.accountId,
})
)
);
return { paymentIntent, transfers };
}
// Listen for Connect account events
export async function POST(req: Request) {
const event = await verifyWebhook(req);
switch (event.type) {
case 'account.updated':
const account = event.data.object as Stripe.Account;
// Check if account completed onboarding
if (account.details_submitted && account.charges_enabled) {
await updateDatabase({ accountId: account.id, status: 'active' });
}
break;
case 'account.application.deauthorized':
// User disconnected their account
const deauthAccount = event.data.object as Stripe.Account;
await handleAccountDisconnection(deauthAccount.id);
break;
case 'capability.updated':
// Track capability status (card_payments, transfers, etc.)
const capability = event.data.object;
await trackCapabilityStatus(capability);
break;
case 'payout.paid':
// Payout to connected account succeeded
const payout = event.data.object as Stripe.Payout;
await notifyVendorPayoutSuccess(payout);
break;
case 'payout.failed':
// Payout failed - notify vendor
const failedPayout = event.data.object as Stripe.Payout;
await notifyVendorPayoutFailure(failedPayout);
break;
}
return Response.json({ received: true });
}
// lib/stripe/terminal.ts
export async function registerReader(
registrationCode: string,
label: string,
locationId: string
) {
const reader = await stripe.terminal.readers.create({
registration_code: registrationCode,
label,
location: locationId,
});
return reader;
}
export async function createTerminalLocation(address: {
line1: string;
city: string;
state: string;
postal_code: string;
country: string;
}) {
const location = await stripe.terminal.locations.create({
display_name: 'Retail Store',
address,
});
return location;
}
// Create payment intent for Terminal
export async function createTerminalPaymentIntent(amount: number) {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method_types: ['card_present'],
capture_method: 'manual', // Authorize first, capture later
});
return paymentIntent;
}
// Server-side: Process Terminal connection token
export async function POST(req: NextRequest) {
const connectionToken = await stripe.terminal.connectionTokens.create();
return Response.json({ secret: connectionToken.secret });
}
// Client component for Terminal SDK
'use client';
import { loadStripeTerminal } from '@stripe/terminal-js';
export function TerminalReader() {
useEffect(() => {
const initTerminal = async () => {
const terminal = await loadStripeTerminal();
const term = terminal.create({
onFetchConnectionToken: async () => {
const res = await fetch('/api/terminal/connection-token');
const { secret } = await res.json();
return secret;
},
onUnexpectedReaderDisconnect: () => {
console.log('Reader disconnected');
},
});
// Discover readers
const discoverResult = await term.discoverReaders();
if (discoverResult.discoveredReaders.length > 0) {
// Connect to first reader
await term.connectReader(discoverResult.discoveredReaders[0]);
}
};
initTerminal();
}, []);
return <div>Terminal Reader Component</div>;
}
// Review high-risk charges before processing
export async function reviewHighRiskCharge(paymentIntentId: string) {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId, {
expand: ['latest_charge'],
});
const charge = paymentIntent.latest_charge as Stripe.Charge;
const outcome = charge?.outcome;
if (outcome?.risk_level === 'highest') {
// Block the payment
await stripe.paymentIntents.cancel(paymentIntentId);
return { action: 'blocked', reason: outcome.reason };
}
if (outcome?.risk_level === 'elevated' && outcome?.risk_score > 65) {
// Require manual review
return { action: 'review', riskScore: outcome.risk_score };
}
return { action: 'approve' };
}
// Add custom fraud metadata
export async function createPaymentWithFraudCheck(
amount: number,
email: string,
ipAddress: string
) {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
metadata: {
customer_email: email,
customer_ip: ipAddress,
order_id: generateOrderId(),
},
// Radar uses this data for fraud detection
});
return paymentIntent;
}
// Force 3D Secure for high-value transactions
export async function createSecurePayment(amount: number) {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method_options: {
card: {
request_three_d_secure: amount > 10000 ? 'any' : 'automatic',
},
},
});
return paymentIntent;
}
// Handle 3DS authentication in client
async function handleCardAction(clientSecret: string) {
const stripe = await getStripe();
const { error, paymentIntent } = await stripe.handleCardAction(clientSecret);
if (error) {
console.error('3DS failed:', error);
return { success: false };
}
if (paymentIntent.status === 'requires_confirmation') {
// Confirm on server
await fetch('/api/confirm-payment', {
method: 'POST',
body: JSON.stringify({ paymentIntentId: paymentIntent.id }),
});
}
return { success: true };
}
// Handle dispute webhooks
case 'charge.dispute.created':
const dispute = event.data.object as Stripe.Dispute;
// Automatically submit evidence for low-amount disputes
if (dispute.amount < 5000) { // $50
await stripe.disputes.update(dispute.id, {
evidence: {
customer_email_address: dispute.metadata.customer_email,
customer_name: dispute.metadata.customer_name,
shipping_tracking_number: dispute.metadata.tracking_number,
},
});
} else {
// Flag for manual review
await notifyDisputeTeam(dispute);
}
break;
case 'charge.dispute.closed':
const closedDispute = event.data.object as Stripe.Dispute;
if (closedDispute.status === 'won') {
await notifyDisputeWon(closedDispute);
} else {
await processDisputeLoss(closedDispute);
}
break;
// Create verification session
export async function createIdentityVerification(email: string) {
const verificationSession = await stripe.identity.verificationSessions.create({
type: 'document',
metadata: {
user_email: email,
},
options: {
document: {
require_live_capture: true,
require_matching_selfie: true,
allowed_types: ['driving_license', 'passport', 'id_card'],
},
},
});
return {
clientSecret: verificationSession.client_secret,
url: verificationSession.url,
};
}
// Check verification status
export async function checkVerificationStatus(sessionId: string) {
const session = await stripe.identity.verificationSessions.retrieve(sessionId);
return {
status: session.status, // 'requires_input', 'verified', 'canceled'
verified: session.status === 'verified',
lastError: session.last_error,
};
}
// Webhook handler
case 'identity.verification_session.verified':
const verifiedSession = event.data.object;
await updateUserVerificationStatus(
verifiedSession.metadata.user_email,
'verified'
);
break;
case 'identity.verification_session.requires_input':
const requiresInputSession = event.data.object;
await notifyUserVerificationIssue(requiresInputSession);
break;
// Create cardholder
export async function createCardholder(
name: string,
email: string,
phone: string
) {
const cardholder = await stripe.issuing.cardholders.create({
name,
email,
phone_number: phone,
billing: {
address: {
line1: '123 Main St',
city: 'San Francisco',
state: 'CA',
postal_code: '94111',
country: 'US',
},
},
type: 'individual',
});
return cardholder;
}
// Create virtual card
export async function createVirtualCard(cardholderId: string) {
const card = await stripe.issuing.cards.create({
cardholder: cardholderId,
currency: 'usd',
type: 'virtual',
status: 'active',
spending_controls: {
spending_limits: [{
amount: 100000, // $1,000 limit
interval: 'monthly',
}],
allowed_categories: ['food_restaurants', 'gas_stations'],
},
});
return card;
}
// Retrieve card details (PAN, CVV, etc.) - SENSITIVE
export async function getCardDetails(cardId: string) {
const card = await stripe.issuing.cards.retrieve(cardId, {
expand: ['number', 'cvc'],
});
return {
number: card.number,
cvc: card.cvc,
expMonth: card.exp_month,
expYear: card.exp_year,
};
}
// Webhook: Real-time authorization approval
case 'issuing_authorization.request':
const authorization = event.data.object as Stripe.Issuing.Authorization;
// Check spending limits
const userLimits = await getUserSpendingLimits(authorization.cardholder);
if (authorization.amount > userLimits.perTransaction) {
// Decline authorization
await stripe.issuing.authorizations.decline(authorization.id, {
metadata: { reason: 'exceeds_limit' },
});
} else {
// Approve authorization
await stripe.issuing.authorizations.approve(authorization.id);
}
break;
case 'issuing_authorization.created':
// Log transaction for user
await logCardTransaction(event.data.object);
break;
// Enable tax on Checkout
export async function createCheckoutWithTax(priceId: string) {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
automatic_tax: { enabled: true },
customer_update: {
address: 'auto', // Collect address for tax calculation
shipping: 'auto',
},
success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
});
return session;
}
// Calculate tax for custom amount
export async function calculateTax(
amount: number,
customerAddress: {
line1: string;
city: string;
state: string;
postal_code: string;
country: string;
}
) {
const calculation = await stripe.tax.calculations.create({
currency: 'usd',
line_items: [{
amount,
reference: 'product_001',
}],
customer_details: {
address: customerAddress,
address_source: 'shipping',
},
});
return {
amountTotal: calculation.amount_total,
taxAmount: calculation.tax_amount_exclusive,
taxBreakdown: calculation.tax_breakdown,
};
}
// Tax-inclusive pricing
export async function createTaxInclusivePrice() {
const price = await stripe.prices.create({
unit_amount: 1000,
currency: 'usd',
product: 'prod_xxx',
tax_behavior: 'inclusive', // Tax is included in price
});
return price;
}
// Create financial account for user
export async function createFinancialAccount(userId: string) {
const financialAccount = await stripe.treasury.financialAccounts.create({
supported_currencies: ['usd'],
features: {
card_issuing: { requested: true },
deposit_insurance: { requested: true },
financial_addresses: { aba: { requested: true } },
inbound_transfers: { ach: { requested: true } },
outbound_payments: {
ach: { requested: true },
us_domestic_wire: { requested: true },
},
},
metadata: { user_id: userId },
});
return financialAccount;
}
// Get account balance
export async function getAccountBalance(accountId: string) {
const account = await stripe.treasury.financialAccounts.retrieve(accountId);
return {
available: account.balance.cash.usd,
pending: account.balance.inbound_pending.usd,
};
}
// Create outbound payment
export async function sendPayment(
financialAccountId: string,
amount: number,
destinationRoutingNumber: string,
destinationAccountNumber: string
) {
const outboundPayment = await stripe.treasury.outboundPayments.create({
financial_account: financialAccountId,
amount,
currency: 'usd',
destination_payment_method_data: {
type: 'us_bank_account',
us_bank_account: {
routing_number: destinationRoutingNumber,
account_number: destinationAccountNumber,
account_holder_type: 'individual',
},
},
});
return outboundPayment;
}
// Add carbon removal to checkout
export async function createCheckoutWithClimate(priceId: string) {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
// Let customer choose carbon removal contribution
custom_fields: [{
key: 'climate_contribution',
label: { type: 'custom', custom: 'Contribute to carbon removal?' },
type: 'dropdown',
dropdown: {
options: [
{ label: 'No contribution', value: '0' },
{ label: '$1 - Remove 1kg CO2', value: '100' },
{ label: '$5 - Remove 5kg CO2', value: '500' },
{ label: '$10 - Remove 10kg CO2', value: '1000' },
],
},
}],
success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
});
return session;
}
// Process climate contribution
export async function createClimateOrder(amount: number, metadata: any) {
const order = await stripe.climate.orders.create({
amount, // Amount in cents to spend on carbon removal
metric_tons: amount / 1000, // ~$1 per kg
beneficiary: 'your_business',
metadata,
});
return order;
}
// List carbon removal suppliers
export async function listClimateSuppliers() {
const suppliers = await stripe.climate.suppliers.list({ limit: 10 });
return suppliers.data;
}
// Create metered price
export async function createMeteredPrice(productId: string) {
const price = await stripe.prices.create({
product: productId,
currency: 'usd',
recurring: {
interval: 'month',
usage_type: 'metered', // Charge based on usage
},
billing_scheme: 'tiered', // or 'per_unit'
tiers_mode: 'graduated',
tiers: [
{ up_to: 1000, unit_amount: 10 }, // $0.10 per unit for first 1000
{ up_to: 5000, unit_amount: 8 }, // $0.08 per unit for 1001-5000
{ up_to: 'inf', unit_amount: 5 }, // $0.05 per unit for 5001+
],
});
return price;
}
// Report usage
export async function reportUsage(
subscriptionItemId: string,
quantity: number,
action: 'increment' | 'set' = 'increment'
) {
const usageRecord = await stripe.subscriptionItems.createUsageRecord(
subscriptionItemId,
{
quantity,
timestamp: Math.floor(Date.now() / 1000),
action, // 'increment' adds to total, 'set' overwrites
},
{ idempotencyKey: `usage_${subscriptionItemId}_${Date.now()}` }
);
return usageRecord;
}
// Get usage summary
export async function getUsageSummary(subscriptionItemId: string) {
const summary = await stripe.subscriptionItems.listUsageRecordSummaries(
subscriptionItemId,
{ limit: 1 }
);
return {
totalUsage: summary.data[0]?.total_usage || 0,
period: {
start: summary.data[0]?.period?.start,
end: summary.data[0]?.period?.end,
},
};
}
// Track API calls
export async function trackApiCall(userId: string, endpoint: string) {
const user = await getUser(userId);
if (!user.subscriptionItemId) {
throw new Error('No active subscription');
}
// Report usage to Stripe
await reportUsage(user.subscriptionItemId, 1, 'increment');
// Log locally for analytics
await logApiCall({ userId, endpoint, timestamp: new Date() });
}
// Middleware to track usage
export async function middleware(req: NextRequest) {
const session = await auth();
if (session?.user?.id) {
// Track this request as usage
await trackApiCall(session.user.id, req.nextUrl.pathname);
}
return NextResponse.next();
}
// Configure portal
export async function configurePortal() {
const configuration = await stripe.billingPortal.configurations.create({
business_profile: {
headline: 'Manage your subscription',
},
features: {
customer_update: {
enabled: true,
allowed_updates: ['email', 'address', 'shipping', 'phone', 'tax_id'],
},
invoice_history: { enabled: true },
payment_method_update: { enabled: true },
subscription_cancel: {
enabled: true,
mode: 'at_period_end',
cancellation_reason: {
enabled: true,
options: [
'too_expensive',
'missing_features',
'switched_service',
'unused',
'customer_service',
'too_complex',
'low_quality',
'other',
],
},
},
subscription_pause: {
enabled: true,
},
subscription_update: {
enabled: true,
default_allowed_updates: ['price', 'quantity', 'promotion_code'],
products: [
{
product: 'prod_basic',
prices: ['price_basic_monthly', 'price_basic_yearly'],
},
{
product: 'prod_pro',
prices: ['price_pro_monthly', 'price_pro_yearly'],
},
],
},
},
});
return configuration.id;
}
// Create portal session with config
export async function createCustomPortalSession(
customerId: string,
configId: string
) {
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_URL}/dashboard`,
configuration: configId,
});
return session.url;
}
// lib/stripe/test-helpers.ts
export async function createTestCustomer(email: string) {
if (!process.env.STRIPE_SECRET_KEY?.startsWith('sk_test_')) {
throw new Error('Test helpers only work in test mode');
}
const customer = await stripe.customers.create({
email,
metadata: { test: 'true' },
});
return customer;
}
export async function createTestSubscription(
customerId: string,
priceId: string
) {
// Use test card that won't require payment
const paymentMethod = await stripe.paymentMethods.create({
type: 'card',
card: { token: 'tok_visa' }, // Test token
});
await stripe.paymentMethods.attach(paymentMethod.id, { customer: customerId });
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
default_payment_method: paymentMethod.id,
});
return subscription;
}
// Trigger test webhook locally
export async function triggerTestWebhook(eventType: string) {
// Uses Stripe CLI: stripe trigger <event>
const { exec } = require('child_process');
exec(`stripe trigger ${eventType}`);
}
// Test card numbers for different scenarios
export const TEST_CARDS = {
// Successful payments
visa: '4242424242424242',
mastercard: '5555555555554444',
amex: '378282246310005',
discover: '6011111111111117',
// 3D Secure required
visa3DS: '4000002500003155',
visa3DSDecline: '4000008400001629',
// Declined
genericDecline: '4000000000009995',
insufficientFunds: '4000000000009987',
lostCard: '4000000000009987',
stolenCard: '4000000000009979',
expiredCard: '4000000000000069',
incorrectCVC: '4000000000000127',
processingError: '4000000000000119',
// Other scenarios
disputeWarning: '4000000000002685', // Triggers early fraud warning
alwaysDispute: '4000000000000259', // Will be disputed immediately
};
// Test with specific card
export async function testPaymentFlow(scenario: keyof typeof TEST_CARDS) {
const paymentMethod = await stripe.paymentMethods.create({
type: 'card',
card: { number: TEST_CARDS[scenario], exp_month: 12, exp_year: 2034, cvc: '123' },
});
const paymentIntent = await stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
payment_method: paymentMethod.id,
confirm: true,
});
return paymentIntent;
}
// Bad: Multiple API calls
const invoice = await stripe.invoices.retrieve('in_xxx');
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
const customer = await stripe.customers.retrieve(invoice.customer);
// Good: Single API call with expand
const invoice = await stripe.invoices.retrieve('in_xxx', {
expand: ['subscription', 'customer', 'payment_intent'],
});
// Now access directly
console.log(invoice.subscription.status);
console.log(invoice.customer.email);
console.log(invoice.payment_intent.client_secret);
// Retrieve multiple customers efficiently
export async function batchRetrieveCustomers(customerIds: string[]) {
const customers = await Promise.all(
customerIds.map(id =>
stripe.customers.retrieve(id).catch(() => null) // Handle missing customers
)
);
return customers.filter(Boolean);
}
// Use list endpoints with pagination
export async function getAllActiveSubscriptions() {
const subscriptions: Stripe.Subscription[] = [];
let hasMore = true;
let startingAfter: string | undefined;
while (hasMore) {
const page = await stripe.subscriptions.list({
status: 'active',
limit: 100,
starting_after: startingAfter,
});
subscriptions.push(...page.data);
hasMore = page.has_more;
startingAfter = page.data[page.data.length - 1]?.id;
}
return subscriptions;
}
// Cache prices (they rarely change)
import { unstable_cache } from 'next/cache';
export const getPrices = unstable_cache(
async () => {
const prices = await stripe.prices.list({
active: true,
expand: ['data.product'],
});
return prices.data;
},
['stripe-prices'],
{
revalidate: 3600, // 1 hour
tags: ['prices'],
}
);
// Invalidate cache when price changes
export async function POST(req: Request) {
const event = await verifyWebhook(req);
if (event.type === 'price.updated' || event.type === 'price.created') {
revalidateTag('prices');
}
return Response.json({ received: true });
}
Need to find something specific?
Use Grep to search the 3,253 documentation files:
# Search all docs
grep -r "search term" .Codex/skills/api/stripe/docs/
# Search API references only
grep -r "search term" .Codex/skills/api/stripe/docs/api/
# Find specific endpoint
ls .Codex/skills/api/stripe/docs/api/payment_intents/
Common doc locations:
docs/api/docs/api/payment_intents/docs/api/subscriptions/docs/api/webhook_endpoints/docs/api/accounts/Setup:
npm install stripe @stripe/stripe-js @stripe/react-stripe-jsPayment Flow:
Webhooks:
/api/webhooks)Production:
Advanced (if needed):