feat: add payment provider webhooks

This commit is contained in:
Codex
2026-06-28 22:42:27 +08:00
parent dee82e670f
commit db65295ddc
11 changed files with 1286 additions and 39 deletions

View File

@@ -27,6 +27,9 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
- `app_private.auth_sessions`:迁移期 session token hash。
- 阿里云短信 `SendSms` provider使用租户级 AccessKey、签名、模板发送。
- 腾讯云短信 `SendSms` provider使用租户级 SecretId/SecretKey、SdkAppId、签名、模板发送。
- `POST /api/commerce/payments/create`:按订单创建微信支付 JSAPI 或支付宝 WAP 支付参数。
- `POST /api/commerce/payments/notify/wechat_pay`:微信支付 API v3 通知验签、AES-GCM 解密、幂等落库和权益开通。
- `POST /api/commerce/payments/notify/alipay`:支付宝 RSA2 通知验签、幂等落库和权益开通。
## 短信 Provider
@@ -129,10 +132,73 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
支付不走 Supabase 内置能力。推荐继续扩展 `commerce`
- `POST /api/commerce/orders` 只负责创建订单,金额以后端套餐为准。
- `POST /api/commerce/payments/:provider/create` 后续按 provider 创建支付参数或收银台地址。
- `POST /api/commerce/payments/:provider/notify` 统一落 `payment_events`,先验签、再幂等、再更新订单和权益。
- `POST /api/commerce/payments/create` 按 provider 创建支付参数或收银台地址。
- `POST /api/commerce/payments/notify/<provider>` 统一落 `payment_events`,先验签、再幂等、再更新订单和权益。
- 支付成功继续复用 `grantSvipEntitlement`,避免微信/支付宝/XPay 各写一套开通逻辑。
当前支持:
- `wechat_pay`:微信支付 API v3 JSAPI 下单;通知验签后用 API v3 key 解密 `resource`
- `alipay`:支付宝 WAP/H5 支付参数生成;通知按 RSA2 验签。
- `manual`:仅本地/运营手工确认,不作为生产自动支付。
### 微信支付配置示例
```json
{
"provider": "wechat_pay",
"mode": "tenant_collect",
"status": "active",
"configPublic": {
"appId": "wx...",
"merchantId": "1900000001",
"merchantSerialNo": "商户证书序列号",
"notifyUrl": "https://api.example.com/api/commerce/payments/notify/wechat_pay?tenantId=<tenantId>",
"wechatpayPublicKey": "微信支付平台证书公钥或平台公钥"
},
"secret": {
"secretScope": "payment",
"secretKey": "wechat_pay",
"secretJson": {
"privateKey": "商户 API 证书私钥 PEM",
"apiV3Key": "32位 API v3 key"
}
}
}
```
### 支付宝配置示例
```json
{
"provider": "alipay",
"mode": "tenant_collect",
"status": "active",
"configPublic": {
"appId": "2021000000000000",
"notifyUrl": "https://api.example.com/api/commerce/payments/notify/alipay?tenantId=<tenantId>",
"returnUrl": "https://h5.example.com/pay/success"
},
"secret": {
"secretScope": "payment",
"secretKey": "alipay",
"secretJson": {
"privateKey": "应用私钥 PEM",
"alipayPublicKey": "支付宝公钥 PEM"
}
}
}
```
支付回调处理规则:
- `payment_events(provider,event_id)` 幂等。
- 通知验签/解密失败直接拒绝,不更新订单。
- 通知金额必须等于后端订单金额。
- 订单已支付时重复通知只返回幂等成功,不重复开通权益。
- 支付成功事务内更新 `orders``payments``payment_events``entitlements`
- 支付密钥只允许放在 `app_private.tenant_secrets` 或生产 KMS/Vault。
B 端合作商年费、服务费、服务器资源费不走学生端 `orders`,而是走平台账务:
- `platform_saas_plans`:平台售卖给合作商的 SaaS 套餐。

View File

