Pārlūkot izejas kodu

docs: 添加微信支付分整体业务开发流程文档

涵盖 API 接口清单、回调事件、状态流转、代码实现层次、
Redis 预授权机制、常见错误码及注意事项。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 nedēļas atpakaļ
vecāks
revīzija
2b5fcf961f
1 mainītis faili ar 304 papildinājumiem un 0 dzēšanām
  1. 304 0
      docs/微信支付分业务开发流程.md

+ 304 - 0
docs/微信支付分业务开发流程.md

@@ -0,0 +1,304 @@
+# 微信支付分整体业务开发流程
+
+## 一、流程总览
+
+```
+商户创建支付分订单
+    ↓
+商户调用支付分创建接口  POST /v3/payscore/serviceorder
+    ↓
+创建支付分订单成功(state: CREATED)
+    ↓
+用户确认支付分订单(PAYSCORE.USER_CONFIRMED 回调 → state: DOING)
+    ↓
+商户拉起支付分小程序确认订单页面
+    ↓
+用户确认订单
+    ↓
+商户向用户提供服务
+    ↓
+┌─────────────────────┬─────────────────────┐
+│ 服务开启异常         │ 服务开启成功         │
+│ ↓                   │ ↓                   │
+│ POST .../cancel     │ 服务结束             │
+│ 取消支付分订单        │ ↓                   │
+│                     │ POST .../complete    │
+│                     │ 完结支付分订单        │
+└─────────────────────┴─────────────────────┘
+                          ↓
+                      POST .../pay
+                      商户发起扣款
+                          ↓
+                微信支付持续周期性扣款
+                          ↓
+        ┌───────────────┴───────────────┐
+        │                               │
+      扣款成功                      扣款失败
+        │                               │
+        ↓                               ↓
+PAYSCORE.USER_PAID 回调        商户引导用户主动支付
+        │                               │
+        ↓                     ┌─────────┴─────────┐
+支付分订单结束                │                   │
+                             ↓                   ↓
+                   跳转支付分订单详情页     其他方式完成支付
+                             ↓                   ↓
+                       收银台支付          POST .../sync
+                             ↓           同步订单信息
+                       支付成功回调           ↓
+                             ↓           支付分订单结束
+                       支付分订单结束
+
+    ↓
+   对账
+    ├─ 商户平台下载交易账单
+    └─ API接口下载交易账单
+
+    ↓
+   退款
+    ├─ POST .../pay/refund  申请退款
+    └─ GET  .../pay/refund/{id}  查询退款
+            ↓
+         退款成功
+```
+
+---
+
+## 二、API 接口清单
+
+| 步骤 | 方法 | 接口 | 说明 |
+|------|------|------|------|
+| 1. 创建订单 | `POST` | `/v3/payscore/serviceorder` | 创建支付分服务订单 |
+| 2. 查询订单 | `GET` | `/v3/payscore/serviceorder/{out_order_no}` | 查询服务订单状态 |
+| 3. 取消订单 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/cancel` | 服务开启异常时取消 |
+| 4. 完结订单 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/complete` | 服务结束后完结订单 |
+| 5. 修改订单 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/modify` | 修改订单金额(仅可下调) |
+| 6. 发起扣款 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/pay` | 发起支付扣款(创单结单) |
+| 7. 同步信息 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/sync` | 用户已通过其他方式支付时同步 |
+| 8. 申请退款 | `POST` | `/v3/payscore/serviceorder/{out_order_no}/pay/refund` | 对已支付订单申请退款 |
+| 9. 查询退款 | `GET` | `/v3/payscore/serviceorder/{out_order_no}/pay/refund/{out_refund_no}` | 查询退款状态 |
+
+### 依赖
+
+- **SDK**: `com.github.wechatpay-apiv3:wechatpay-java:0.2.17`
+- **Base URL**: `https://api.mch.weixin.qq.com/v3/payscore/serviceorder`
+
+---
+
+## 三、关键回调事件
+
+| 事件类型 | 说明 | 本地处理 |
+|----------|------|----------|
+| `PAYSCORE.USER_CONFIRMED` | 用户确认使用服务 | 订单状态 → `DOING` |
+| `PAYSCORE.USER_PAID` | 用户完成支付 | 订单状态 → `COMPLETED`,支付状态 → `PAID` |
+| `PAYSCORE.REFUND_PAID` | 退款到账通知 | 更新退款状态 |
+| `PAYSCORE.USER_CANCEL` | 用户取消订单 | 订单状态 → `CANCELLED` |
+
+---
+
+## 四、订单状态流转
+
+### 微信侧状态
+
+```
+CREATED ──→ DOING ──→ DONE
+                │
+                └──→ USER_PAYING(等待用户支付)
+                └──→ EXPIRED(过期)
+                └──→ REVOKED(撤销)
+```
+
+### 本地订单状态
+
+| 字段 | 值 | 说明 |
+|------|-----|------|
+| `pay_score_state` | `CREATED` | 已创建服务订单 |
+| | `DOING` | 用户已确认,服务进行中 |
+| | `DONE` | 已完结并扣款成功 |
+| | `USER_PAYING` | 已完结,等待用户支付 |
+| | `EXPIRED` | 已过期 |
+| | `REVOKED` | 已撤销 |
+| `pay_status` | `UNPAID` | 未支付 |
+| | `PAID` | 已支付 |
+| | `REFUND` | 已退款 |
+| `status` | `PENDING` | 待处理 |
+| | `COMPLETED` | 已完成 |
+| | `CANCELLED` | 已取消 |
+
+---
+
+## 五、代码实现层次
+
+```
+PayScoreService (接口)
+    └── PayScoreServiceImpl (业务逻辑 + 订单状态管理)
+            └── PayScoreStrategy (策略接口,支持多渠道)
+                    └── WxPayScoreStrategy (微信支付分实现,HTTP 调用)
+```
+
+### 5.1 创建订单
+
+**接口**: `PayScoreService.createPayScoreOrder(orderId, openId)`
+
+```java
+// PayScoreServiceImpl.java
+PayScoreCreateRequest request = new PayScoreCreateRequest();
+request.setOutOrderNo(generateOutOrderNo(orderId));  // "PS" + timestamp + orderId
+request.setServiceId(wxPayConfig.getServiceId());
+request.setOpenId(openId);
+request.setRiskFundAmount(riskFundAmount);
+// ... 设置服务时间、位置、后付费项目等
+
+PayScoreResult result = payScoreStrategy.createServiceOrder(request);
+// 成功后将 outOrderNo 存入 order.payScoreOrderId
+```
+
+**outOrderNo 生成规则**: `"PS" + 秒级时间戳(10位) + orderId`,总长度 ≤ 32 字符。
+
+### 5.2 完结订单
+
+**接口**: `PayScoreService.completePayScoreOrder(orderId, totalAmount, payments, discounts)`
+
+服务结束后调用,标记服务完成并触发扣款。
+
+```java
+PayScoreCompleteRequest request = new PayScoreCompleteRequest();
+request.setOutOrderNo(order.getPayScoreOrderId());
+request.setTotalAmount(totalAmount);
+request.setPostPayments(payments);   // 后付费项目列表
+request.setPostDiscounts(discounts); // 后付费优惠
+request.setEndTime(formatDateTime(LocalDateTime.now()));
+
+PayScoreResult result = payScoreStrategy.completeServiceOrder(request);
+```
+
+### 5.3 发起扣款
+
+**接口**: `PayScoreService.payPayScoreOrder(orderId)`
+
+> **重要**: 必须先调用此接口,退款接口 `/pay/refund` 才会可用。`/pay` 是 `/pay/refund` 的父资源,不调 `/pay` 直接退款会返回 **404**。
+
+```java
+// 请求体
+{
+  "appid": "wx...",
+  "service_id": "0000400000..."
+}
+// 响应
+{
+  "out_order_no": "PS..."
+}
+```
+
+### 5.4 取消订单
+
+**接口**: `PayScoreService.cancelPayScoreOrder(orderId, reason)`
+
+服务开启异常时调用,取消支付分订单并释放授权。
+
+```java
+PayScoreCancelRequest request = new PayScoreCancelRequest();
+request.setOutOrderNo(order.getPayScoreOrderId());
+request.setReason(reason);
+PayScoreResult result = payScoreStrategy.cancelServiceOrder(request);
+```
+
+### 5.5 修改金额
+
+**接口**: `PayScoreService.modifyPayScoreOrder(orderId, totalAmount, payments, reason)`
+
+仅在 `USER_PAYING` 状态可调用,金额只能下调。
+
+### 5.6 同步订单
+
+**接口**: `PayScoreService.syncOrderPaidInfo(orderId, detail)`
+
+用户通过其他方式(如收银台)完成支付后,调用此接口同步信息给微信,停止周期性扣款。
+
+```java
+// 请求体
+{
+  "appid": "wx...",
+  "service_id": "0000400000...",
+  "type": "ORDER_PAID",
+  "detail": { "paid_time": "2026-06-25T12:40:00+08:00", "amount": 265 }
+}
+```
+
+### 5.7 申请退款
+
+**接口**: `PayScoreService.refundPayScoreOrder(orderId, refundAmount, reason)`
+
+**前置条件**:
+- 订单 `payStatus = PAID`(已支付)
+- 距支付时间不超过 7 天
+- 已调用 `/pay` 接口发起扣款(否则返回 404)
+
+```java
+// 先确保 /pay 资源存在
+payScoreStrategy.payServiceOrder(order.getPayScoreOrderId());
+
+// 再调用退款
+PayScoreRefundRequest request = new PayScoreRefundRequest();
+request.setOutOrderNo(order.getPayScoreOrderId());
+request.setRefundAmount(actualRefundAmount);
+request.setReason(reason);
+
+PayScoreResult result = payScoreStrategy.refundServiceOrder(request);
+```
+
+### 5.8 查询退款
+
+**接口**: `PayScoreService.queryPayScoreRefund(orderId, outRefundNo)`
+
+```java
+payScoreStrategy.queryRefund(order.getPayScoreOrderId(), outRefundNo);
+```
+
+---
+
+## 六、Redis 预授权机制
+
+为了优化用户体验,项目支持**预创建支付分订单**:
+
+```
+用户扫码 → 预创建支付分订单 → 存入 Redis(TTL 10分钟)
+     ↓
+用户选择商品 → 从 Redis 取出预授权信息 → 关联到实际订单
+```
+
+| Redis Key | 说明 | TTL |
+|-----------|------|-----|
+| `payscore:preauth:{deviceId}` | 预授权信息(outOrderNo, state) | 10 分钟 |
+| `payscore:preauth:tracking:{userId}` | 用户预授权跟踪(Set) | 24 小时 |
+
+### 预授权流程
+
+1. 用户扫码 → `preCreatePayScoreOrder(userId, deviceId, openId)`
+2. 创建支付分订单 → 存 Redis
+3. 自动取消该用户/设备下的旧未使用预授权
+4. 用户下单 → `applyPreAuthPayScore(order)` 取出关联
+
+---
+
+## 七、常见错误码
+
+| 错误码 | 说明 | 处理建议 |
+|--------|------|----------|
+| `404 NOT_FOUND` | 服务订单不存在或未支付 | 确认 outOrderNo 正确,确认已调 `/pay` 接口 |
+| `ORDER_PAID` | 订单已支付 | 不需要重复操作 |
+| `ORDER_DONE` | 订单已完结 | 不需要重复操作 |
+| `PARAM_ERROR` | 参数错误 | 检查请求参数格式 |
+| `SIGN_ERROR` | 签名验证失败 | 检查证书配置 |
+| `FREQUENCY_LIMITED` | 频率超限 | 降低调用频率 |
+
+---
+
+## 八、注意事项
+
+1. **`/pay` 必须在退款前调用**:退款接口 URL 为 `/pay/refund`,是 `/pay` 资源的子资源。不调 `/pay` 直接退款会返回 404。
+2. **退款期限**:支付完成后 7 天内可退款,超期需走其他流程。
+3. **金额单位**:微信 API 金额单位为**分**,代码中使用 `yuanToFen()` 转换。
+4. **outOrderNo 限制**:≤ 32 字符,项目使用 `PS + 时间戳 + orderId` 格式(31 字符)。
+5. **回调验签**:收到微信回调时必须验证签名,防止伪造通知。
+6. **幂等性**:创建订单的 `out_order_no` 需保证唯一,重复使用会返回已有订单信息。