Paypal
Accept payments with PayPal. Use this skill when the project needs to integrate
You are a payments specialist who integrates PayPal into projects. PayPal offers multiple integration paths — Smart Payment Buttons, hosted checkout, REST API for orders and subscriptions — plus access to PayPal's massive user base and Venmo. ## Key Points - Use Smart Payment Buttons — they handle UI, eligibility, and localization - Always capture on the server, not the client — verify payment before granting access - Use `custom_id` on orders and subscriptions to link to your users - Test in sandbox with sandbox accounts before going live - Handle refund webhooks — revoke access when refunded - Support both PayPal and card payments for maximum conversion - Use PayPal's subscription plans for recurring billing - Granting access on client-side `onApprove` without server verification - Building custom payment buttons instead of using Smart Payment Buttons - Not handling subscription cancellation and payment failure webhooks - Using authorize-only intent for digital goods (capture immediately instead) - Not testing the full flow in sandbox — PayPal sandbox has unique quirks ## Quick Example ```html <script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&vault=true&intent=subscription"></script> ``` ```bash npm install @paypal/paypal-server-sdk ```
skilldb get payment-services-skills/PaypalFull skill: 225 linesPayPal Payment Integration
You are a payments specialist who integrates PayPal into projects. PayPal offers multiple integration paths — Smart Payment Buttons, hosted checkout, REST API for orders and subscriptions — plus access to PayPal's massive user base and Venmo.
Core Philosophy
PayPal is a payment method, not just a processor
Unlike Stripe (which processes cards), PayPal is a payment method that users already have funded. Integration adds a trusted checkout option alongside card payments, increasing conversion for users who prefer PayPal or Venmo.
Capture, don't authorize-only
For most SaaS and digital products, capture payments immediately at checkout. Use authorize-and-capture only for physical goods where fulfillment may fail.
Smart Payment Buttons handle the UI
PayPal's JavaScript SDK renders contextual buttons (PayPal, Venmo, Pay Later) that adapt to the user's location, device, and eligibility. Don't build custom buttons.
Setup
Client-side (PayPal JS SDK)
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&vault=true&intent=subscription"></script>
Server-side
npm install @paypal/paypal-server-sdk
import { PayPalHttpClient, SandboxEnvironment, LiveEnvironment } from '@paypal/paypal-server-sdk';
const environment = process.env.NODE_ENV === 'production'
? new LiveEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET)
: new SandboxEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET);
const client = new PayPalHttpClient(environment);
Key Techniques
Smart Payment Buttons (one-time payment)
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
createOrder: async () => {
const res = await fetch('/api/paypal/create-order', { method: 'POST' });
const data = await res.json();
return data.orderId;
},
onApprove: async (data) => {
const res = await fetch('/api/paypal/capture-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: data.orderID }),
});
const result = await res.json();
if (result.status === 'COMPLETED') {
window.location.href = '/success';
}
},
}).render('#paypal-button-container');
</script>
Server-side order creation and capture
// Create order
export async function POST(req: Request) {
const order = await client.execute({
path: '/v2/checkout/orders',
method: 'POST',
body: {
intent: 'CAPTURE',
purchase_units: [{
amount: {
currency_code: 'USD',
value: '29.00',
},
custom_id: userId, // Your reference
description: 'Pro Plan - Monthly',
}],
},
});
return Response.json({ orderId: order.result.id });
}
// Capture order
export async function POST(req: Request) {
const { orderId } = await req.json();
const capture = await client.execute({
path: `/v2/checkout/orders/${orderId}/capture`,
method: 'POST',
});
if (capture.result.status === 'COMPLETED') {
const userId = capture.result.purchase_units[0].custom_id;
await grantAccess(userId);
}
return Response.json({ status: capture.result.status });
}
Subscriptions
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
style: { shape: 'pill', label: 'subscribe' },
createSubscription: (data, actions) => {
return actions.subscription.create({
plan_id: 'P-xxx', // Plan ID from PayPal dashboard
custom_id: userId,
});
},
onApprove: (data) => {
fetch('/api/paypal/subscription-activated', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
subscriptionId: data.subscriptionID,
userId: userId,
}),
});
},
}).render('#paypal-button-container');
</script>
Subscription management (REST API)
// Get subscription details
const sub = await client.execute({
path: `/v1/billing/subscriptions/${subscriptionId}`,
method: 'GET',
});
// Cancel subscription
await client.execute({
path: `/v1/billing/subscriptions/${subscriptionId}/cancel`,
method: 'POST',
body: { reason: 'Customer requested cancellation' },
});
// Suspend subscription
await client.execute({
path: `/v1/billing/subscriptions/${subscriptionId}/suspend`,
method: 'POST',
body: { reason: 'Payment method issue' },
});
Webhook Processing
| Event | Action |
|---|---|
PAYMENT.CAPTURE.COMPLETED | Grant access |
BILLING.SUBSCRIPTION.ACTIVATED | Set up subscription |
BILLING.SUBSCRIPTION.CANCELLED | Revoke access |
BILLING.SUBSCRIPTION.SUSPENDED | Restrict access |
BILLING.SUBSCRIPTION.PAYMENT.FAILED | Notify user |
PAYMENT.CAPTURE.REFUNDED | Revoke access, adjust records |
import crypto from 'crypto';
export async function POST(req: Request) {
const body = await req.text();
// Verify webhook signature (PayPal uses certificate-based verification)
// In production, verify with PayPal's webhook verification API
const event = JSON.parse(body);
switch (event.event_type) {
case 'PAYMENT.CAPTURE.COMPLETED':
const customId = event.resource.custom_id;
await grantAccess(customId);
break;
case 'BILLING.SUBSCRIPTION.CANCELLED':
await revokeAccess(event.resource.custom_id);
break;
case 'BILLING.SUBSCRIPTION.PAYMENT.FAILED':
await notifyPaymentFailed(event.resource.custom_id);
break;
}
return new Response('OK');
}
Best Practices
- Use Smart Payment Buttons — they handle UI, eligibility, and localization
- Always capture on the server, not the client — verify payment before granting access
- Use
custom_idon orders and subscriptions to link to your users - Test in sandbox with sandbox accounts before going live
- Handle refund webhooks — revoke access when refunded
- Support both PayPal and card payments for maximum conversion
- Use PayPal's subscription plans for recurring billing
Anti-Patterns
- Granting access on client-side
onApprovewithout server verification - Building custom payment buttons instead of using Smart Payment Buttons
- Not handling subscription cancellation and payment failure webhooks
- Using authorize-only intent for digital goods (capture immediately instead)
- Not testing the full flow in sandbox — PayPal sandbox has unique quirks
- Ignoring Venmo eligibility — it's available through Smart Payment Buttons
Install this skill directly: skilldb add payment-services-skills
Related Skills
Adyen
Accept payments with Adyen. Use this skill when the project needs to integrate
Braintree
Accept payments with Braintree (PayPal). Use this skill when the project needs
Checkout Com
Accept payments with Checkout.com. Use this skill when the project needs to
Coinbase Commerce
Accept cryptocurrency payments with Coinbase Commerce. Use this skill when the
Creem
Accept payments with Creem as merchant of record. Use this skill when the project
Klarna
Accept payments with Klarna. Use this skill when the project needs to integrate