supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
stripe-payments
Use when processing payments, handling payment methods, implementing PaymentIntents, or integrating Payment Element. Invoke for payment flow setup, 3D Secure authentication, refund processing, payment method management, or checkout implementation.
Full skill instructions
Expert knowledge of Stripe payments infrastructure. Covers PaymentIntents, payment methods, 3D Secure authentication, refunds, Payment Element, and complete payment flow implementation.
Invoke this skill when:
Location: /Users/zach/Documents/cc-skills/docs/stripe/
Coverage (~436 files in payments/):
PaymentIntents:
Payment Methods:
Authentication:
Payment Element:
Refunds & Disputes:
Question: "How do I implement a basic payment flow?"
Response:
// Backend: Create PaymentIntent
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// POST /api/create-payment-intent
export async function createPaymentIntent(req, res) {
try {
const { amount, currency = 'usd' } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100, // Convert to cents
currency,
automatic_payment_methods: {
enabled: true, // Enable all available payment methods
},
metadata: {
orderId: '12345',
customerId: req.user.id,
},
});
res.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Frontend: Confirm payment with Stripe.js
import { loadStripe } from '@stripe/stripe-js';
import {
Elements,
PaymentElement,
useStripe,
useElements,
} from '@stripe/react-stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
function CheckoutForm({ clientSecret }: { clientSecret: string }) {
const stripe = useStripe();
const elements = useElements();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setLoading(true);
setError(null);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/payment/success`,
},
});
if (error) {
setError(error.message || 'Payment failed');
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button type="submit" disabled={!stripe || loading}>
{loading ? 'Processing...' : 'Pay now'}
</button>
{error && <div className="error">{error}</div>}
</form>
);
}
export function CheckoutPage({ amount }: { amount: number }) {
const [clientSecret, setClientSecret] = useState('');
useEffect(() => {
// Create PaymentIntent on mount
fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount }),
})
.then((r) => r.json())
.then((data) => setClientSecret(data.clientSecret));
}, [amount]);
if (!clientSecret) return <div>Loading...</div>;
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm clientSecret={clientSecret} />
</Elements>
);
}
Payment Flow:
References:
docs/stripe/payments/payment-intents/docs/stripe/payments/accept-a-payment/Question: "How do I save payment methods for future use?"
Response:
// Backend: Create customer and attach payment method
export async function savePaymentMethod(req, res) {
try {
const { paymentMethodId, email } = req.body;
// Create or retrieve customer
let customer;
const existingCustomer = await stripe.customers.list({
email,
limit: 1,
});
if (existingCustomer.data.length > 0) {
customer = existingCustomer.data[0];
} else {
customer = await stripe.customers.create({
email,
payment_method: paymentMethodId,
invoice_settings: {
default_payment_method: paymentMethodId,
},
});
}
// Attach payment method to customer
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customer.id,
});
res.json({ customerId: customer.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Create PaymentIntent with saved payment method
export async function chargeCustomer(req, res) {
try {
const { customerId, amount } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100,
currency: 'usd',
customer: customerId,
payment_method_types: ['card'],
setup_future_usage: 'off_session', // Save for future use
metadata: {
orderId: '12345',
},
});
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// List customer payment methods
export async function listPaymentMethods(req, res) {
try {
const { customerId } = req.query;
const paymentMethods = await stripe.paymentMethods.list({
customer: customerId as string,
type: 'card',
});
res.json({ paymentMethods: paymentMethods.data });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Delete payment method
export async function deletePaymentMethod(req, res) {
try {
const { paymentMethodId } = req.body;
await stripe.paymentMethods.detach(paymentMethodId);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Frontend: Show saved payment methods
function SavedPaymentMethods({ customerId }: { customerId: string }) {
const [methods, setMethods] = useState([]);
useEffect(() => {
fetch(`/api/payment-methods?customerId=${customerId}`)
.then(r => r.json())
.then(data => setMethods(data.paymentMethods));
}, [customerId]);
const handleDelete = async (id: string) => {
await fetch('/api/payment-methods', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodId: id }),
});
setMethods(methods.filter(m => m.id !== id));
};
return (
<div>
{methods.map(method => (
<div key={method.id}>
{method.card.brand} •••• {method.card.last4}
<button onClick={() => handleDelete(method.id)}>Remove</button>
</div>
))}
</div>
);
}
References:
docs/stripe/payments/payment-methods/docs/stripe/payments/save-and-reuse/Question: "How do I process refunds?"
Response:
// Full refund
export async function fullRefund(req, res) {
try {
const { paymentIntentId } = req.body;
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
reason: 'requested_by_customer', // or 'duplicate', 'fraudulent'
});
res.json({ refund });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Partial refund
export async function partialRefund(req, res) {
try {
const { paymentIntentId, amount } = req.body;
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
amount: amount * 100, // Refund specific amount
reason: 'requested_by_customer',
metadata: {
reason: 'Partial order cancellation',
},
});
res.json({ refund });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Cancel PaymentIntent (before capture)
export async function cancelPayment(req, res) {
try {
const { paymentIntentId } = req.body;
const paymentIntent = await stripe.paymentIntents.cancel(paymentIntentId);
res.json({ paymentIntent });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// List refunds
export async function listRefunds(req, res) {
try {
const { paymentIntentId } = req.query;
const refunds = await stripe.refunds.list({
payment_intent: paymentIntentId as string,
limit: 10,
});
res.json({ refunds: refunds.data });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Check refund status
export async function getRefund(req, res) {
try {
const { refundId } = req.query;
const refund = await stripe.refunds.retrieve(refundId as string);
res.json({ refund });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Refund Reasons:
requested_by_customer - Customer requestedduplicate - Duplicate paymentfraudulent - Fraudulent transactionRefund Timing:
References:
docs/stripe/payments/refunds/Question: "How do I handle payment webhooks?"
Response:
import { buffer } from 'micro';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
// Disable body parser (Next.js API route)
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).end();
}
const buf = await buffer(req);
const sig = req.headers['stripe-signature'];
let event: Stripe.Event;
try {
// Verify webhook signature
event = stripe.webhooks.constructEvent(buf, sig!, webhookSecret);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle event
try {
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handlePaymentSuccess(paymentIntent);
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object as Stripe.PaymentIntent;
await handlePaymentFailure(failedPayment);
break;
case 'payment_intent.canceled':
const canceledPayment = event.data.object as Stripe.PaymentIntent;
await handlePaymentCanceled(canceledPayment);
break;
case 'charge.refunded':
const refund = event.data.object as Stripe.Charge;
await handleRefund(refund);
break;
case 'payment_method.attached':
const paymentMethod = event.data.object as Stripe.PaymentMethod;
await handlePaymentMethodAttached(paymentMethod);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
} catch (err) {
console.error('Error handling webhook:', err);
res.status(500).json({ error: 'Webhook handler failed' });
}
}
async function handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) {
console.log('Payment succeeded:', paymentIntent.id);
// Update order status
await db.orders.update({
where: { id: paymentIntent.metadata.orderId },
data: {
status: 'paid',
paymentIntentId: paymentIntent.id,
},
});
// Send confirmation email
await sendEmail({
to: paymentIntent.receipt_email!,
subject: 'Payment Confirmed',
body: 'Your payment was successful!',
});
}
async function handlePaymentFailure(paymentIntent: Stripe.PaymentIntent) {
console.log('Payment failed:', paymentIntent.id);
const lastError = paymentIntent.last_payment_error;
console.log('Error:', lastError?.message);
// Update order status
await db.orders.update({
where: { id: paymentIntent.metadata.orderId },
data: {
status: 'payment_failed',
errorMessage: lastError?.message,
},
});
// Send failure email
await sendEmail({
to: paymentIntent.receipt_email!,
subject: 'Payment Failed',
body: 'Your payment could not be processed. Please try again.',
});
}
async function handlePaymentCanceled(paymentIntent: Stripe.PaymentIntent) {
console.log('Payment canceled:', paymentIntent.id);
await db.orders.update({
where: { id: paymentIntent.metadata.orderId },
data: { status: 'canceled' },
});
}
async function handleRefund(charge: Stripe.Charge) {
console.log('Refund processed:', charge.id);
await db.orders.update({
where: { paymentIntentId: charge.payment_intent as string },
data: { status: 'refunded' },
});
}
async function handlePaymentMethodAttached(paymentMethod: Stripe.PaymentMethod) {
console.log('Payment method attached:', paymentMethod.id);
// Store payment method details
await db.paymentMethods.create({
data: {
id: paymentMethod.id,
customerId: paymentMethod.customer as string,
type: paymentMethod.type,
last4: paymentMethod.card?.last4,
brand: paymentMethod.card?.brand,
},
});
}
Important Webhook Events:
payment_intent.succeeded - Payment successfulpayment_intent.payment_failed - Payment failedpayment_intent.canceled - Payment canceledcharge.refunded - Refund processedpayment_method.attached - Payment method savedReferences:
docs/stripe/webhooks/docs/stripe/payments/handling-payment-events/Question: "How do I handle 3D Secure authentication?"
Response:
// Backend: PaymentIntent with SCA
export async function createPaymentWithSCA(req, res) {
try {
const { amount, currency, customerId } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100,
currency,
customer: customerId,
payment_method_types: ['card'],
// Explicitly request authentication
confirmation_method: 'manual',
confirm: false,
});
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Frontend: Handle authentication
function CheckoutWithSCA({ clientSecret }: { clientSecret: string }) {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
// Confirm payment (may trigger 3DS)
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: elements.getElement('card')!,
billing_details: {
name: 'Customer Name',
email: '[email protected]',
},
},
}
);
if (error) {
// Show error (authentication failed)
console.error(error.message);
} else if (paymentIntent.status === 'requires_action') {
// Handle additional authentication
const { error: authError } = await stripe.handleCardAction(clientSecret);
if (authError) {
console.error('Authentication failed:', authError.message);
} else {
// Payment authenticated, confirm again
const { error: confirmError } = await stripe.confirmCardPayment(
clientSecret
);
if (confirmError) {
console.error(confirmError.message);
} else {
// Payment successful
console.log('Payment succeeded!');
}
}
} else if (paymentIntent.status === 'succeeded') {
// Payment successful without additional auth
console.log('Payment succeeded!');
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement />
<button type="submit">Pay</button>
</form>
);
}
// Automatic handling with Payment Element (recommended)
function CheckoutAutoSCA({ clientSecret }: { clientSecret: string }) {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
// Payment Element handles 3DS automatically
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/payment/success`,
},
});
if (error) {
console.error(error.message);
}
// Success handled by return_url
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button type="submit">Pay</button>
</form>
);
}
3DS States:
requires_payment_method - Needs payment methodrequires_confirmation - Ready to confirmrequires_action - Needs authentication (3DS)processing - Being processedsucceeded - Payment successfulReferences:
docs/stripe/payments/3d-secure/docs/stripe/payments/strong-customer-authentication/const paymentIntent = await stripe.paymentIntents.create(
{
amount: 1000,
currency: 'usd',
},
{
idempotencyKey: 'order_12345', // Prevents duplicate charges
}
);
// Success
4242 4242 4242 4242
// Requires authentication (3DS)
4000 0027 6000 3184
// Declined
4000 0000 0000 0002
// Insufficient funds
4000 0000 0000 9995
const paymentIntent = await stripe.paymentIntents.retrieve('pi_xxx');
if (paymentIntent.status === 'succeeded') {
// Payment complete
}
# Find payment docs
grep -r "PaymentIntent\|payment\|charge" /Users/zach/Documents/cc-skills/docs/stripe/payments/
# Find authentication docs
grep -r "3D Secure\|SCA\|authentication" /Users/zach/Documents/cc-skills/docs/stripe/payments/
# Find refund docs
grep -r "refund\|cancel" /Users/zach/Documents/cc-skills/docs/stripe/payments/
# List payment files
ls /Users/zach/Documents/cc-skills/docs/stripe/payments/
PaymentIntent already succeeded: Trying to confirm twice
Invalid API key: Wrong or missing key
Customer not found: Using invalid customer ID
Webhook signature mismatch: Wrong secret or modified payload
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content