
PayPal 是很多跨境 SaaS、独立站、工具产品会考虑的支付方式。它覆盖范围广,用户熟悉度高,尤其在国际市场里,PayPal 仍然是很重要的付款选项。
但 PayPal 接入和 Stripe 的思路不完全一样。很多坑不是出在“能不能弹出 PayPal 按钮”,而是出在环境、账号、订单捕获、Webhook、订阅状态和生产切换上。
本文基于 PayPal 官方文档整理:
PayPal 有 sandbox 和 live 两套环境。沙盒用来模拟真实付款,不会触碰真实资金;生产环境才是真实交易。
PayPal 官方 sandbox 文档说明,sandbox 是一个独立测试环境,可以用虚拟账号模拟真实交易。
常见错误是:前端用了 sandbox client id,后端却调用 live endpoint;或者数据库里保存了 sandbox 订单 ID,生产环境又拿来校验;或者测试买家账号和商家账号混在一起。
你要明确区分:
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
支付系统里,环境混用是最难排查的坑之一。
PayPal REST API 使用 OAuth 2.0 access token。
PayPal 官方 REST 文档说明,调用 API 时需要用 client id 和 client secret 换取 access token。client id 可以用于按钮和部分前端 SDK 场景,但 client secret 必须保存在服务端。
不要把 client secret 放到前端。后端需要用它换 access token,再调用 PayPal API。
一个基本关系是:
client id + client secret -> access token
access token -> 调用 PayPal REST API
如果你只理解前端按钮,不理解后端 token,就很容易在订单确认、订阅查询和 Webhook 校验时卡住。
PayPal Checkout 里,用户批准付款不等于你已经收到了钱。
订单通常需要经历创建、用户批准、捕获支付等步骤。真正的履约,应该在支付 capture 完成之后进行。
PayPal Checkout Webhook 文档也提醒,PAYMENT.CAPTURE.PENDING 代表支付完成仍在等待,不应在支付完成前履约;PAYMENT.CAPTURE.COMPLETED 才是可以履约的重要事件。
所以不要在用户点击 PayPal 按钮后立刻开通权益,也不要只因为前端返回成功就发货。
正确做法是:后端确认订单 capture 完成,或通过 Webhook 收到完成事件后,再更新本地订单状态。
PayPal Webhook 是支付状态同步的关键。
PayPal 官方 Webhooks 文档说明,Webhook 是 PayPal 在事件发生时向你的服务端发送的 HTTPS POST。订阅、退款、支付完成、支付失败、订单状态变化,都可能通过 Webhook 通知。
如果你不处理 Webhook,就很容易遇到这些问题:
用户付款成功但本地没有开通
用户退款了但系统仍然有权限
订阅付款失败但本地仍然显示有效
订阅取消了但系统没有同步
支付 pending 时提前履约
PayPal 支付集成必须有 Webhook 处理链路。
Webhook 来自外部网络,不能直接相信请求内容。
PayPal Webhooks 文档提到,可以把消息、webhook id 和 header 信息提交给 PayPal 的 verify signature endpoint 进行签名验证。
也就是说,你收到 Webhook 后,要确认它确实来自 PayPal,再处理业务。
基本流程应该是:
接收 Webhook
保存原始事件
验证签名
按 event id 去重
分发事件处理
更新本地状态
记录日志
不要把 Webhook 当普通公开接口处理。
PayPal 订阅不是创建成功就结束。
PayPal 订阅文档里列出了很多订阅相关 Webhook,例如:
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
如果你只处理订阅创建,就会错过续费、失败、取消、暂停和过期。
本地数据库至少要保存:
paypal_subscription_id
paypal_plan_id
subscription_status
current_period
last_payment_status
cancelled_at
用户权限应该根据本地同步后的订阅状态判断,而不是只看第一次创建。
PayPal Subscriptions 通常会涉及 Product 和 Plan。官方订阅文档说明,订阅流程一般包括创建 product、创建 plan、用 JavaScript SDK 展示 PayPal 按钮、买家同意并订阅。
如果你产品里有多个套餐、月付年付、试用、升级降级,就要提前规划 PayPal plan 和你本地 plan 的映射。
不要把 PayPal plan id 散落在代码里。建议保存到配置或数据库:
local_plan = pro_monthly
paypal_plan_id = P-xxx
currency = USD
interval = month
这样后面改价格、加套餐、切换环境时更安全。
支付不是只有成功和失败两种状态。
PayPal Webhook 里可能出现 pending、denied、reversed、failed 等事件。尤其在跨境支付、不同支付方式、风控审核场景下,状态可能不会立即完成。
不要把所有非成功状态都简单当失败,也不要在 pending 时提前开通长期权益。
比较稳妥的策略是:
COMPLETED:开通或延长权益
PENDING:标记等待,不开通长期权益
DENIED / FAILED:提示用户重试或更换方式
REVERSED / REFUNDED:回收或调整权益
状态机越清楚,支付问题越少。
PayPal 官方生产环境文档提醒,上线时要获取 live credentials,并把 API endpoint 从 sandbox 改为 live。
常见上线错误是只换了前端 SDK client id,没有换后端 secret;或者换了 API endpoint,但 webhook URL 仍然指向测试环境;或者 live app 没有启用对应能力。
上线清单至少包括:
前端 SDK client id
后端 client secret
API base URL
Webhook URL
Webhook 订阅事件
Product / Plan id
数据库环境配置
测试账号和真实账号区分
PayPal 上线不是“把 sandbox 改成 live”这么简单。
PayPal 官方 sandbox 文档建议用 sandbox 测试和调试流程。
你至少要测试:
普通一次性付款成功
用户取消付款
支付 pending
支付 denied
订阅创建
订阅续费
订阅付款失败
订阅取消
退款
Webhook 重复发送
Webhook 签名失败
如果只测试“按钮弹出”和“付款成功”,上线后一定会遇到意外状态。
PayPal 的难点,不是把按钮放到页面上,而是把支付生命周期和你本地业务状态同步好。
一个可靠的 PayPal 接入,要重点处理:sandbox/live 分离、服务端 access token、capture 完成后履约、Webhook 验签、订阅生命周期、pending 状态、生产切换和充分测试。
下一篇,我们继续聊基础能力选型:邮件发送方案对比。