Stripe Payments
Implement payment acceptance using Stripe's Payment Intents API, Checkout, Elements, and Payment Links.
Priority Table
| Category | Impact | Rules | Key Concern |
|---|---|---|---|
| Setup | HIGH | 3 | Integration path, Stripe.js loading, automatic payment methods |
| Payment Intents | HIGH | 4 | Idempotency, amounts, status handling, intent reuse |
| Checkout Sessions | HIGH | 4 | Webhook fulfillment, async payments, line items, session ID |
| Payment Element | MEDIUM | 4 | Return URL, submit state, wallet dedup, billing details |
| Security | HIGH | 4 | Client secret, SCA optimization, off-session recovery, metadata |
| Error Handling | HIGH | 3 | User messages, retry strategy, decline classification |
| Webhooks | HIGH | 3 | Signature verification, idempotent handlers, fast responses |
Integration Decision Tree
How much UI control do you need?
|
+-- Minimal code / hosted page?
| -> Checkout Sessions (references/checkout.md)
|
+-- No code at all?
| -> Payment Links (references/payment-links.md)
|
+-- Embedded in your app with Stripe UI?
| -> Payment Element (references/payment-element.md)
|
+-- Full custom UI?
-> Payment Intents + confirm on client (references/payment-intents.md)
Quick Reference
Setup Rules
- setup-automatic-payment-methods - Use
automatic_payment_methods: { enabled: true }instead of hardcodingpayment_method_types - setup-load-stripe-js - Always load Stripe.js from
js.stripe.com, never self-host or bundle - setup-choose-integration - Use Checkout Sessions for most apps; Payment Element for custom UX; Payment Links for no-code
Payment Intent Rules
- intent-use-idempotency-keys - Always include idempotency keys on create, capture, and refund; derive from business IDs
- intent-reuse-payment-intents - Update existing PaymentIntents on cart changes instead of creating new ones
- intent-handle-all-statuses - Handle
succeeded,processing,requires_action,requires_payment_method,requires_capture - intent-amounts-in-cents - Pass amounts in smallest currency unit (cents): $20.00 =
amount: 2000
Checkout Session Rules
- checkout-webhook-fulfillment - Always use
checkout.session.completedwebhook for fulfillment, never the success URL - checkout-handle-async-payments - Check
payment_statusin completed event; listen forasync_payment_succeededfor bank transfers - checkout-retrieve-line-items - Call
listLineItems()separately; webhook payload does not include them - checkout-include-session-id - Include
{CHECKOUT_SESSION_ID}in success URL for status verification
Payment Element Rules
- element-provide-return-url - Always provide
return_urlinconfirmPayment(); redirect-based methods fail without it - element-disable-submit-button - Disable submit button during processing to prevent double submissions
- element-deduplicate-wallets - Set
wallets: { applePay: 'never', googlePay: 'never' }when using Express Checkout Element - element-collect-billing-separately - If hiding billing fields, supply them in
confirmParams.payment_method_data.billing_details
Security Rules
- security-protect-client-secret - Never log, URL-embed, or persist
client_secret; deliver via HTTPS JSON only - security-sca-setup-future-usage - Set
setup_future_usage: 'off_session'when saving cards for off-session charging - security-off-session-recovery - Implement recovery flow for
authentication_requirederrors on off-session payments - security-no-sensitive-metadata - Never store PII, card numbers, passwords, or API keys in metadata
Error Handling Rules
- error-never-expose-raw-errors - Show user-friendly messages; hide sensitive decline codes (
fraudulent,lost_card,stolen_card) - error-retry-with-backoff - Retry only connection/API/rate-limit errors with exponential backoff + jitter + idempotency keys
- error-classify-decline-codes - Route retryable declines (incorrect CVC) differently from terminal ones (insufficient funds)
Webhook Rules
- webhook-verify-signatures - Use
express.raw()for webhook route; JSON-parsed bodies fail signature verification - webhook-idempotent-handlers - Check if event was already processed before fulfilling; Stripe retries on failure
- webhook-return-200-quickly - Acknowledge webhooks immediately; process heavy tasks asynchronously via job queue
Quick Start: Checkout Session (Recommended for MVP)
Server
const session = await stripe.checkout.sessions.create({
line_items: [{ price: 'price_xxx', quantity: 1 }],
mode: 'payment', // or 'subscription' or 'setup'
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel',
});
// Redirect customer to session.url
Webhook
// Listen for checkout.session.completed
case 'checkout.session.completed':
const session = event.data.object;
await fulfillOrder(session);
break;
Quick Start: Payment Element (Recommended for Custom UX)
Server - Create PaymentIntent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000, // $20.00 in cents
currency: 'usd',
automatic_payment_methods: { enabled: true },
});
return { clientSecret: paymentIntent.client_secret };
Client - Mount Payment Element
import { loadStripe } from '@stripe/stripe-js';
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
const stripePromise = loadStripe('pk_test_xxx');
function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (e) => {
e.preventDefault();
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: 'https://example.com/complete' },
});
if (error) setMessage(error.message);
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button>Pay</button>
</form>
);
}
function App({ clientSecret }) {
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm />
</Elements>
);
}
Payment Intent Lifecycle
requires_payment_method -> requires_confirmation -> requires_action -> processing -> succeeded
-> requires_payment_method (retry)
-> canceled
Key states:
requires_action- 3DS/SCA authentication needed (handled bystripe.confirmPayment())processing- async payment methods (bank debits) still settlingsucceeded- funds captured, safe to fulfill
Critical Webhooks
| Event | When | Action |
|---|---|---|
payment_intent.succeeded | Payment confirmed | Fulfill order |
payment_intent.payment_failed | Payment declined | Notify customer |
payment_intent.requires_action | Needs 3DS | Client handles automatically |
charge.dispute.created | Chargeback filed | See money-management reference |
checkout.session.completed | Checkout finished | Fulfill + redirect |
How to Use This Skill
- Start here - Use the decision tree above to pick your integration path
- Follow rules - Check the relevant rules in
rules/for correct implementation patterns - Deep dive - Consult
references/for comprehensive documentation on specific topics - Test - Use Stripe test cards (
4242 4242 4242 4242for success,4000 0025 0000 3155for 3DS)
References
- references/payment-intents.md - PaymentIntent lifecycle, confirmation, captures, cancellation
- references/checkout.md - Checkout Sessions, line items, modes, customization
- references/payment-element.md - Elements setup, appearance API, payment method config
- references/payment-links.md - No-code payment pages, configuration
- references/payment-methods.md - Cards, wallets (Apple/Google Pay), bank debits, BNPL
- references/3d-secure.md - SCA compliance, 3DS flows, exemptions
- references/error-handling.md - Error types, decline codes, retry strategies