# 微信支付分整体业务开发流程

## 一、流程总览

```
商户创建支付分订单
    ↓
商户调用支付分创建接口  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` 需保证唯一，重复使用会返回已有订单信息。
