
Paypal Integration
- 60 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Add PayPal, Venmo, and Pay Later buttons using the PayPal Commerce Platform SDK with Express Checkout for one-tap buying.
About
Integrates PayPal, Venmo, and Pay Later via the PayPal Commerce Platform SDK with Express Checkout. A developer uses it to offer PayPal-family payment options and faster checkout.
- PayPal, Venmo, and Pay Later buttons via the Commerce Platform SDK
- Express Checkout for one-tap buying
Paypal Integration by the numbers
- 60 all-time installs (skills.sh)
- Ranked #3,161 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill paypal-integrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 60 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Add PayPal, Venmo, and Pay Later buttons using the PayPal Commerce Platform SDK with Express Checkout for one-tap buying.
Files
PayPal Integration
Overview
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.
When to Use This Skill
- When adding PayPal as a payment option alongside a card processor
- When implementing PayPal Express Checkout on the product page or cart (reduces steps to purchase)
- When targeting markets where PayPal is the dominant payment method (Germany, Netherlands, Brazil)
- When adding Venmo or Pay Later (installments) to appeal to younger shoppers
Core Instructions
Step 1: Create and configure your PayPal Business account
1. Sign up for a PayPal Business account at paypal.com/business if you do not already have one 2. Verify your business identity (required before you can receive payments) 3. For development and testing: sign up at developer.paypal.com and create sandbox buyer/seller accounts 4. Create an app at developer.paypal.com → My Apps → Create App to get your Client ID and Secret
Step 2: Install PayPal on your platform
---
Shopify
PayPal Express Checkout is built into Shopify and is enabled by default on most stores.
To verify or reconfigure: 1. Go to Settings → Payments 2. Under Payment providers, click PayPal or Activate PayPal 3. If not already connected, click Activate and log in with your PayPal Business account to link it 4. Choose between:
- PayPal Express Checkout: the classic checkout flow (recommended)
- PayPal Complete Payments: a newer integration that also supports Venmo and Pay Later
Enable Venmo and Pay Later: 1. In your PayPal Business dashboard, go to Account Settings → Payment Preferences 2. Enable Venmo and Pay Later — these appear automatically in the Shopify checkout once activated on your PayPal account 3. To show a Pay Later banner on product pages: install the PayPal Pay Later Messaging app from the Shopify App Store
WooCommerce
1. Install the WooCommerce PayPal Payments plugin (the official plugin maintained by WooCommerce/PayPal — available free from WordPress.org) 2. Go to WooCommerce → Settings → Payments → PayPal Payments → Set Up 3. Click Connect to PayPal and log in with your PayPal Business account (OAuth setup — no need to copy/paste API keys) 4. The plugin automatically enables: PayPal Standard, Venmo, Pay Later, card fields, and the PayPal Credit option 5. Configure button placement under WooCommerce → Settings → Payments → PayPal Payments → Smart Payment Buttons:
- Enable buttons on: product pages, cart page, checkout page
- Configure button color (gold, blue, silver, white, black)
6. To show Pay Later messaging on product pages: go to Pay Later Messaging section in the plugin settings and enable it
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.
BigCommerce
1. Go to Settings → Payment Methods → Online Payment Methods 2. Find PayPal Powered by Braintree and click Set Up 3. Click Connect with PayPal and authorize with your PayPal Business account 4. Enable the payment methods you want: PayPal, Venmo, Pay Later 5. Configure which pages show the PayPal buttons in the configuration panel
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.
---
Custom / Headless
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.
Step 3: Test with PayPal sandbox
1. Go to developer.paypal.com → Sandbox → Accounts and use the pre-created sandbox buyer and seller accounts 2. Set environment variables to use sandbox:
PAYPAL_CLIENT_ID=<sandbox-client-id>
PAYPAL_CLIENT_SECRET=<sandbox-client-secret>
PAYPAL_API_BASE=https://api-m.sandbox.paypal.com3. Test a full purchase flow using the sandbox buyer account credentials 4. Verify the webhook fires and your handler processes it correctly
Best Practices
- Always capture server-side — do not trust the
onApproveclient callback alone; always capture using the Orders API from your server - Use `PayPal-Request-Id` header — this idempotency key prevents double charges if a request is retried
- Enable Pay Later messaging on product pages — showing "4 interest-free payments of $X" before checkout increases AOV; the WooCommerce plugin has this built in
- Handle `onCancel` gracefully — when users close the PayPal popup, do not show an error; just let them try again or choose another payment method
- Verify webhook signatures — PayPal can call your webhook endpoint with forged payloads; always verify the
paypal-transmission-sigheader
Common Pitfalls
| 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 |
Related Skills
- @stripe-integration
- @checkout-flow-optimization
- @order-processing-pipeline
- @buy-now-pay-later
{
"context": "Tests whether the agent loads the PayPal SDK correctly (CDN vs npm), uses a singleton loader pattern for SPAs, configures the SDK script with required query parameters, applies the correct button style options, and handles the cancel callback without showing an error.",
"type": "weighted_checklist",
"checklist": [
{
"name": "CDN script tag (not npm)",
"max_score": 12,
"description": "The PayPal SDK is loaded via a <script> tag pointing to https://www.paypal.com/sdk/js, NOT via an npm install or import from a node_modules package (no 'npm install @paypal' or 'import ... from @paypal/...' or 'require(\"@paypal\")')"
},
{
"name": "client-id in SDK URL",
"max_score": 8,
"description": "The SDK script URL includes a client-id query parameter (e.g. ?client-id=...)"
},
{
"name": "intent=capture in SDK URL",
"max_score": 8,
"description": "The SDK script URL includes intent=capture as a query parameter"
},
{
"name": "components param in SDK URL",
"max_score": 8,
"description": "The SDK script URL includes a components query parameter (e.g. components=buttons or components=buttons,funding-eligibility)"
},
{
"name": "Singleton loader pattern",
"max_score": 14,
"description": "The dynamic SDK loader uses a module-level variable (e.g. sdkPromise or similar) to cache the loading promise, so the script is only appended to the DOM once even if called multiple times"
},
{
"name": "Button layout vertical",
"max_score": 8,
"description": "The PayPal Buttons style includes layout: 'vertical'"
},
{
"name": "Button color gold",
"max_score": 8,
"description": "The PayPal Buttons style includes color: 'gold'"
},
{
"name": "Button shape rect",
"max_score": 8,
"description": "The PayPal Buttons style includes shape: 'rect'"
},
{
"name": "Button label paypal",
"max_score": 8,
"description": "The PayPal Buttons style includes label: 'paypal'"
},
{
"name": "onCancel no error shown",
"max_score": 10,
"description": "The onCancel callback does NOT call the error handler or display an error message to the user — it either does nothing, logs silently, or simply leaves the user on the checkout page"
},
{
"name": "Buttons cleanup on unmount",
"max_score": 8,
"description": "The React component closes or destroys the PayPal buttons instance when the component unmounts (e.g. calls buttons.close() in the useEffect cleanup function)"
}
]
}
Add PayPal Checkout to a React Storefront
Problem/Feature Description
A small independent bookshop has built a React storefront using Next.js. They currently accept payments via Stripe, but their analytics show that a significant share of their repeat customers prefer PayPal — several have abandoned checkout after not seeing the option. The team wants to add PayPal as a second payment method on the checkout page without disrupting the existing Stripe flow.
The checkout page is a standard React component. The team is concerned about page load performance: the checkout page is visited by returning customers who often already have their cart ready, so they don't want any payment SDK loaded on pages that don't need it. They also want to make sure the integration is production-ready and won't cause strange bugs if a user navigates away and back to the checkout page multiple times in the same session.
Output Specification
Produce a self-contained implementation of the PayPal checkout integration for this React/Next.js storefront. Your output should include:
1. `lib/loadPaypalSDK.js` — a utility that loads the PayPal JavaScript SDK on demand 2. `components/PayPalButtons.jsx` — a React component that renders the PayPal payment buttons and handles the checkout flow. Assume the component receives these props:
cartId(string): the ID of the current cartonSuccess(result): callback called after a successful payment, receiving{ orderId }onError(message): callback called on payment error- The component should POST to
/api/paypal/create-order(with{ cartId }) to create an order, and POST to/api/paypal/capture-order(with{ orderId, cartId }) to capture it
3. `implementation-notes.md` — a short document explaining any key decisions in your implementation, including how the SDK is loaded and why
Assume NEXT_PUBLIC_PAYPAL_CLIENT_ID is available as an environment variable.
{
"context": "Tests whether the agent implements the PayPal order creation and capture server endpoints correctly: using the Orders API v2, including a breakdown in the order amount, using PayPal-Request-Id for idempotency, capturing server-side, checking capture completion status, extracting the capture ID from the correct path, and supporting both sandbox and production environments via environment variable.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Orders API v2 endpoint",
"max_score": 8,
"description": "The create-order call uses POST /v2/checkout/orders (not v1 or another path)"
},
{
"name": "intent CAPTURE",
"max_score": 8,
"description": "The order payload includes intent: 'CAPTURE'"
},
{
"name": "Amount breakdown",
"max_score": 10,
"description": "The order's purchase_units amount includes a breakdown object with at minimum item_total (and optionally shipping and tax_total fields)"
},
{
"name": "Create-order idempotency key",
"max_score": 10,
"description": "The create-order POST request includes a PayPal-Request-Id header"
},
{
"name": "Capture-order idempotency key",
"max_score": 8,
"description": "The capture-order POST request also includes a distinct PayPal-Request-Id header (e.g. prefixed differently from the create-order key)"
},
{
"name": "Server-side capture",
"max_score": 10,
"description": "Capturing the payment is done via a server-side API call to /v2/checkout/orders/{id}/capture, NOT by the client calling PayPal directly"
},
{
"name": "Capture status check",
"max_score": 10,
"description": "After the capture call, the code checks that capture.status === 'COMPLETED' before treating the payment as successful"
},
{
"name": "Capture ID extraction path",
"max_score": 10,
"description": "The captureId is extracted from capture.purchase_units[0].payments.captures[0].id (correct nested path)"
},
{
"name": "Environment variable for API base",
"max_score": 10,
"description": "The PayPal API base URL is read from an environment variable (e.g. PAYPAL_API_BASE or PAYPAL_ENVIRONMENT) rather than hardcoded, enabling sandbox/production toggling"
},
{
"name": "PayPal order ID stored",
"max_score": 8,
"description": "After creating the PayPal order, the PayPal order ID is stored on the cart or order record (e.g. paypalOrderId field) for reconciliation"
},
{
"name": "OAuth token via client credentials",
"max_score": 8,
"description": "The server fetches a PayPal access token using Basic auth with client credentials (POST /v1/oauth2/token with grant_type=client_credentials)"
}
]
}
Implement PayPal Payment Server Endpoints
Problem/Feature Description
A mid-size outdoor gear retailer is adding PayPal as a payment option to their existing Node.js/Express e-commerce backend. Their frontend team has already wired up the PayPal JavaScript SDK to call two server-side endpoints: one to create a PayPal order when a customer clicks "Pay with PayPal", and one to capture the funds once the customer approves the payment in the PayPal popup.
The engineering lead is particularly worried about two failure modes they've seen with other integrations: (1) duplicate charges when a network timeout causes the client to retry a request, and (2) situations where a payment shows as "approved" on the frontend but the funds were never actually settled. She wants the implementation to be resilient to both.
The company also runs a staging environment against PayPal's sandbox and a production environment against the live API — the same codebase must work in both without code changes, controlled only by environment variables.
Output Specification
Produce the two server-side endpoint handlers as standalone JavaScript files:
1. `api/paypal/create-order.js` — handles POST /api/paypal/create-order. Expects { cartId } in the request body. Should return { id: "<paypalOrderId>" } on success, or an appropriate error response. Use the following mock cart object in your implementation (you don't need a real database):
// Mock cart — use this directly in your implementation
const mockCart = {
id: "cart_abc123",
total: 89.97,
subtotal: 79.99,
shippingCost: 5.99,
taxAmount: 3.99,
items: [
{
unitPrice: 39.99,
quantity: 1,
variant: { name: "Trail Running Shoes - Size 10", sku: "TRS-10" }
},
{
unitPrice: 39.99,
quantity: 1,
variant: { name: "Merino Wool Socks - M", sku: "MWS-M" }
}
]
};2. `api/paypal/capture-order.js` — handles POST /api/paypal/capture-order. Expects { orderId, cartId } in the request body. Should confirm the payment is fully settled, record the relevant payment identifiers, and return { shopOrderId, captureId }.
3. `implementation-notes.md` — a short document explaining the key design decisions, including how you handle retries and environment switching.
Both files should read PayPal credentials from environment variables. You do not need to implement a real database layer — use console.log or a mock to represent persistence calls.
{
"context": "Tests whether the agent implements a PayPal webhook handler that correctly verifies webhook signatures, uses the appropriate event types for fulfillment (PAYMENT.CAPTURE.COMPLETED rather than CHECKOUT.ORDER.APPROVED), handles refund and chargeback events, and implements idempotency based on the PayPal capture ID to prevent duplicate order creation on webhook retries.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Signature verification call",
"max_score": 12,
"description": "The webhook handler calls PayPal's /v1/notifications/verify-webhook-signature endpoint to verify the incoming request before processing"
},
{
"name": "Signature headers forwarded",
"max_score": 10,
"description": "The signature verification request includes the PayPal transmission headers: paypal-transmission-id, paypal-transmission-time, paypal-cert-url, paypal-auth-algo, paypal-transmission-sig"
},
{
"name": "webhook_id included",
"max_score": 8,
"description": "The webhook verification payload includes webhook_id (read from an environment variable such as PAYPAL_WEBHOOK_ID)"
},
{
"name": "Reject invalid signature",
"max_score": 10,
"description": "The handler returns a 401 or 400 error (does NOT process the event) when verification_status is not 'SUCCESS'"
},
{
"name": "PAYMENT.CAPTURE.COMPLETED handled",
"max_score": 10,
"description": "The handler contains a case or branch for the PAYMENT.CAPTURE.COMPLETED event type to trigger order fulfillment"
},
{
"name": "CHECKOUT.ORDER.APPROVED NOT used for fulfillment",
"max_score": 10,
"description": "The handler does NOT use CHECKOUT.ORDER.APPROVED as the trigger for creating orders or fulfilling payments"
},
{
"name": "PAYMENT.CAPTURE.REFUNDED handled",
"max_score": 8,
"description": "The handler contains a case or branch for the PAYMENT.CAPTURE.REFUNDED event"
},
{
"name": "PAYMENT.CAPTURE.REVERSED handled",
"max_score": 8,
"description": "The handler contains a case or branch for the PAYMENT.CAPTURE.REVERSED event (chargeback)"
},
{
"name": "Capture ID idempotency check",
"max_score": 12,
"description": "Before creating or fulfilling an order in response to a webhook, the code checks whether an order with the given PayPal capture_id already exists (to prevent duplicate processing on retries)"
},
{
"name": "200 response on success",
"max_score": 8,
"description": "The handler returns a 200 response after successfully processing (or safely ignoring) a webhook event"
},
{
"name": "Capture ID sourced correctly",
"max_score": 4,
"description": "The capture ID used for idempotency is read from the webhook resource payload (e.g. resource.id), not fabricated or read from a URL parameter"
}
]
}
Build a PayPal Webhook Handler for Order Fulfillment
Problem/Feature Description
A consumer electronics retailer processes hundreds of PayPal transactions per day. They currently trigger order fulfillment (warehouse pick-and-pack, shipping label generation) immediately when the PayPal popup closes on the frontend. This has caused problems: occasionally the funds are never actually settled, yet orders get shipped. They also had an incident where a webhook retry — caused by a temporary network blip on their end — created duplicate shipments for the same order.
The engineering team wants to move to a webhook-driven fulfillment model where the backend listens for confirmed payment events from PayPal and only triggers fulfillment once the payment is definitively settled. They also want to handle refunds and chargebacks automatically by updating internal order records. A previous contractor started a webhook endpoint but left it incomplete — the team needs a production-ready implementation.
Output Specification
Produce the webhook handler as a Node.js file:
1. `api/webhooks/paypal.js` — an Express-compatible request handler for POST /api/webhooks/paypal. It should:
- Authenticate the incoming request before processing
- Route payment events to appropriate handlers
- Prevent duplicate processing if the same event is delivered more than once
- Return appropriate HTTP responses
2. `implementation-notes.md` — a short document describing:
- How your handler authenticates incoming requests
- Which events it handles and what action each triggers
- How it prevents duplicate order fulfillment on webhook retries
Assume the following environment variables are available: PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID. Read PayPal credentials from these variables. You do not need a real database — use mock functions or console.log to represent database lookups and order updates. The implementation should be complete enough that a reviewer can assess the security and reliability properties from reading the code.
{
"name": "finsi/paypal-integration",
"version": "0.1.0",
"summary": "PayPal checkout, express buttons, PayPal Commerce Platform setup",
"skills": {
"paypal-integration": {
"path": "SKILL.md"
}
}
}