# 微信支付分整体业务开发流程 ## 一、流程总览 ``` 商户创建支付分订单 ↓ 商户调用支付分创建接口 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` 需保证唯一,重复使用会返回已有订单信息。