简介
本技术文档面向 DouPHP 国际支付插件,聚焦 PayPal 与 Stripe 两种主流国际支付方式的集成实现。文档从系统架构、组件职责、数据流与处理逻辑入手,深入讲解:
- PayPal 标准版(Web Payment Standard/IPN)的实现要点、配置项与回调流程;
- Stripe Checkout Session、Payment Intent、Webhook 事件验签与对账的完整链路;
- 国际化支付的多币种支持、金额最小单位转换、最低收款限额、汇率与跨境手续费、税务处理的实践建议;
- 配置管理(API 密钥、Webhook URL、返回 URL 等)、错误处理与异常安全最佳实践;
- 测试环境与生产环境的部署注意事项。
项目结构
DouPHP 将支付方式以“插件”形式组织,PayPal 与 Stripe 分别位于 plugin/paypal 与 plugin/stripe 目录下,每个插件包含:
- manifest.php:声明插件分组与 Provider 类;
- *Provider.php:实现支付接口,暴露 meta/start/notify/finish/query 等能力;
- *Service.php:封装具体业务逻辑,调用支付网关 API 或表单跳转,完成订单状态推进。
graph TB
subgraph "支付插件"
PP["PayPal 插件<br/>manifest.php / PaypalProvider.php / PaypalService.php"]
ST["Stripe 插件<br/>manifest.php / stripeprovider.php / stripeservice.php"]
end
subgraph "框架核心"
PS["支付服务 PaymentService"]
CFG["配置 Config"]
DB["数据库 DB"]
MAIL["邮件 SiteMail"]
end
PP --> PS
ST --> PS
PP --> CFG
ST --> CFG
PS --> DB
PS --> MAIL
核心组件
- PayPal 插件
- Provider:对外暴露 paypal 插件 ID、元信息与 start/notify/finish 方法,委托 Service 处理。
- Service:使用老版 Web Payment Standard(IPN)方式发起付款,通过 POST 表单跳转到 PayPal;notify 回调进行 IPN 验证并标记成功;finish 同步回跳时幂等收尾并发送邮件通知。
- Stripe 插件
- Provider:对外暴露 stripe 插件 ID、元信息与 start/notify/finish/query 方法,支持主动对账。
- Service:基于 Stripe Checkout(Hosted Sessions)模式创建会话并跳转;notify 接收 webhook,HMAC-SHA256 验签后标记成功;finish 根据 session_id 反查并收尾;query 通过 PaymentIntents API 主动对账。
架构总览
下图展示用户下单到支付完成的端到端流程,涵盖 PayPal 与 Stripe 两条路径。
sequenceDiagram
participant U as "用户"
participant O as "订单系统"
participant P as "支付插件(Provider)"
participant S as "支付服务(Service)"
participant G as "支付网关(PayPal/Stripe)"
participant N as "回调/通知"
U->>O : 提交订单
O->>P : 调用 start()
P->>S : 委托 Service.start()
alt PayPal
S->>G : 构造表单并跳转至 PayPal
G-->>N : IPN 异步通知
N->>S : notify() 验证并标记成功
G-->>U : 返回 finish()
U->>S : finish() 幂等收尾
else Stripe
S->>G : 创建 Checkout Session
G-->>U : 跳转托管支付页
G-->>N : Webhook 事件(验签)
N->>S : notify() 验签并标记成功
G-->>U : success_url 回跳
U->>S : finish() 幂等收尾
end
详细组件分析
PayPal 插件分析
- 插件元信息
- 插件 ID:paypal
- 配置项:卖家邮箱、支付货币(USD/EUR/GBP/AUD/CAD/JPY/HKD)
- 发起支付
- 通过 HTML 表单 POST 到 PayPal 官方地址,携带 cmd/business/item_name/currency_code/amount/invoice/charset/no_shipping/notify_url/return/cancel_return 等字段,自动提交跳转。
- 异步通知
- 读取请求参数,拼接 _notify-validate 回发 PayPal 校验;校验通过后检查 payment_status、receiver_email、mc_gross、mc_currency 等关键字段,调用支付服务标记成功。
- 同步回跳
- 从 finish 页面获取 invoice 与 txn_id,幂等标记成功后发送订单支付通知邮件。
flowchart TD
A["收到 PayPal 回调"] --> B{"invoice 是否存在"}
B --> |否| F["返回 fail"]
B --> |是| C["查找支付记录"]
C --> D{"记录存在?"}
D --> |否| F
D --> |是| E["拼接 _notify-validate 并回发验证"]
E --> V{"VERIFIED?"}
V --> |否| F
V --> |是| H{"payment_status 有效且金额/货币匹配?"}
H --> |否| F
H --> |是| I["标记支付成功并保存原始数据"]
I --> G["返回 success"]
Stripe 插件分析
- 插件元信息
- 插件 ID:stripe
- 配置项:Secret Key、Webhook Signing Secret、支付货币(多币种选择)
- 发起支付(Checkout Session)
- 构建 line_items(currency、unit_amount、product_data.name),设置 client_reference_id=payment_sn,success_url 携带 {CHECKOUT_SESSION_ID},cancel_url 为首页;调用 checkout/sessions 创建会话并返回跳转链接。
- 异步通知(Webhook)
- 读取原始请求体 php://input,解析 HTTP_STRIPE_SIGNATURE 头,按 HMAC-SHA256 与时戳容差 ±300s 验签;仅处理 checkout.session.completed 与 payment_intent.succeeded 两类事件;根据事件对象提取 paymentSn 与 transactionId 并标记成功。
- 同步回跳(finish)
- 从 success_url 获取 session_id,查询 Session 得到 client_reference_id 与 payment_intent,再幂等标记成功并发送邮件。
- 主动对账(query)
- 优先用本地 transaction_id 查询 PaymentIntent;若无则按 client_reference_id 查询 Checkout Session 列表获取 payment_intent;根据 status 映射为 succeeded/pending/closed/failed。
sequenceDiagram
participant U as "用户"
participant S as "StripeService"
participant API as "Stripe API"
participant W as "Webhook"
U->>S : start()
S->>API : POST /checkout/sessions
API-->>S : 返回 session.url
S-->>U : 跳转 Stripe 托管支付页
API-->>W : 事件(验签)
W->>S : notify()
S->>API : (可选) 查询 Session/Intent
S-->>W : 返回 success/fail
API-->>U : success_url?session_id=...
U->>S : finish()
S->>API : GET /checkout/sessions/{id}
S-->>U : 跳转订单页
面向对象关系图(代码级)
classDiagram
class PaypalProvider {
+pluginId() string
+meta() array
+start(request) string
+notify(payload) string
+finish(payload) string
}
class PaypalService {
+start(request) string
+notify(payload) string
+finish(payload) string
-encodeRaw(data) string
}
class StripeProvider {
+pluginId() string
+meta() array
+start(request) string
+notify(payload) string
+finish(payload) string
+query(request) PaymentQueryResult
}
class StripeService {
+start(request) string
+notify(payload) string
+finish(payload) string
+query(request) PaymentQueryResult
-apiRequest(method,path,params,secretKey) array
-verifyWebhookSignature(payload,header,secret) bool
-toMinorUnit(amount,currency) int
-isZeroDecimalCurrency(currency) bool
-getMinimumAmount(currency) int
-buildConfig() array
-encodeRaw(data) string
}
PaypalProvider --> PaypalService : "委托"
StripeProvider --> StripeService : "委托"
依赖关系分析
- PayPal 插件依赖
- 框架基础服务 BaseService、PaymentService、DB、Config、SiteMail;
- 外部依赖:PayPal Web Payment Standard(IPN)表单与回调。
- Stripe 插件依赖
- 框架基础服务 BaseService、PaymentService、DB、Config、SiteMail;
- 外部依赖:Stripe REST API(checkout/sessions、payment_intents)与 Webhook。
graph LR
PP["PaypalProvider"] --> PSvc["PaypalService"]
PSvc --> PaySvc["PaymentService"]
PSvc --> CFG["Config"]
PSvc --> DB["DB"]
PSvc --> Mail["SiteMail"]
SP["StripeProvider"] --> SSvc["StripeService"]
SSvc --> PaySvc
SSvc --> CFG
SSvc --> DB
SSvc --> Mail
SSvc --> API["Stripe REST API"]
性能与可靠性
- 网络与并发
- PayPal IPN 与 Stripe Webhook 均为异步回调,需保证幂等处理与快速响应;避免在回调中执行耗时操作。
- 金额与货币
- Stripe 金额采用最小货币单位,零小数货币(如 JPY)不乘 100;需严格遵循各货币最低收款限额。
- 签名与时间窗
- Stripe Webhook 验签必须使用原始请求体与 HTTP_STRIPE_SIGNATURE 头,时间戳容差 ±300s。
- 对账能力
- Stripe 提供 query 主动对账,可弥补 webhook 延迟或丢失场景;PayPal 当前未实现 REST 对账,后续如需对账应升级至 REST API。
故障排查指南
- PayPal 常见问题
- 回调失败:检查 invoice 是否为空、是否找到对应支付记录、_notify-validate 是否返回 VERIFIED、payment_status 是否为 Completed/Pending、receiver_email/mc_gross/mc_currency 是否与配置一致。
- 金额不一致:确认传入 amount 与 mc_gross 一致,注意货币精度。
- Stripe 常见问题
- Webhook 验签失败:确保读取 php://input 原始体,正确解析 HTTP_STRIPE_SIGNATURE,核对 webhook_secret,检查时间戳容差。
- 金额错误:确认 currency 与 unit_amount 换算正确,符合最低收款限额。
- 对账失败:若本地无 transaction_id,按 client_reference_id 查询 Session 列表获取 payment_intent 后再查 Intent。
- 日志与追踪
- 建议在 notify/finish 中记录关键参数与结果(如 paymentSn、transactionId、event type、HTTP 状态码),便于定位问题。
结论
- PayPal 插件采用经典 IPN 流程,适合快速接入;但缺乏 REST 对账能力,建议在需要强一致性场景考虑升级方案。
- Stripe 插件基于 Checkout Session 与 Webhook,具备完善的验签、幂等与主动对账能力,推荐作为国际支付首选。
- 国际化支付需重视多币种、金额单位、最低限额、汇率与税费策略,并在配置层集中管理密钥与回调地址。
附录
配置管理清单
- PayPal
- 卖家邮箱:用于接收款项
- 支付货币:USD/EUR/GBP/AUD/CAD/JPY/HKD
- 回调地址:由 Service 生成 notify_url 与 return 地址
- Stripe
- Secret Key:sktest(测试)/ sklive(生产)
- Webhook Signing Secret:whsec_*,用于验签
- 支付货币:USD/EUR/GBP/JPY/HKD/CNY/AUD/CAD/SGD/TWD
- 回调地址:由 Service 生成 notify_url 与 finish_url
支付流程示例(以路径指引代替代码片段)
- 初始化支付
- PayPal:参考 plugin/paypal/PaypalService.php:44-72
- Stripe:参考 plugin/stripe/stripeservice.php:66-106
- 创建订单并发起支付
- 前端调用 Provider.start(),内部委托 Service 完成
- 处理支付回调
- PayPal:参考 plugin/paypal/PaypalService.php:78-129
- Stripe:参考 plugin/stripe/stripeservice.php:117-166
- 同步回跳收尾
- PayPal:参考 plugin/paypal/PaypalService.php:135-157
- Stripe:参考 plugin/stripe/stripeservice.php:176-205
- 主动对账(Stripe)
- 参考 plugin/stripe/stripeservice.php:217-274
国际化支付实践建议
- 多币种支持
- 明确站点结算货币与用户支付货币;Stripe 需按最小单位换算,PayPal 直接传递货币代码。
- 汇率转换
- 建议在订单创建时锁定汇率或提示风险;支付网关侧可能以当时汇率结算。
- 跨境手续费
- 关注网关费率与银行手续费,可在商品定价或结算环节体现。
- 税务处理
- 根据地区法规计算并显示税费;Stripe 支持在 line_items 中附加税信息(按需扩展)。
测试与生产环境部署
- 测试环境
- Stripe:使用 sktest* 与测试 Webhook Secret;在 Stripe Dashboard 添加本地或内网穿透的 Webhook 端点。
- PayPal:使用沙箱账号与测试邮箱;确保 notify_url 可达。
- 生产环境
- 切换为 sklive* 与真实 Webhook Secret;配置域名白名单与 HTTPS;开启严格的日志与告警。
- 校验服务器证书与防火墙出站访问;确保回调接口高可用与幂等。