开始增加支付-1

This commit is contained in:
yml
2026-06-03 12:21:23 +08:00
parent 6dcea2d56c
commit ae8928f458
22 changed files with 2098 additions and 33 deletions
+5
View File
@@ -35,6 +35,8 @@ API 规划以 [项目计划](project-plan.md) 第 10 章为准。
- `POST /api/orders`
- `GET /api/orders`
- `GET /api/orders/{id}`
- `POST /api/orders/{id}/pay`
- `POST /api/orders/{id}/pay/query`
- `POST /api/orders/{id}/cancel`
- `POST /api/orders/{id}/handoff`
- `GET /api/orders/{id}/handoff-records`
@@ -48,8 +50,11 @@ API 规划以 [项目计划](project-plan.md) 第 10 章为准。
- `GET /api/disputes/{id}`
- `GET /api/wallet/balance`
- `GET /api/wallet/ledger`
- `POST /api/wallet/recharge/pay`
- `POST /api/wallet/recharge/pay/{id}/query`
- `POST /api/files/upload`
- `GET /api/files/object`
- `POST /api/payments/leshua/notify`
- `GET /api/notifications`
- `POST /api/notifications/{id}/read`
- `GET /api/admin/auth/captcha`
+371
View File
@@ -0,0 +1,371 @@
# 乐刷支付接入文档
本文根据语雀《联合收单接口文档 / 商户交易》整理,作为本项目接入乐刷扫码/简易支付的内部记录。原始文档地址:
https://www.yuque.com/hayley-boppa/ws2xbg/lk89dvro0oc69f1m
## 接口总览
乐刷交易接口统一使用 `POST`,大部分能力共用网关路径:
- 测试网关:`https://t-paygate.lepass.cn/cgi-bin/lepos_pay_gateway.cgi`
- 生产网关:`https://paygate.leshuazf.com/cgi-bin/lepos_pay_gateway.cgi`
- Content-Type`application/x-www-form-urlencoded`
- 金额单位:分,RMB,最小 1 分,不允许小数。
- 通信返回中的 `resp_code=0` 只表示通信成功,业务是否成功要看 `result_code``status`
接口目录:
| 场景 | service / 地址 | 说明 | 本期接入 |
| --- | --- | --- | --- |
| 统一下单 | `service=get_tdcode` | 主扫,C 扫 B,返回二维码链接或简易支付跳转链接 | 是 |
| 条码支付 | `service=upload_authcode` | 被扫,B 扫 C,用户出示付款码 | 否 |
| 支付结果通知 | 商户通知 URL | 乐刷推送支付成功等结果 | 是 |
| 扫码交易结果查询 | `service=query_status` | 查询交易结果 | 是 |
| 扫码退款 | `service=unified_refund` | 发起退款,退款有效期和次数受限制 | 暂缓 |
| 扫码退款查询 | `service=unified_query_refund` | 查询退款状态 | 暂缓 |
| 扫码退款通知 | 商户通知 URL | 乐刷推送退款状态 | 暂缓 |
| 订单关闭 | `service=close_order` | 关闭未支付订单 | 暂缓 |
| 授权码查询 openid | `service=query_openid` | 条码/JSAPI 配套 | 暂缓 |
| 授权码查询银联 user_id | `service=query_userid` | 银联 JS 支付配套 | 暂缓 |
## 签名规则
请求、应答和通知都需要验签。当前代码只实现 MD5,SM3 先记录规则但不接入。
MD5 签名步骤:
1. 取所有参与签名的参数集合 `M`
2. 默认过滤空值,按参数名 ASCII 升序排序。
3.`key=value` 拼接为 `key1=value1&key2=value2`,字段名和值使用原始值,不做 URL Encode。
4. 末尾追加 `&key=商户请求密钥`
5. 对最终字符串做 MD5,结果转大写。
注意:
- 请求签名:一般不包含 `sign` 本身。
- 应答验签:按乐刷返回参数验签,实际返回字段可能因升级增加,验签时要允许新增字段。
- 支付/退款通知验签:`error_code``sign` 不参与签名,空值参与签名,密钥使用乐刷提供的通知验签密钥。当前实现为 `LESHUA_NOTIFY_KEY`,为空时回退 `LESHUA_SIGN_KEY`
- `sign_type=SM3` 时签名结果为 64 位;不上传 `sign_type` 默认 MD5。
## 统一下单
用于主扫支付,消费者主动扫码或打开简易支付收银台。本项目先接扫码/简易支付。
请求固定字段:
| 字段 | 必填 | 说明 | 本项目取值 |
| --- | --- | --- | --- |
| `service` | 是 | 接口名 | `get_tdcode` |
| `merchant_id` | 是 | 乐刷商户号 | `LESHUA_MERCHANT_ID` |
| `third_order_id` | 是 | 商户内部订单号,同商户下唯一 | 当前使用订单号 `order_no` |
| `amount` | 是 | 订单金额,单位分 | 租金 + 押金 |
| `pay_way` | 是 | 支付类型 | 默认 `ZFBZF`,可配置 |
| `jspay_flag` | 是 | 支付形态 | 默认 `2` 简易支付 |
| `nonce_str` | 是 | 32 位随机字符串 | 后端生成 |
| `sign` | 是 | 签名 | MD5 |
常用可选字段:
| 字段 | 说明 |
| --- | --- |
| `sign_type` | `MD5``SM3`,不传默认 MD5。当前只支持 MD5。 |
| `notify_url` | 支付结果通知地址,必须是乐刷可访问的绝对路径。 |
| `jump_url` | 简易支付前台跳转地址。文档写 `jspay_flag=2` 时必传,但也提示受微信支付宝限制可不传;生产建议配置。 |
| `client_ip` | 发起交易的客户端 IP。 |
| `body` | 商品描述,不能包含换行等特殊字符。 |
| `attach` | 附加数据,成功后原样返回。 |
| `order_expiration` | 订单有效期,单位秒,最大 600 秒;支付宝建议 60 的整数倍。 |
`jspay_flag`
| 值 | 说明 |
| --- | --- |
| `0` | 支付宝 Native、银联 Native 扫码支付 |
| `1` | 微信/支付宝/银联 JSAPI |
| `2` | 微信、支付宝简易支付,跳转乐刷收银台 |
| `3` | 微信/支付宝小程序 |
`pay_way`
| 值 | 说明 |
| --- | --- |
| `WXZF` | 微信 |
| `ZFBZF` | 支付宝 |
| `UPSMZF` / `UPSMPAY` | 银联二维码,不同章节/回调字段可能返回不同值 |
| `DCPAY` | 数字货币 |
下单返回重点字段:
| 字段 | 说明 |
| --- | --- |
| `resp_code` | 通信状态,`0` 成功 |
| `result_code` | 业务结果,`0` 成功 |
| `error_code` / `error_msg` | 错误码和错误描述 |
| `merchant_id` | 乐刷商户号 |
| `third_order_id` | 商户订单号 |
| `leshua_order_id` | 乐刷订单号 |
| `td_code` | 二维码链接,`jspay_flag=0/1/2` 可能返回 |
| `jspay_url` | 简易支付跳转地址 |
| `jspay_info` | JSAPI/小程序/银联 JS 支付信息 |
| `pay_way` | 支付类型 |
| `sign` | 返回签名 |
## 支付结果通知
通知采用 HTTP `POST`,建议使用 `application/x-www-form-urlencoded` 接收。支付通知可能延迟或失败,文档建议主接交易查询接口。本项目同时接通知和查单。
通知特点:
- 支付成功后乐刷一般约 30 秒推送通知。
- 通知频率示例:`0s / 15s / 30s / 1m / 4m / 34m / 64m / 94m / 124m / 184m`
- 回调处理时间要求 5 秒内。
- 乐刷可能重复通知,业务必须幂等。
- 通知成功响应必须是纯文本 `000000`,没有 JSON 和引号;其他响应或超时都会重试。
支付成功通知关键字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `sign` | 是 | MD5 签名 |
| `sign_type` | 否 | 签名类型 |
| `merchant_id` | 是 | 乐刷商户号 |
| `leshua_order_id` | 是 | 乐刷订单号 |
| `third_order_id` | 是 | 商户订单号 |
| `amount` | 是 | 订单金额,分 |
| `status` | 是 | 订单状态 |
| `pay_way` | 是 | 支付类型 |
| `pay_time` | 是 | 支付时间,如 `2018-08-20 19:19:19` |
| `bank_type` | 否 | 银行类型 |
| `openid` / `sub_openid` | 否 | 用户标识 |
| `out_transaction_id` | 否 | 微信、支付宝等上游订单号 |
| `attach` | 否 | 下单附加数据原样返回 |
| `trade_type` | 否 | `MICROPAY``NATIVE``JSAPI``SmPgPay``JSAPIQuick` 等 |
| `channel_order_id` | 否 | 通道订单号 |
| `settlement_amount` | 否 | 实际结算金额,分 |
| `discount_amount` | 否 | 优惠金额,分 |
通知验签规则:
- `error_code``sign` 不参与签名。
- 空值参与签名。
- 使用乐刷提供的通知验签密钥。
## 扫码交易结果查询
查询接口用于主动确认交易状态。文档要求:除非返回明确的支付成功、支付失败、订单关闭等最终状态,其他错误或未支付状态都需要继续查询;超过一定时间未支付可调用关单。
请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `service` | 是 | 固定 `query_status` |
| `merchant_id` | 是 | 乐刷商户号 |
| `third_order_id` | 二选一 | 商户订单号 |
| `leshua_order_id` | 二选一 | 乐刷订单号,优先使用 |
| `nonce_str` | 是 | 随机字符串 |
| `sign_type` | 否 | 签名类型 |
| `sign` | 是 | 签名 |
返回重点字段:
| 字段 | 说明 |
| --- | --- |
| `resp_code` / `result_code` | 通信和业务结果 |
| `status` | 订单状态 |
| `leshua_order_id` | 乐刷订单号 |
| `third_order_id` | 商户订单号 |
| `amount` | 订单金额 |
| `pay_way` | 支付类型 |
| `pay_time` | 支付完成时间,成功时返回 |
| `refund_amount` | 已退款金额 |
| `simple_url_flag` | `1` 表示简易支付 |
| `interm_state` | 支付中间态,如 `NOTPAY``USERPAYING` |
## 退款与退款查询
本期暂不实现退款,但记录接口便于后续接入。
退款限制:
- 超过三个月或发起后超过 60 天的订单无法退款,具体以乐刷返回为准。
- 同一交易订单最多发起 50 次部分退款。
- 退款接口一般返回退款中,如需最终状态要调用退款查询。
退款请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `service` | 是 | 固定 `unified_refund` |
| `merchant_id` | 是 | 乐刷商户号 |
| `third_order_id` | 二选一 | 商户订单号 |
| `leshua_order_id` | 二选一 | 乐刷订单号,优先使用 |
| `merchant_refund_id` | 是 | 商户退款单号,同商户下唯一,不能含 `_` 等特殊字符 |
| `refund_amount` | 是 | 退款金额,分 |
| `refund_type` | 否 | `0` 不使用余额,`1` 可使用余额,`2` 仅使用余额 |
| `notify_url` | 否 | 退款结果通知地址 |
| `nonce_str` | 是 | 随机字符串 |
| `sign` | 是 | 签名 |
退款查询请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `service` | 是 | 固定 `unified_query_refund` |
| `merchant_id` | 是 | 乐刷商户号 |
| `third_order_id` / `leshua_order_id` | 二选一 | 支付订单标识 |
| `merchant_refund_id` / `leshua_refund_id` | 二选一 | 退款单标识 |
| `nonce_str` | 是 | 随机字符串 |
| `sign` | 是 | 签名 |
退款返回/通知重点字段:
| 字段 | 说明 |
| --- | --- |
| `status` | 退款状态,见订单状态表 |
| `refund_amount` | 退款金额 |
| `merchant_refund_id` | 商户退款单号 |
| `leshua_refund_id` | 乐刷退款单号 |
| `total_amount` | 原订单总金额 |
| `order_balance` | 退款后订单余额,退款请求返回可能有 |
| `refund_time` | 退款成功时间 |
| `settlement_refund_amount` | 实际退款金额 |
| `discount_refund_amount` | 优惠退款金额 |
| `failure_reason` | 退款失败原因 |
退款通知成功也需要返回纯文本 `000000`
## 订单关闭
用于关闭未支付订单。本期暂不实现。
限制:
- 条码支付订单需要下单 15 秒后才允许关单。
- 统一下单订单无此 15 秒限制。
- 银联二维码不支持关单。
请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `service` | 是 | 固定 `close_order` |
| `merchant_id` | 是 | 乐刷商户号 |
| `third_order_id` | 二选一 | 商户订单号 |
| `leshua_order_id` | 二选一 | 乐刷订单号,优先使用 |
| `nonce_str` | 是 | 随机字符串 |
| `sign` | 是 | 签名 |
返回字段包含 `status``leshua_order_id``pay_way``amount` 等。
## 授权码相关
授权码接口主要服务条码支付、微信 JSAPI、银联 JS 支付,本期不接。
查询微信 openid
- `service=query_openid`
- 必填:`merchant_id``auth_code``appid``nonce_str``sign`
- 返回:`openid``sub_openid` 等。
查询银联云闪付 user_id
- `service=query_userid`
- 必填:`merchant_id``user_auth_code``nonce_str``sign`
- 可选/条件:`app_up_identifier`
- 返回:`user_id` 等。
## 状态码
扫码/异步通知常见订单状态:
| 值 | 说明 | 本项目处理 |
| --- | --- | --- |
| `0` | 支付中 | `paying` |
| `2` | 支付成功 | `paid`,推进订单待交接 |
| `6` | 订单关闭 | `closed` |
| `8` | 支付失败 | `failed` |
| `10` | 退款中 | 暂按 `paying`/后续扩展退款状态 |
| `11` | 退款成功 | 暂缓 |
| `12` | 退款失败 | 暂缓 |
| `30` | 刷卡支付成功 | 当前也按支付成功处理 |
| `31` | 刷卡撤销成功 | 暂缓 |
| `32` | 刷卡退货成功 | 暂缓 |
| `33` | 刷卡冲正成功 | 暂缓 |
| `41` | 预授权成功 | 暂缓 |
| `43` | 预授权撤销成功 | 暂缓 |
| `45` | 预授权完成请求成功 | 暂缓 |
| `47` | 预授权完成通知成功 | 暂缓 |
| `49` | 预授权完成撤销成功 | 暂缓 |
## 常见错误码
| 错误码 | 说明 |
| --- | --- |
| `-20001` | 参数错误或无效 |
| `-20002` / `-1002` | 无效乐刷商户号 |
| `-20003` | 商户订单号格式有误 |
| `-20004` / `-4006` | 乐刷订单不存在 |
| `-20005` | 服务错误 |
| `-20006` | 验签失败 |
| `-20007` | 签名 Key 未配置 |
| `-20008` | 支付类型错误 |
| `-20009` | 订单金额填写有误 |
| `-20010` / `-4027` | 系统错误/系统异常 |
| `-20011` | 非法参数 |
| `-20012` | 授权码为空 |
| `-20013` | 非法授权码 |
| `-20014` | 未知类型授权码 |
| `-20015` | 非法公众号支付标识 |
| `-20016` | 无效乐刷订单号 |
| `-20018` | 无效第三方订单号或乐刷订单号 |
| `-20019` | 订单状态不允许退款 |
| `-20020` | 订单未退款 |
| `-20023` / `-5017` | 订单可退余额不足 |
| `-20024` | 乐刷或第三方订单号必填 1 个 |
| `-20025` | 乐刷或第三方退款订单号必填 1 个 |
| `-20026` / `-1006` | 第三方订单号已存在 |
| `-20027` | 该订单已支付 |
| `-4004` | 订单正在处理中 |
| `-5103` | 商户退款 ID 已存在 |
| `-5014` | 商户无退款权限 |
| `-5015` | 暂不支持退款 |
| `-5018` | 订单退款次数超限 |
| `-2125` | 交易被风控拦截 |
| `-5035` | 下单异常,可能是通道网络、通道状态或通道商户异常,以 `error_msg` 为准 |
## 本项目接入映射
后端环境变量:
| 变量 | 说明 |
| --- | --- |
| `PAYMENT_PROVIDER` | `mock``leshua`。本地默认 `mock`,生产设为 `leshua`。 |
| `LESHUA_GATEWAY_URL` | 乐刷网关地址。 |
| `LESHUA_MERCHANT_ID` | 乐刷商户号。 |
| `LESHUA_SIGN_KEY` | 请求签名密钥。 |
| `LESHUA_NOTIFY_KEY` | 通知验签密钥。 |
| `LESHUA_NOTIFY_URL` | 支付结果通知地址,公网绝对 URL。 |
| `LESHUA_JUMP_URL` | 简易支付完成后的跳转地址。 |
| `LESHUA_PAY_WAY` | 默认支付方式,当前默认 `ZFBZF`。 |
| `LESHUA_JSPAY_FLAG` | 默认支付形态,当前默认 `2`。 |
| `LESHUA_SIGN_TYPE` | 当前仅支持 `MD5`。 |
后端 API
| API | 说明 |
| --- | --- |
| `POST /api/orders/:id/pay` | 创建或复用支付单,mock 模式会立即模拟渠道支付成功。 |
| `POST /api/orders/:id/pay/query` | 查询支付单并主动向乐刷查单。 |
| `POST /api/payments/leshua/notify` | 乐刷支付通知回调,不需要登录鉴权,成功返回 `000000`。 |
数据模型:
- 新增 `payment_orders` 表保存支付单、渠道单号、支付链接、请求/响应原文、状态和支付时间。
- `third_order_id` 当前使用租号订单号,后续如果要支持关闭后重新发起多次支付,应改为支付单号或订单号加支付轮次。
- 渠道确认支付成功后,不扣用户 `available`,直接向租客 `frozen` 写入 `channel_order_lock`,并推进订单到 `pending_handoff`,后续结账沿用现有冻结释放/结算逻辑。
当前范围:
- 已接:扫码/简易支付下单、查单、支付成功通知、mock 跑通链路。
- 暂缓:条码支付、JSAPI/小程序必要 openid 获取、退款、退款通知、关单、刷卡交易查询、SM3 签名。