MDZelo
首页/Indie Hacker Guide/PayPal Integration Pitfalls

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:

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:

text
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:

text
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:

text
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:

text
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:

text
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:

text
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:

text
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:

text
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:

text
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:

text
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

分享你对这一章的想法

还没有评论成为第一个分享想法的人。