Files
hfb_sys/docs/乐刷支付接入文档.md
T

373 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 乐刷支付接入文档
本文根据语雀《联合收单接口文档 / 商户交易》整理,作为本项目接入乐刷扫码/简易支付的内部记录。原始文档地址:
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/wallet/recharge/pay` | 创建钱包充值支付单,返回扫码支付二维码链接;mock 模式会立即模拟充值成功。 |
| `POST /api/wallet/recharge/pay/:id/query` | 查询钱包充值支付单,并主动向乐刷查单。 |
| `POST /api/orders/:id/pay` | 订单只使用钱包余额支付,不再直接调用乐刷。 |
| `POST /api/payments/leshua/notify` | 乐刷支付通知回调,不需要登录鉴权,成功返回 `000000`。 |
数据模型:
- 新增 `payment_orders` 表保存支付单、渠道单号、支付链接、请求/响应原文、状态和支付时间。
- 乐刷支付单仅用于钱包充值,`order_id = 0``third_order_id` 使用 `payment_no`
- 渠道确认钱包充值成功后,向用户 `available` 写入 `channel_recharge` 流水。
- 订单支付不再创建乐刷支付单,只扣用户钱包 `available` 并转入 `frozen`,后续结账沿用现有冻结释放/结算逻辑。
当前范围:
- 已接:钱包扫码/简易支付充值、查单、支付成功通知、mock 跑通链路;订单钱包余额支付。
- 暂缓:条码支付、JSAPI/小程序必要 openid 获取、退款、退款通知、关单、刷卡交易查询、SM3 签名。