| name | stripe-payments |
| description | 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. |
| allowed-tools | Read, Grep, Glob |
Stripe Payments Expert
Purpose
Expert knowledge of Stripe payments infrastructure. Covers PaymentIntents, payment methods, 3D Secure authentication, refunds, Payment Element, and complete payment flow implementation.
When to Use
Invoke this skill when:
- Implementing payment acceptance
- Setting up PaymentIntents flow
- Handling payment methods (cards, wallets, bank accounts)
- Implementing 3D Secure authentication
- Processing refunds and disputes
- Integrating Payment Element
- Building checkout flows
- Handling payment webhooks
- Implementing payment confirmations
- Debugging payment issues
Documentation Available
Location: /Users/zach/Documents/cc-skills/docs/stripe/
Coverage (~436 files in payments/):
-
PaymentIntents:
- Creating and confirming payments
- Payment flow lifecycle
- Automatic payment methods
- Manual confirmation
-
Payment Methods:
- Cards (Visa, Mastercard, Amex, etc.)
- Digital wallets (Apple Pay, Google Pay)
- Bank transfers (ACH, SEPA)
- Buy now pay later (Klarna, Afterpay)
- Local payment methods
-
Authentication:
- 3D Secure (SCA compliance)
- Strong Customer Authentication
- Authentication challenges
-
Payment Element:
- Pre-built UI components
- Customization options
- Integration patterns
-
Refunds & Disputes:
- Full and partial refunds
- Refund reasons
- Dispute handling
Related Skills
- stripe-billing-subscriptions: For recurring payments
- stripe-connect: For marketplace payments
- stripe-api-integration: For webhooks and API patterns
- stripe-terminal-issuing: For in-person payments
Best Practices
- Use PaymentIntents - Not deprecated Charges API
- Handle webhooks properly - Verify signatures
- Implement idempotency - Use idempotency keys
- Test with test cards - Use official test card numbers
- Handle errors gracefully - Show user-friendly messages
- Store customer IDs - Enable saved payment methods
- Use Payment Element - Pre-built, optimized UI
- Implement SCA - Required in Europe
- Log payment attempts - For debugging and reconciliation
Examples
Example 1: Basic Payment Flow with PaymentIntents
Question: "How do I implement a basic payment flow?"
Response:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createPaymentIntent(req, res) {
try {
const { amount, currency = 'usd' } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100,
currency,
automatic_payment_methods: {
enabled: true,
},
metadata: {
orderId: '12345',
customerId: req.user.id,
},
});
res.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
}
import { loadStripe } ;
{
,
,
useStripe,
useElements,
} ;
stripePromise = (process..!);
() {
stripe = ();
elements = ();
[loading, setLoading] = ();
[error, setError] = useState< | >();
= () => {
e.();
(!stripe || !elements) ;
();
();
{ error } = stripe.({
elements,
: {
: ,
},
});
(error) {
(error. || );
();
}
};
(
);
}
() {
[clientSecret, setClientSecret] = ();
( {
(, {
: ,
: { : },
: .({ amount }),
})
.( r.())
.( (data.));
}, [amount]);
(!clientSecret) ;
(
);
}
Payment Flow:
- Create PaymentIntent on backend
- Return client_secret to frontend
- Collect payment details with Payment Element
- Confirm payment with Stripe.js
- Handle redirect for authentication (3DS)
- Webhook confirms successful payment
References:
- See:
docs/stripe/payments/payment-intents/
- See:
docs/stripe/payments/accept-a-payment/
Example 2: Payment Methods & Saved Cards
Question: "How do I save payment methods for future use?"
Response:
export async function savePaymentMethod(req, res) {
try {
const { paymentMethodId, email } = req.body;
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,
},
});
}
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customer.id,
});
res.json({ customerId: customer.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
() {
{
{ customerId, amount } = req.;
paymentIntent = stripe..({
: amount * ,
: ,
: customerId,
: [],
: ,
: {
: ,
},
});
res.({ : paymentIntent. });
} (error) {
res.().({ : error. });
}
}
() {
{
{ customerId } = req.;
paymentMethods = stripe..({
: customerId ,
: ,
});
res.({ : paymentMethods. });
} (error) {
res.().({ : error. });
}
}
() {
{
{ paymentMethodId } = req.;
stripe..(paymentMethodId);
res.({ : });
} (error) {
res.().({ : error. });
}
}
() {
[methods, setMethods] = ([]);
( {
()
.( r.())
.( (data.));
}, [customerId]);
= () => {
(, {
: ,
: { : },
: .({ : id }),
});
(methods.( m. !== id));
};
(
);
}
References:
- See:
docs/stripe/payments/payment-methods/
- See:
docs/stripe/payments/save-and-reuse/
Example 3: Refunds and Cancellations
Question: "How do I process refunds?"
Response:
export async function fullRefund(req, res) {
try {
const { paymentIntentId } = req.body;
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
reason: 'requested_by_customer',
});
res.json({ refund });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
export async function partialRefund(req, res) {
try {
const { paymentIntentId, amount } = req.body;
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
amount: amount * 100,
reason: 'requested_by_customer',
metadata: {
reason: 'Partial order cancellation',
},
});
res.json({ refund });
} catch (error) {
res.().({ : error. });
}
}
() {
{
{ paymentIntentId } = req.;
paymentIntent = stripe..(paymentIntentId);
res.({ paymentIntent });
} (error) {
res.().({ : error. });
}
}
() {
{
{ paymentIntentId } = req.;
refunds = stripe..({
: paymentIntentId ,
: ,
});
res.({ : refunds. });
} (error) {
res.().({ : error. });
}
}
() {
{
{ refundId } = req.;
refund = stripe..(refundId );
res.({ refund });
} (error) {
res.().({ : error. });
}
}
Refund Reasons:
requested_by_customer - Customer requested
duplicate - Duplicate payment
fraudulent - Fraudulent transaction
Refund Timing:
- Cards: 5-10 business days
- ACH: 5-10 business days
- Instant for some payment methods
References:
- See:
docs/stripe/payments/refunds/
Example 4: Webhooks for Payment Events
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!;
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 {
event = stripe.webhooks.constructEvent(buf, sig!, webhookSecret);
} catch (err) {
console.error(, err.);
res.().();
}
{
(event.) {
:
paymentIntent = event.. .;
(paymentIntent);
;
:
failedPayment = event.. .;
(failedPayment);
;
:
canceledPayment = event.. .;
(canceledPayment);
;
:
refund = event.. .;
(refund);
;
:
paymentMethod = event.. .;
(paymentMethod);
;
:
.();
}
res.({ : });
} (err) {
.(, err);
res.().({ : });
}
}
() {
.(, paymentIntent.);
db..({
: { : paymentIntent.. },
: {
: ,
: paymentIntent.,
},
});
({
: paymentIntent.!,
: ,
: ,
});
}
() {
.(, paymentIntent.);
lastError = paymentIntent.;
.(, lastError?.);
db..({
: { : paymentIntent.. },
: {
: ,
: lastError?.,
},
});
({
: paymentIntent.!,
: ,
: ,
});
}
() {
.(, paymentIntent.);
db..({
: { : paymentIntent.. },
: { : },
});
}
() {
.(, charge.);
db..({
: { : charge. },
: { : },
});
}
() {
.(, paymentMethod.);
db..({
: {
: paymentMethod.,
: paymentMethod. ,
: paymentMethod.,
: paymentMethod.?.,
: paymentMethod.?.,
},
});
}
Important Webhook Events:
payment_intent.succeeded - Payment successful
payment_intent.payment_failed - Payment failed
payment_intent.canceled - Payment canceled
charge.refunded - Refund processed
payment_method.attached - Payment method saved
References:
- See:
docs/stripe/webhooks/
- See:
docs/stripe/payments/handling-payment-events/
Example 5: 3D Secure Authentication
Question: "How do I handle 3D Secure authentication?"
Response:
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'],
confirmation_method: 'manual',
confirm: false,
});
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
function CheckoutWithSCA({ clientSecret }: { clientSecret: string }) {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (e: React.FormEvent) => {
e.();
(!stripe || !elements) ;
{ error, paymentIntent } = stripe.(
clientSecret,
{
: {
: elements.()!,
: {
: ,
: ,
},
},
}
);
(error) {
.(error.);
} (paymentIntent. === ) {
{ : authError } = stripe.(clientSecret);
(authError) {
.(, authError.);
} {
{ : confirmError } = stripe.(
clientSecret
);
(confirmError) {
.(confirmError.);
} {
.();
}
}
} (paymentIntent. === ) {
.();
}
};
(
);
}
() {
stripe = ();
elements = ();
= () => {
e.();
(!stripe || !elements) ;
{ error } = stripe.({
elements,
: {
: ,
},
});
(error) {
.(error.);
}
};
(
);
}
3DS States:
requires_payment_method - Needs payment method
requires_confirmation - Ready to confirm
requires_action - Needs authentication (3DS)
processing - Being processed
succeeded - Payment successful
References:
- See:
docs/stripe/payments/3d-secure/
- See:
docs/stripe/payments/strong-customer-authentication/
Common Patterns
Idempotency for Retries
const paymentIntent = await stripe.paymentIntents.create(
{
amount: 1000,
currency: 'usd',
},
{
idempotencyKey: 'order_12345',
}
);
Test Card Numbers
4242 4242 4242 4242
4000 0027 6000 3184
4000 0000 0000 0002
4000 0000 0000 9995
Payment Status Check
const paymentIntent = await stripe.paymentIntents.retrieve('pi_xxx');
if (paymentIntent.status === 'succeeded') {
}
Search Helpers
grep -r "PaymentIntent\|payment\|charge" /Users/zach/Documents/cc-skills/docs/stripe/payments/
grep -r "3D Secure\|SCA\|authentication" /Users/zach/Documents/cc-skills/docs/stripe/payments/
grep -r "refund\|cancel" /Users/zach/Documents/cc-skills/docs/stripe/payments/
ls /Users/zach/Documents/cc-skills/docs/stripe/payments/
Common Errors
-
PaymentIntent already succeeded: Trying to confirm twice
- Solution: Check status before confirming
-
Invalid API key: Wrong or missing key
- Solution: Verify STRIPE_SECRET_KEY is set
-
Customer not found: Using invalid customer ID
- Solution: Create customer first or verify ID
-
Webhook signature mismatch: Wrong secret or modified payload
- Solution: Use correct STRIPE_WEBHOOK_SECRET and raw body
Security Tips
- Never expose secret key - Use only on backend
- Verify webhook signatures - Prevent tampering
- Use HTTPS - Required for production
- Implement idempotency - Prevent duplicate charges
- Validate amounts - Server-side validation
- Log payment attempts - Audit trail
- Handle PCI compliance - Use Stripe.js/Elements
Notes
- Documentation covers latest Stripe API (2023+)
- PaymentIntents is the recommended API (not Charges)
- Payment Element handles most payment methods automatically
- Webhooks are essential for reliable payment confirmation
- Test mode uses test API keys and test card numbers
- File paths reference local documentation cache
- For latest updates, check https://stripe.com/docs/payments