# 乐刷支付接入文档 本文根据语雀《联合收单接口文档 / 商户交易》整理,作为本项目接入乐刷扫码/简易支付的内部记录。原始文档地址: 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`、`leshua` 和 `sign` 不参与签名,其他返回字段按原样参与;空值参与签名;密钥只使用乐刷提供的通知验签密钥 `LESHUA_NOTIFY_KEY`。实测通知携带 `sign_type=MD5`,按普通返回字段参与签名。 - 乐刷 XML 通知里的空标签也属于空值参数,必须保留并参与签名,例如 `` 应进入待签名串为 `goods_tag=`。 - `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 签名。