@@ -92,11 +92,13 @@
| SVIP 套餐 | 可联调 | `/api/catalog/svip-plans` |
| 创建订单/订单列表 | 可联调 | `/api/commerce/orders` |
| 手工支付确认 | 迁移期 | 可用于测试,不是生产支付 |
| 微信支付 JSAPI | 可联调 | `/api/commerce/payments/create``notify/wechat_pay`,已覆盖 API v3 签名、通知解密、幂等和权益开通 |
| 支付宝 WAP/H5 | 可联调 | `/api/commerce/payments/create``notify/alipay`,已覆盖 RSA2 通知验签、幂等和权益开通 |
| 权益查询/校验 | 可联调 | `/api/commerce/entitlements` |
| 激活码兑换 | 可联调 | 事务开通权益 |
| 优惠券后台配置 | 可联调 | `/api/tenant-admin/coupons` |
| 优惠券前台兑换/下单抵扣 | 待补齐 | 后端还需接入下单计算 |
| 微信/支付宝/小程序支付 | 待补齐 | 需 provider、验签、幂等、退款、补偿 |
| 退款/补偿/对账 | 待补齐 | 需退款接口、支付补偿任务、对账、异常订单处理 |
## 租户后台与平台后台

View File

@@ -49,8 +49,8 @@
### P1 商用功能完善
1. 支付
- 微信支付、支付宝、XPay 或实际使用的支付网关 adapter
- webhook 验签、幂等、退款、支付补偿任务
- 已完成微信支付 JSAPI、支付宝 WAP/H5 的创建支付参数和 webhook 幂等开通权益
- 继续补退款、支付补偿任务、对账、异常订单处理
- 租户自有商户收款和平台代收/服务商模式。
2. 国内登录和短信

View File

@@ -146,7 +146,7 @@ tenant:<tenantId>:theme
| 知识手册 | `/api/catalog/handbook-subjects``handbook-chapters``handbook-entries` |
| 分数线 | `/api/scoreline/fields``schools``majors``records``trend``years` |
| 资料下载 | `/api/catalog/assets``/api/catalog/assets/download` |
| 商城 | `/api/catalog/svip-plans``POST /api/commerce/orders` |
| 商城 | `/api/catalog/svip-plans``POST /api/commerce/orders``POST /api/commerce/payments/create` |
| 订单/权益 | `/api/commerce/orders``/api/commerce/entitlements` |
| 激活码兑换 | `POST /api/commerce/activation-codes/redeem` |
| 个人中心 | `GET/PATCH /api/profile/me` |
@@ -245,6 +245,74 @@ identity.unionId
- 如果登录前已经解析到推广码,登录成功后再调用 `/api/referral/bind` 完成首绑保护。
- 手机号授权后续应走独立的“绑定手机号”接口,不要把微信手机号解密逻辑写在页面里。
## 支付对接
支付流程必须以后端订单金额和后端回调为准,前端只负责拉起支付。
### 创建订单
```text
POST /api/commerce/orders
body: {
"planId": "<svipPlanId>",
"quantity": 1,
"payProvider": "wechat_pay | alipay",
"payMethod": "jsapi | wap",
"regionId": "<regionId>"
}
```
返回 `orderNo` 后,再创建支付参数:
```text
POST /api/commerce/payments/create
body: {
"orderNo": "<orderNo>",
"provider": "wechat_pay",
"openId": "<微信小程序登录后的 openId>"
}
```
微信小程序返回的 `paymentParams` 可直接映射到 `Taro.requestPayment`
```text
appId
timeStamp
nonceStr
package
signType
paySign
```
支付宝 H5/WAP 返回:
```text
paymentParams.url
```
H5 可以跳转到该 URL。小程序端如果后续要接支付宝小程序需要新增独立 provider/method不要复用 H5 WAP URL。
支付完成后前端不要自行开通会员。前端应轮询或重新请求:
```text
GET /api/commerce/orders
GET /api/commerce/entitlements
```
后端支付回调地址由租户支付账户配置:
```text
/api/commerce/payments/notify/wechat_pay?tenantId=<tenantId>
/api/commerce/payments/notify/alipay?tenantId=<tenantId>
```
前端禁止:
- 传入自定义金额。
- 伪造支付成功状态。
- 保存商户号私钥、API v3 key、支付宝应用私钥。
- 在页面里实现 webhook 验签或权益开通。
## 第一阶段页面建议
1. `pages/bootstrap/index`