PayPal Integration Pitfalls
PayPal is a common payment option for cross-border SaaS, indie products, and standalone websites. It has broad coverage and strong user familiarity, especially in international markets.
But PayPal integration is not exactly the same as Stripe. Many issues are not about whether the PayPal button appears. They happen around environments, accounts, order capture, webhooks, subscription state, and production migration.
This article follows PayPal's official documentation:
- Get started with PayPal REST APIs
- PayPal sandbox testing guide
- Subscriptions
- Integrate Subscriptions
- Subscriptions webhooks
- Subscribe to checkout webhooks
- Move your app to production
Pitfall 1: Mixing Sandbox and Live
PayPal has sandbox and live environments. Sandbox simulates real payment without moving real money. Live is production.
PayPal's sandbox docs describe it as an isolated testing environment that simulates live transactions.
Common mistakes include using a sandbox client ID on the frontend while the backend calls live endpoints, storing sandbox order IDs in production data, or mixing test buyer and merchant accounts.
Separate these clearly:
sandbox client id
sandbox client secret
sandbox business account
sandbox personal buyer account
sandbox API endpoint
live client id
live client secret
live merchant account
live API endpoint
Environment mixing is one of the hardest payment bugs to debug.
Pitfall 2: Not Understanding Access Tokens
PayPal REST APIs use OAuth 2.0 access tokens.
PayPal's REST docs explain that API calls require exchanging client ID and client secret for an access token. Client ID can be used in buttons and some SDK flows, but client secret must stay on the server.
Do not put client secret in frontend code. The backend uses it to get an access token, then calls PayPal APIs.
Basic relationship:
client id + client secret -> access token
access token -> PayPal REST API calls
If you only understand the button and not the backend token, order confirmation, subscription lookup, and webhook verification become difficult.
Pitfall 3: Treating Approval as Completed Payment
In PayPal Checkout, buyer approval does not always mean you received the money.
An order usually goes through creation, buyer approval, and payment capture. Fulfillment should happen after capture is completed.
PayPal Checkout webhook docs note that PAYMENT.CAPTURE.PENDING means payment completion is still pending, and fulfillment should not happen until payment completion succeeds. PAYMENT.CAPTURE.COMPLETED is the key event for fulfillment.
Do not grant access immediately after the PayPal button returns success.
The safer approach is to confirm capture completion on the backend, or handle the completion webhook before updating local order state.
Pitfall 4: Ignoring Webhooks
PayPal webhooks are central to payment state synchronization.
PayPal's webhook docs describe webhooks as HTTPS POST requests sent to your server when events occur. Subscriptions, refunds, completed payments, failed payments, and order changes can all arrive through webhooks.
If you ignore webhooks, you may see:
User paid but local access was not granted
User refunded but still has access
Subscription payment failed but local status stays active
Subscription was cancelled but local state did not update
Access was granted while payment was pending
A PayPal integration needs a webhook handling flow.
Pitfall 5: Not Verifying Webhooks
Webhook requests come from the public internet. Do not trust their content directly.
PayPal's webhook docs explain that you can send the message, webhook ID, and header information back to PayPal's verify signature endpoint for verification.
After receiving a webhook, confirm that it really came from PayPal before processing business logic.
Basic flow:
Receive webhook
Store raw event
Verify signature
Deduplicate by event id
Dispatch event handling
Update local state
Record logs
Do not treat a webhook as a normal public API.
Pitfall 6: Only Handling Subscription Creation
PayPal subscriptions do not end after creation.
PayPal subscription docs list many subscription webhook events:
BILLING.SUBSCRIPTION.CREATED
BILLING.SUBSCRIPTION.ACTIVATED
BILLING.SUBSCRIPTION.UPDATED
BILLING.SUBSCRIPTION.CANCELLED
BILLING.SUBSCRIPTION.SUSPENDED
BILLING.SUBSCRIPTION.EXPIRED
BILLING.SUBSCRIPTION.PAYMENT.FAILED
PAYMENT.SALE.COMPLETED
If you only handle subscription creation, you miss renewals, failures, cancellation, suspension, and expiration.
Store at least:
paypal_subscription_id
paypal_plan_id
subscription_status
current_period
last_payment_status
cancelled_at
User access should depend on synchronized local subscription state, not only first creation.
Pitfall 7: Poor Product and Plan Mapping
PayPal Subscriptions usually involve Product and Plan. PayPal's subscription docs describe the flow as creating a product, creating a plan, showing the PayPal button with the JavaScript SDK, and the buyer approving the subscription.
If you have multiple plans, monthly and yearly billing, trials, upgrades, or downgrades, plan the mapping between PayPal plans and local plans early.
Do not scatter PayPal plan IDs throughout code. Store them in config or database:
local_plan = pro_monthly
paypal_plan_id = P-xxx
currency = USD
interval = month
This makes price changes, new plans, and environment switching safer.
Pitfall 8: Ignoring Pending, Denied, and Failed States
Payment is not only success or failure.
PayPal webhooks may include pending, denied, reversed, failed, and other states. Cross-border payments, alternative payment methods, and risk review can delay final completion.
Do not treat all non-success states as the same failure, and do not grant long-term access while payment is pending.
A safer state policy:
COMPLETED: Grant or extend access
PENDING: Mark as waiting; do not grant long-term access
DENIED / FAILED: Ask user to retry or change method
REVERSED / REFUNDED: Revoke or adjust access
A clear state machine prevents many payment bugs.
Pitfall 9: Only Changing Part of the Live Configuration
PayPal's production docs say that going live requires live credentials and changing API endpoints from sandbox to live.
Common launch mistakes include changing only the frontend SDK client ID but not the backend secret, changing the API endpoint while the webhook URL still points to staging, or using a live app without required features enabled.
Launch checklist:
Frontend SDK client ID
Backend client secret
API base URL
Webhook URL
Webhook event subscriptions
Product / Plan IDs
Database environment config
Test accounts vs real accounts
Going live is not just replacing “sandbox” with “live.”
Pitfall 10: Not Testing Enough
PayPal's sandbox docs recommend testing and debugging flows in sandbox.
At minimum, test:
One-time payment success
Buyer cancellation
Payment pending
Payment denied
Subscription creation
Subscription renewal
Subscription payment failure
Subscription cancellation
Refund
Duplicate webhook delivery
Webhook signature failure
If you only test “button opens” and “payment succeeds,” production will surprise you.
Final Thoughts
The hard part of PayPal integration is not placing a button on the page. It is synchronizing the payment lifecycle with your local business state.
A reliable PayPal integration focuses on sandbox/live separation, server-side access tokens, fulfillment after capture completion, webhook verification, subscription lifecycle, pending states, production migration, and enough testing.
Next, we continue with infrastructure choices: Email Sending Solution Comparison.
评论
0分享你对这一章的想法