|
|
@@ -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` 需保证唯一,重复使用会返回已有订单信息。
|