PayPal
Configure PayPal payments
PayPal covers a global buyer base and supports one-time payments and subscriptions.
Setup
-
Create a PayPal Developer account
- Go to PayPal Developer
- Create an app and copy the Client ID and Client Secret
-
Use the sandbox while you develop
- In the Developer Dashboard, switch to Sandbox
- Use the sandbox Client ID and Client Secret
- Set
PAYPAL_SANDBOX=true
-
Configure the webhook
- Developer Dashboard → Webhooks
- Webhook URL:
https://yourdomain.com/api/payment/webhook/paypal - Copy the Webhook ID into
PAYPAL_WEBHOOK_ID
Environment variables
Add these to .env:
# PayPal
PAYPAL_CLIENT_ID=AYxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PAYPAL_CLIENT_SECRET=ELxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PAYPAL_WEBHOOK_ID=WH-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PAYPAL_SANDBOX=true # Set to false in productionPAYPAL_WEBHOOK_ID is the Webhook ID from the app where you registered https://yourdomain.com/api/payment/webhook/paypal.
One-time payments and subscriptions
One-time payment (Orders + Capture)
- You do not create a Product or Plan
- Use this for lifetime access and other single purchases
Subscription (Subscriptions)
- Create a Product and a Plan in the PayPal Dashboard
- Developer Dashboard → Products & Services → Subscriptions
- Create a Product, then a Plan
- Copy the Plan ID into
paypalPlanIdon the plan inconfig/payment.ts - Subscription status comes from the webhook or from an active status query
Sandbox
PayPal has a sandbox for development.
Turn the sandbox on
-
Set the environment variable
PAYPAL_SANDBOX=true -
Sandbox API:
https://api-m.sandbox.paypal.com -
Sandbox accounts
- Create sandbox buyer and seller accounts in the Developer Dashboard
- Complete test checkouts with the sandbox buyer account
Sandbox and production webhooks are separate. Register the webhook on the sandbox app. Sandbox webhook events can take several seconds to arrive.
Switch to production
- Set
PAYPAL_SANDBOX=false - Use the live Client ID and Client Secret
- Register the webhook again on the live app, at
https://yourdomain.com/api/payment/webhook/paypal, and updatePAYPAL_WEBHOOK_ID
Webhook events
Subscribe to these events so one-time payments and the subscription lifecycle are both covered:
| Event | Meaning |
|---|---|
PAYMENT.CAPTURE.COMPLETED | Capture succeeded |
PAYMENT.CAPTURE.DENIED | Capture was denied |
PAYMENT.CAPTURE.REFUNDED | Payment was refunded |
BILLING.SUBSCRIPTION.ACTIVATED | Subscription activated |
BILLING.SUBSCRIPTION.UPDATED | Subscription updated |
BILLING.SUBSCRIPTION.CANCELLED | Subscription cancelled |
BILLING.SUBSCRIPTION.EXPIRED | Subscription expired |
BILLING.SUBSCRIPTION.SUSPENDED | Subscription suspended |
PAYMENT.SALE.COMPLETED | Sale completed |
Plan examples
The config/payment.ts examples below apply only to static pricing (the default). With dynamic pricing, you do not need to edit config/payment.ts. Create plans in the admin panel at /admin/pricing and fill in paypalPlanId there.
Subscription
monthlyPaypal: {
provider: 'paypal',
id: 'monthlyPaypal',
amount: 10,
currency: 'USD',
duration: {
months: 1,
type: 'recurring'
},
paypalPlanId: 'P-XXXXXXXXXX', // Plan ID from the PayPal Dashboard
i18n: {
'en': {
name: 'PayPal Monthly Plan',
description: 'Monthly recurring subscription via PayPal',
duration: 'month',
features: ['All premium features', 'Priority support']
},
}
}One-time payment (lifetime)
lifetimePaypal: {
provider: 'paypal',
id: 'lifetimePaypal',
amount: 199,
currency: 'USD',
duration: {
months: 9999,
type: 'one_time'
},
i18n: {
'en': {
name: 'Lifetime (PayPal)',
description: 'One-time payment, lifetime access',
duration: 'lifetime',
features: ['All premium features', 'Priority support', 'Free lifetime updates']
},
}
}Payment flow
One-time payment (Orders + Capture)
Buyer selects a plan → order is created → redirect to PayPal →
return to /api/payment/return/paypal → server captures the order →
order and access update → success pageSubscription
Buyer selects a subscription → subscription is created → redirect to PayPal →
return to /api/payment/return/paypal?subscription=true →
server checks that the subscription is ACTIVE → access updates → success pageThe webhook at /api/payment/webhook/paypal is the source of truth for delayed events, cancellations, refunds, and renewals.
Notes
- PayPal supports multiple currencies (USD, EUR, GBP, and others)
- One-time payments and subscriptions are both supported
- Subscriptions need a Plan ID from the PayPal Dashboard
- Callback URLs must use HTTPS
- Sandbox and production are fully separate
- Sandbox webhook events can be delayed
Back to Payment overview