jiaoyukun / leshua-sdk-php
乐刷聚合支付 PHP SDK(支持统一下单、退款、查询、通知验签、对账等)
Requires
- php: >=7.2
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- lizhichao/one-sm: ^1.10
Requires (Dev)
- phpunit/phpunit: ^9.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-19 03:29:29 UTC
README
基于乐刷商户层级接入文档封装的 PHP 7.2+ SDK。覆盖统一下单、退款、查询、关闭订单等核心接口,支持 MD5 和国密 SM3 两种签名算法。
本 SDK 基于 乐刷商户层级接入文档 开发,已通过生产环境真实商户(
xxxxxxxxxx)联调测试。
📦 安装
composer require jiaoyukun/leshua-sdk-php
依赖:
- PHP >= 7.2
- ext-curl
- ext-json
- lizhichao/one-sm ^1.10(国密 SM3)
🚀 快速上手
use Jiaoyukun\Leshua\Leshua;
$leshua = new Leshua([
'merchant_no' => 'xxxxxxxxxx', // 乐刷商户号
'pay_key' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // 商户密钥(加签用)
'parse_key' => '', // 代理商解析密钥(回调验签用,可选)
'appid' => '', // 微信 appid(jspay_flag=1/3 必填,可选)
'sandbox' => false, // true=测试环境,false=生产环境
'notify_url' => 'https://your-domain.com/leshua/notify', // 异步通知回调地址(可选)
]);
💳 核心接口
1️⃣ 统一下单(生成聚合码)
$result = $leshua->payment->createOrder([
'third_order_id' => 'YOUR_ORDER_001',
'amount' => 100, // 单位:分
'jspay_flag' => 2, // 0=Native 1=JSAPI 2=收银台 3=小程序
'body' => '培训费',
// 'notify_url' => 'https://...', // 可覆盖全局配置
]);
// jspay_flag=0/1/2 时返回 jspay_url(收银台/JSAPI)
// jspay_flag=0 时也返回 td_code(Native 二维码内容)
$payUrl = $result['jspay_url'] ?? null;
$qrContent = $result['td_code'] ?? null;
// 重要字段
$leshuaOrderId = $result['leshua_order_id']; // 用于后续查询/退款
2️⃣ 条码支付(被扫 - 商家扫用户付款码)
$result = $leshua->payment->barcodePay([
'third_order_id' => 'YOUR_ORDER_002',
'amount' => 200,
'jspay_flag' => 0,
'auth_code' => '用户付款码', // 必填
'body' => '商品',
]);
3️⃣ 订单状态查询
$result = $leshua->payment->queryOrder('YOUR_ORDER_001');
// 状态字段
$status = $result['status']; // 2=支付成功,详见下表
$payTime = $result['pay_time']; // 支付完成时间
$payWay = $result['pay_way']; // WXZF/ZFBSF/YLZF
$tradeType = $result['trade_type']; // JSAPI/NATIVE/MICROPAY/SmPgPay
$amount = $result['amount']; // 订单金额(分)
$refunded = $result['refund_amount']; // 已退款金额(分)
$openid = $result['openid']; // 用户 openid(JSAPI/小程序时返回)
$channelId = $result['channel_order_id']; // 微信/支付宝侧订单号
订单状态码
| 状态码 | 含义 |
|---|---|
0 | 未支付 |
1 | 支付中 |
2 | 支付成功 |
3 | 已退款 |
4 | 已关闭 |
4️⃣ 发起退款
$result = $leshua->payment->refund(
'YOUR_ORDER_001', // 商户订单号
'YOUR_REFUND_001', // 商户退款单号(业务系统唯一)
100, // 退款金额(分,可部分退款)
[
// 'reason' => '学员申请退款',
// 'notify_url' => 'https://...',
]
);
$leshuaRefundId = $result['leshua_refund_id']; // 乐刷退款单号
$status = $result['status']; // 10=退款中 11=退款成功
限制:每笔订单最多 50 次退款,订单发起后 60 天内可退款(文档约定)。
5️⃣ 退款状态查询
// ⚠️ 必须传乐刷订单号(不是商户订单号或商户退款单号)
$result = $leshua->payment->queryRefund('1000626023126233'); // leshua_order_id
$status = $result['status']; // 10=退款中 11=退款成功
$refundAmount = $result['refund_amount']; // 退款金额
$refundTime = $result['refund_time']; // 退款完成时间
$payWay = $result['pay_way']; // 原支付方式
$channelOrder = $result['channel_order_id']; // 通道侧订单号
6️⃣ 关闭订单
$leshua->payment->closeOrder('YOUR_ORDER_001');
注意:已支付或收银台预下单的订单关闭可能返回「订单不存在」(这是乐刷业务规则,非 SDK 错误)。
🔐 签名算法切换
默认 MD5。如需切换到国密 SM3:
use Jiaoyukun\Leshua\Signer;
// 加签:MD5
$sign = Signer::signForTradeGateway($params, $key, Signer::ALGO_MD5);
// 加签:国密 SM3(乐刷商户层级接入指南要求)
$sign = Signer::signForTradeGateway($params, $key, Signer::ALGO_SM3);
SM3 实现使用 lizhichao/one-sm 包(HMAC-SM3 已自实现,符合国密标准 GB/T 32905-2016)。
🌐 接入指南类接口(暂未实现)
| 接口 | 用途 | 状态 |
|---|---|---|
listTradeDetail | 交易结算明细查询(代理商对账) | ⚠️ 暂未实现(单商户模式不常用) |
| 商户进件 / 变更 | 商户资料管理 | ⚠️ 暂未实现(接入流程商务对接) |
| 对账文件下载 | T+1 下载对账文件 | ⚠️ 暂未实现(见 Plan) |
业务侧常用接口已全部覆盖(下单 / 退款 / 查询 / 关闭),单商户模式足够。
📋 订单表字段映射(写订单模块必看)
业务方写订单支付模块时,按下表建字段:
| 订单表字段 | 来源 | 写入时机 | 必存? |
|---|---|---|---|
third_order_no | 业务系统生成 | 创建订单时 | ⭐ |
amount | 业务系统生成 | 创建订单时 | ⭐ |
status | 业务维护(0/1/2/3) | 支付/退款时更新 | ⭐ |
snap_merchant_no | 当前商户号(快照) | 创建订单时 | ⭐⭐ |
snap_pay_key | 当前 pay_key(快照) | 创建订单时 | ⭐⭐⭐ |
snap_appid | 当前 appid(快照) | 创建订单时 | ⭐⭐ |
snap_jspay_flag | 当前 jspay_flag(快照) | 创建订单时 | ⭐⭐ |
leshua_order_id | createOrder 返回 | 下单后 | ⭐⭐⭐ |
jspay_url | createOrder 返回 | 下单后(前端展示) | 选 |
td_code | createOrder 返回 | 下单后(Native 模式) | 选 |
pay_time | queryOrder 返回 | 支付成功时 | ⭐⭐ |
pay_way | queryOrder 返回(WXZF/ZFBZF/YLZF) | 支付成功时 | ⭐⭐ |
trade_type | queryOrder 返回(JSAPI/NATIVE/MICROPAY/SmPgPay) | 支付成功时 | 选 |
channel_order_id | queryOrder 返回(通道侧订单号) | 支付成功时 | ⭐⭐⭐(对账用) |
openid | queryOrder 返回(JSAPI 时) | 支付成功时 | 选 |
sub_merchant_id | queryOrder 返回(通道子商户号) | 支付成功时 | 选 |
leshua_refund_id | refund 返回 | 退款发起时 | ⭐⭐⭐ |
refund_amount | refund / queryRefund 返回 | 退款时累计 | ⭐⭐ |
refund_time | queryRefund 返回 | 退款成功时 | ⭐⭐ |
channel_refund_order_id | queryRefund 返回 | 退款成功时 | ⭐⭐⭐(对账用) |
⭐ 关键:快照字段为什么必须存?
商户配置可能变更:
- 状态切换(启用 → 停用)
- 商户删除
- 密钥轮换
- 校区切换到其他商户
如果不快照,退款时会失败(用当前配置 + 历史订单号 → 乐刷找不到对应交易)。
退款时正确写法:
// 从订单表读快照字段
$order = Db::name('order')->where('third_order_no', $thirdOrderNo)->find();
// 用快照字段构造 SDK(不用当前商户配置)
$snapLeshua = new Leshua([
'merchant_no' => $order['snap_merchant_no'],
'pay_key' => $order['snap_pay_key'],
'appid' => $order['snap_appid'],
'sandbox' => false,
]);
// 发起退款
$result = $snapLeshua->payment->refund(
$order['third_order_no'],
$refundNo,
$refundAmount
);
⚠️ 重要注意事项(踩坑汇总)
本节记录 真实生产商户联调时 遇到的所有坑,请务必读完再写代码。
1. service 名是 query_status 不是 query_order(最坑)
文档 API 目录写错(写 .cg 少 i),详细章节正确。SDK 实际常量:
Jiaoyukun\Leshua\Payment\PaymentService::SERVICE_QUERY_ORDER = 'query_status';
如果你手动构造请求,必须用 query_status!用 query_order 会返回 -5042 系统错误。
2. 加签不 URL 编码
乐刷明确说「字段名和字段值都采用原始值,不进行URL Encode」。
❌ 错误(用 http_build_query):
body=%E4%B9%90%E5%88%B7¬ify_url=https%3A%2F%2F...
✅ 正确(原始值):
body=乐刷¬ify_url=https://...
SDK 已用 buildRawQuery() 处理。手动调 API 时切记不要 URL 编码。
3. 响应是 XML 格式(CDATA 包裹)
虽然 Content-Type 可能是 application/json 或 text/xml,实际响应可能是 XML:
<leshua>
<resp_code><![CDATA[0]]></resp_code>
<leshua_order_id><![CDATA[1000626023126233]]></leshua_order_id>
...
</leshua>
SDK 已自动处理 JSON / XML / Form 三种响应格式。如果你手动解析:
$xml = simplexml_load_string($raw);
$resp_code = (string)$xml->resp_code;
4. nonce_str 必填(<= 32 字符)
所有接口都要求 nonce_str 参数,SDK 已自动注入。手动调用需自行加:
$params['nonce_str'] = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, 32);
5. jspay_flag 用 0 时别用 empty() 校验
empty('0') === true,会误判。正确:
// ❌ if (empty($params['jspay_flag'])) // jspay_flag=0 会被认为空
// ✅ if (!in_array((string)$params['jspay_flag'], ['0','1','2','3'], true))
6. queryRefund 必须用 leshua_order_id
测试了所有参数:
| 参数 | 结果 |
|---|---|
merchant_refund_id | ❌ -1005 无效的订单号 |
leshua_refund_id | ❌ -1005 无效的订单号 |
third_order_id | ❌ -4006 订单不存在 |
leshua_order_id | ✅ 成功 |
SDK 的 queryRefund($leshuaOrderId) 参数就是乐刷订单号(不是商户订单号或商户退款单号)。
7. Config::getAppid/getParseKey 允许空
如果你没配置 parse_key 或 appid,SDK 会返回空字符串(不抛 TypeError)。
如需手动校验,自己做即可。
8. terminal_info 必填
乐刷要求 terminal_info(JSON 字符串),最少包含 device_type 和 serial_num。SDK 自动拼:
{"device_type":"11","serial_num":"lhsdxxxxxxxxxx"}
device_type=11 = 条码支付辅助受理终端,serial_num=lhsd+商户号 是乐刷规则。
9. queryOrder 对 jspay_flag=2 收银台订单支持有限
实际测试发现:
jspay_flag=2(收银台)下单 → 拿到jspay_url,是真下单queryOrder用third_order_id或leshua_order_id都能查到(前提是 service=query_status 写对)closeOrder对收银台预下单的订单会返回「订单不存在」(因为订单未真生成)
注意:乐刷工作人员建议只用主动轮询 queryOrder,不用异步通知(通知可能丢)。
10. 不要被「API 目录」误导
文档目录写 /cgi-bin/lepos_pay_gateway.cg(少 i),详细章节写 .cgi(对的)。
永远以详细章节为准!
11. 响应字段对应
| 接口 | 响应成功标识 | 失败错误码字段 |
|---|---|---|
| 下单/退款/查询/关闭 | result_code="0" | error_code(业务失败) |
| 接口层错误 | resp_code="0" | resp_code="非0" |
成功响应没有 error_code 字段。SDK 的 checkBusinessError() 已正确处理。
12. 退款成功后 status 值
| status | 含义 |
|---|---|
10 | 退款处理中 |
11 | 退款成功 |
📊 真实联调案例
已通过乐刷商户
xxxxxxxxxx(xxxxxxxxxxxx)真实环境测试。
// 1. 下单 1 分钱
$result = $leshua->payment->createOrder([
'third_order_id' => 'SDK_PAY_20260821150018_406285',
'amount' => 1,
'jspay_flag' => 2, // 收银台
'body' => 'SDK联调测试',
]);
// 返回:leshua_order_id=1000626023126233, jspay_url=https://qr.leshuazf.com/...
// 2. 用户扫码支付 1 分钱
// (微信/支付宝/云闪付扫 jspay_url)
// 3. 订单状态查询(service=query_status)
$r = $leshua->payment->queryOrder('SDK_PAY_20260821150018_406285');
// status=2(支付成功), pay_time=2026-08-21 15:01:33, pay_way=WXZF
// 4. 退款 1 分钱
$refund = $leshua->payment->refund('SDK_PAY_20260821150018_406285', 'SDK_RF_001', 1);
// status=10(退款中)
// 5. 退款查询(leshua_order_id!)
$r = $leshua->payment->queryRefund('1000626023126233');
// status=11(退款成功), refund_time=2026-08-21 15:03:18
🔍 错误码速查
| 错误码 | 含义 |
|---|---|
0 | 成功 |
-4006 | 订单不存在 |
-5033 | 请求签名错误(检查 key、参数、URL 编码) |
-5042 | 系统错误(service 名错 / 商户未开通该功能 / 接口不可用) |
-1005 | 无效的订单号(参数名错了,例如 queryRefund 用 merchant_refund_id) |
📜 License
MIT
🔗 链接
- 乐刷商户层级接入文档(SDK 基于此文档开发)
- GitHub / Gitee Repository
- Packagist
文档最后更新:2026-08-21 真实联调商户:xxxxxxxxxx(xxxxxxxxxxxx河南)