商户创建支付分订单
↓
商户调用支付分创建接口 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} 查询退款
↓
退款成功
| 步骤 | 方法 | 接口 | 说明 |
|---|---|---|---|
| 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} |
查询退款状态 |
com.github.wechatpay-apiv3:wechatpay-java:0.2.17https://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 调用)
接口: PayScoreService.createPayScoreOrder(orderId, openId)
// 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 字符。
接口: PayScoreService.completePayScoreOrder(orderId, totalAmount, payments, discounts)
服务结束后调用,标记服务完成并触发扣款。
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);
接口: PayScoreService.payPayScoreOrder(orderId)
重要: 必须先调用此接口,退款接口
/pay/refund才会可用。/pay是/pay/refund的父资源,不调/pay直接退款会返回 404。
// 请求体
{
"appid": "wx...",
"service_id": "0000400000..."
}
// 响应
{
"out_order_no": "PS..."
}
接口: PayScoreService.cancelPayScoreOrder(orderId, reason)
服务开启异常时调用,取消支付分订单并释放授权。
PayScoreCancelRequest request = new PayScoreCancelRequest();
request.setOutOrderNo(order.getPayScoreOrderId());
request.setReason(reason);
PayScoreResult result = payScoreStrategy.cancelServiceOrder(request);
接口: PayScoreService.modifyPayScoreOrder(orderId, totalAmount, payments, reason)
仅在 USER_PAYING 状态可调用,金额只能下调。
接口: PayScoreService.syncOrderPaidInfo(orderId, detail)
用户通过其他方式(如收银台)完成支付后,调用此接口同步信息给微信,停止周期性扣款。
// 请求体
{
"appid": "wx...",
"service_id": "0000400000...",
"type": "ORDER_PAID",
"detail": { "paid_time": "2026-06-25T12:40:00+08:00", "amount": 265 }
}
接口: PayScoreService.refundPayScoreOrder(orderId, refundAmount, reason)
前置条件:
payStatus = PAID(已支付)已调用 /pay 接口发起扣款(否则返回 404)
// 先确保 /pay 资源存在
payScoreStrategy.payServiceOrder(order.getPayScoreOrderId());
// 再调用退款
PayScoreRefundRequest request = new PayScoreRefundRequest();
request.setOutOrderNo(order.getPayScoreOrderId());
request.setRefundAmount(actualRefundAmount);
request.setReason(reason);
PayScoreResult result = payScoreStrategy.refundServiceOrder(request);
接口: PayScoreService.queryPayScoreRefund(orderId, outRefundNo)
payScoreStrategy.queryRefund(order.getPayScoreOrderId(), outRefundNo);
为了优化用户体验,项目支持预创建支付分订单:
用户扫码 → 预创建支付分订单 → 存入 Redis(TTL 10分钟)
↓
用户选择商品 → 从 Redis 取出预授权信息 → 关联到实际订单
| Redis Key | 说明 | TTL |
|---|---|---|
payscore:preauth:{deviceId} |
预授权信息(outOrderNo, state) | 10 分钟 |
payscore:preauth:tracking:{userId} |
用户预授权跟踪(Set) | 24 小时 |
preCreatePayScoreOrder(userId, deviceId, openId)applyPreAuthPayScore(order) 取出关联| 错误码 | 说明 | 处理建议 |
|---|---|---|
404 NOT_FOUND |
服务订单不存在或未支付 | 确认 outOrderNo 正确,确认已调 /pay 接口 |
ORDER_PAID |
订单已支付 | 不需要重复操作 |
ORDER_DONE |
订单已完结 | 不需要重复操作 |
PARAM_ERROR |
参数错误 | 检查请求参数格式 |
SIGN_ERROR |
签名验证失败 | 检查证书配置 |
FREQUENCY_LIMITED |
频率超限 | 降低调用频率 |
/pay 必须在退款前调用:退款接口 URL 为 /pay/refund,是 /pay 资源的子资源。不调 /pay 直接退款会返回 404。yuanToFen() 转换。PS + 时间戳 + orderId 格式(31 字符)。out_order_no 需保证唯一,重复使用会返回已有订单信息。