skills/payments-checkout/stripe-integration/SKILL.md
Build secure payment flows with Stripe — Payment Intents, subscription billing, webhook handling, and European SCA compliance for card payments
npx skillsauth add finsilabs/awesome-ecommerce-skills stripe-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Stripe is the most widely supported payment processor across ecommerce platforms. On Shopify, it powers Shopify Payments natively. On WooCommerce and BigCommerce, official plugins provide full Stripe Checkout with minimal configuration. Custom code is only required for headless storefronts — and even then, Stripe's hosted Checkout page and Elements components remove most of the complexity.
Shopify Payments is powered by Stripe. It is the simplest possible Stripe integration:
If you need to use Stripe directly (e.g., for features Shopify Payments does not support):
Configure Stripe webhooks for Shopify: Shopify handles webhook processing internally for Shopify Payments. If using Stripe directly, register your webhook endpoint under Stripe Dashboard → Developers → Webhooks.
Enable additional payment methods:
Set up webhooks:
Enable Stripe Radar (fraud prevention): Stripe Radar is enabled by default. Configure rules under Stripe Dashboard → Radar → Rules to block or review high-risk transactions.
Enable Stripe Link (saved cards): In the BigCommerce Stripe settings, enable Stripe Link to allow returning customers to pay with one click using their saved payment details.
Install the Stripe SDK:
npm install stripe @stripe/stripe-js @stripe/react-stripe-js
Option A: Stripe Checkout (hosted page — simplest)
For the fastest integration with no payment form to build:
// Server: create a Checkout Session
const session = await stripe.checkout.sessions.create({
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: 'Order #' + orderNumber },
unit_amount: Math.round(orderTotal * 100), // cents
},
quantity: 1,
}],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/orders/{CHECKOUT_SESSION_ID}/confirmation`,
cancel_url: `${YOUR_DOMAIN}/cart`,
metadata: { order_id: orderId },
});
// Redirect customer to session.url
res.redirect(session.url);
Option B: Payment Intents with Stripe Elements (custom checkout form)
// Server: create a Payment Intent
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(orderTotal * 100), // cents
currency: 'usd',
automatic_payment_methods: { enabled: true }, // Enables all eligible methods
metadata: { order_id: orderId, customer_email: customerEmail },
});
res.json({ clientSecret: paymentIntent.client_secret });
// Client: render the payment form using Stripe Elements
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
function CheckoutForm({ onSuccess }) {
const stripe = useStripe();
const elements = useElements();
async function handleSubmit(e) {
e.preventDefault();
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: `${window.location.origin}/orders/confirmation` },
});
if (error) showError(error.message);
// On success, Stripe redirects to return_url automatically
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button type="submit" disabled={!stripe}>Pay Now</button>
</form>
);
}
export function PaymentPage({ clientSecret }) {
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm />
</Elements>
);
}
Handle webhooks for order fulfillment:
// POST /api/webhooks/stripe
// IMPORTANT: use raw body parser — do not parse as JSON
export async function handleStripeWebhook(req, res) {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody, // raw buffer — not JSON.parse'd
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'payment_intent.succeeded':
await fulfillOrder(event.data.object);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailed(event.data.object);
break;
case 'charge.refunded':
await handleRefund(event.data.object);
break;
case 'charge.dispute.created':
await handleDispute(event.data.object);
break;
}
res.json({ received: true });
}
// Always check if already fulfilled — webhooks can arrive multiple times
async function fulfillOrder(paymentIntent) {
const orderId = paymentIntent.metadata.order_id;
const order = await db.orders.findUnique({ where: { id: orderId } });
if (order.status !== 'pending') return; // Idempotency check
await db.orders.update({ where: { id: orderId }, data: { status: 'confirmed', paidAt: new Date() } });
await sendOrderConfirmationEmail(orderId);
}
Local webhook testing:
# Install Stripe CLI and forward events to your local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Copy the webhook signing secret printed by the CLI into your .env
PCI DSS scope: Using Stripe Elements or Stripe Checkout significantly reduces your PCI DSS compliance burden:
The WooCommerce Stripe plugin and BigCommerce's Stripe integration both use Stripe.js for tokenization, qualifying most merchants for SAQ A-EP.
SCA (Strong Customer Authentication) for European customers:
Using automatic_payment_methods: { enabled: true } on Payment Intents automatically handles 3D Secure challenges when required by the customer's bank. No additional configuration needed.
order_id to every Payment Intent metadata — this enables reconciliation and dispute management| Problem | Solution |
|---------|----------|
| Webhook signature verification fails | Pass the raw request body (not JSON-parsed) to constructEvent; configure your framework to skip JSON parsing for the webhook route |
| 3DS challenges not triggering | Use automatic_payment_methods: { enabled: true } instead of manually listing payment method types |
| WooCommerce Stripe plugin shows blank payment form | Check for JavaScript console errors; often caused by a CSP (Content Security Policy) blocking Stripe.js; add js.stripe.com to your CSP allowlist |
| Stripe payment shows as succeeded but order not confirmed | Webhooks are the authoritative signal — if the webhook handler has an error, the order will not be confirmed; check your webhook logs in Stripe Dashboard → Developers → Webhooks → [Event] |
| Test mode charges appearing in live data | Verify you are using live API keys in production; Stripe's Dashboard has a separate live/test toggle — confirm you are in the correct mode |
| Currency amount wrong | Stripe uses the smallest currency unit — $20.00 = 2000 cents; JPY ¥3,000 = 3000 (no multiplication needed) |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion