skills/payments-checkout/paypal-integration/SKILL.md
Add PayPal, Venmo, and Pay Later buttons to your store using the PayPal Commerce Platform SDK with Express Checkout for one-tap buying
npx skillsauth add finsilabs/awesome-ecommerce-skills paypal-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.
PayPal Checkout lets shoppers pay using their PayPal balance, Venmo, Pay Later (installments), or a card processed by PayPal. It is particularly valuable in Germany, Netherlands, and Brazil where PayPal is the dominant payment method, and in any market where customers prefer not to enter card details. All major platforms have official PayPal integrations that require only credentials — no custom code needed for standard checkout.
PayPal Express Checkout is built into Shopify and is enabled by default on most stores.
To verify or reconfigure:
Enable Venmo and Pay Later:
PayPal Checkout (alternative, older integration): The older WooCommerce PayPal Checkout plugin still works but the newer WooCommerce PayPal Payments plugin is recommended for all new setups.
Alternatively, BigCommerce supports PayPal Commerce Platform — go to Settings → Payment Methods and choose PayPal Commerce Platform for access to the full suite of PayPal payment options.
For headless storefronts, use the PayPal JavaScript SDK with the Orders API v2:
Load the PayPal JavaScript SDK:
<!-- Always load from the CDN — never install as an npm package -->
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&intent=capture&components=buttons"></script>
Create a PayPal order server-side:
// POST /api/paypal/create-order
async function createPayPalOrder(req, res) {
const { cartId } = req.body;
const cart = await db.carts.findUnique({ where: { id: cartId } });
const accessToken = await getPayPalAccessToken();
const orderRes = await fetch('https://api-m.paypal.com/v2/checkout/orders', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'PayPal-Request-Id': cartId, // Idempotency key
},
body: JSON.stringify({
intent: 'CAPTURE',
purchase_units: [{
reference_id: cartId,
amount: {
currency_code: 'USD',
value: cart.total.toFixed(2),
},
}],
}),
});
const order = await orderRes.json();
res.json({ id: order.id });
}
Render PayPal buttons and capture payment:
useEffect(() => {
if (!window.paypal) return;
window.paypal.Buttons({
style: { layout: 'vertical', color: 'gold', shape: 'rect' },
createOrder: async () => {
const res = await fetch('/api/paypal/create-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cartId }),
});
const data = await res.json();
return data.id; // PayPal Order ID
},
onApprove: async (data) => {
// Always capture server-side — never trust client-side only
const res = await fetch('/api/paypal/capture-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: data.orderID, cartId }),
});
const result = await res.json();
if (result.success) router.push(`/orders/${result.shopOrderId}/confirmation`);
},
onCancel: () => {
// User closed PayPal popup — leave cart intact, show no error
},
onError: (err) => {
console.error('PayPal error:', err);
setError('PayPal encountered an error. Please try again or use a card.');
},
}).render('#paypal-button-container');
}, [cartId]);
Capture the payment server-side:
// POST /api/paypal/capture-order
async function capturePayPalOrder(req, res) {
const { orderId, cartId } = req.body;
const accessToken = await getPayPalAccessToken();
const captureRes = await fetch(`https://api-m.paypal.com/v2/checkout/orders/${orderId}/capture`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'PayPal-Request-Id': `capture-${orderId}`, // Idempotency key
},
});
const capture = await captureRes.json();
if (capture.status !== 'COMPLETED') {
return res.status(400).json({ error: 'Payment capture failed' });
}
// Create your internal order record
const order = await createOrderFromCart(cartId, {
paymentMethod: 'paypal',
paypalOrderId: orderId,
paypalCaptureId: capture.purchase_units[0].payments.captures[0].id,
});
res.json({ success: true, shopOrderId: order.id });
}
Handle PayPal webhooks:
Register webhooks in your PayPal developer dashboard for: PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.REFUNDED, PAYMENT.CAPTURE.REVERSED. Verify webhook signatures using PayPal's signature verification API before processing.
PAYPAL_CLIENT_ID=<sandbox-client-id>
PAYPAL_CLIENT_SECRET=<sandbox-client-secret>
PAYPAL_API_BASE=https://api-m.sandbox.paypal.com
onApprove client callback alone; always capture using the Orders API from your serverPayPal-Request-Id header — this idempotency key prevents double charges if a request is retriedonCancel gracefully — when users close the PayPal popup, do not show an error; just let them try again or choose another payment methodpaypal-transmission-sig header| Problem | Solution |
|---------|----------|
| "This seller doesn't accept payments" error | Ensure your PayPal account has completed seller onboarding (email verification, bank account linked); also verify sandbox vs. production credentials match |
| PayPal popup blocked on some browsers | The createOrder callback must return a value immediately from a user click event; any async delay can trigger popup blockers — move server calls before the button renders or use PayPal's built-in API |
| Order confirmed by webhook before capture completes | Use the PAYMENT.CAPTURE.COMPLETED webhook (not CHECKOUT.ORDER.APPROVED); the latter fires before funds are captured |
| Duplicate order on webhook retry | Check for an existing order with the PayPal capture ID before creating a new one |
| WooCommerce PayPal plugin not showing Venmo/Pay Later | Venmo and Pay Later must be enabled in your PayPal Business account settings (not just the plugin) — check Account Settings → Payment Preferences in PayPal |
| Shopify PayPal not processing after reconnection | Disconnect and reconnect your PayPal account in Shopify → Settings → Payments; old OAuth tokens sometimes expire |
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