原创

PayPal 接入避坑

paste-image-1784600078257.png

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

支付系统里,环境混用是最难排查的坑之一。

坑二:只拿 client id,不理解 access token

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 收到完成事件后,再更新本地订单状态。

坑四:不处理 Webhook

PayPal Webhook 是支付状态同步的关键。

PayPal 官方 Webhooks 文档说明,Webhook 是 PayPal 在事件发生时向你的服务端发送的 HTTPS POST。订阅、退款、支付完成、支付失败、订单状态变化,都可能通过 Webhook 通知。

如果你不处理 Webhook,就很容易遇到这些问题:

用户付款成功但本地没有开通
用户退款了但系统仍然有权限
订阅付款失败但本地仍然显示有效
订阅取消了但系统没有同步
支付 pending 时提前履约

PayPal 支付集成必须有 Webhook 处理链路。

坑五:不验证 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

这样后面改价格、加套餐、切换环境时更安全。

坑八:没有处理 pending、denied 和失败状态

支付不是只有成功和失败两种状态。

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 状态、生产切换和充分测试。

下一篇,我们继续聊基础能力选型:邮件发送方案对比

正文到此结束
Loading...