From f3eb260841e0782a04b6be2509666495b871cf85 Mon Sep 17 00:00:00 2001 From: testadmin Date: Thu, 16 Apr 2026 23:49:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(epay):=20usdtpro=20=E6=8C=87=E7=BA=B9?= =?UTF-8?q?=E9=87=91=E9=A2=9D=E6=94=B9=E7=94=B1=20epay=20=E4=BE=A7?= =?UTF-8?q?=E6=9D=83=E5=A8=81=E8=AE=A1=E7=AE=97=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E7=B2=BE=E5=BA=A6=E6=BC=82=E7=A7=BB=E5=AF=BC=E8=87=B4=E5=85=85?= =?UTF-8?q?=E5=80=BC=E4=B8=8D=E5=88=B0=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - epay_online_order 改为调用 epay mapi.php(createOrderApi) - 让 epay 自己走完 Pay::create → Plugin::loadForSubmit → usdtpro_plugin::submit - 从返回 jump url 解析 trade_no,再查 epay.pre_order 读取权威 usdtpro - 错误返回统一 encrypt_data + Success/Data 加密结构,前端能正确显示 - 所有错误文案改为越南语用户友好版(代理余额不足→"Hạn mức nạp mỗi lần ... VND") - 代理余额不足场景额外返回 max_amount 字段,前端可用作输入上限提示 - 移除 epay_create_usdt_pre_order 自算方法 --- .../onlinechip/controller/traits/epayPay.php | 216 +++++++----------- 1 file changed, 82 insertions(+), 134 deletions(-) diff --git a/application/onlinechip/controller/traits/epayPay.php b/application/onlinechip/controller/traits/epayPay.php index 2976a25..e51e38b 100644 --- a/application/onlinechip/controller/traits/epayPay.php +++ b/application/onlinechip/controller/traits/epayPay.php @@ -55,7 +55,8 @@ trait epayPay if (isset($post['encryptData'])) { $post = decrypt_data($post); if (!$post) { - return json(['status' => 0, 'message' => '数据解密失败']); + $data = encrypt_data(['status' => 0, 'message' => 'Dữ liệu không hợp lệ']); + return json(['Success' => 1, 'Data' => $data]); } } @@ -74,7 +75,8 @@ trait epayPay ])->find(); if (!$user || $user_id <= 0 || $money <= 0 || $client <= 0) { - return json(['status' => 0, 'message' => '参数错误,请稍后再试']); + $data = encrypt_data(['status' => 0, 'message' => 'Tham số không hợp lệ, vui lòng thử lại']); + return json(['Success' => 1, 'Data' => $data]); } // 地区支线权限(与 AlinPay 一致的风控) @@ -82,7 +84,8 @@ trait epayPay $area_list = config('area_list'); if (!isset($area_list[$user['area_id']])) { - return json(['status' => 0, 'message' => '所在地区暂不支持充值']); + $data = encrypt_data(['status' => 0, 'message' => 'Khu vực hiện chưa hỗ trợ nạp tiền']); + return json(['Success' => 1, 'Data' => $data]); } $isAllow = false; @@ -92,7 +95,8 @@ trait epayPay } } if (!$isAllow) { - return json(['status' => 0, 'message' => '该账号暂不支持在线充值']); + $data = encrypt_data(['status' => 0, 'message' => 'Tài khoản này chưa được phép nạp online']); + return json(['Success' => 1, 'Data' => $data]); } // 总代理余额校验 @@ -105,10 +109,18 @@ trait epayPay ])->find(); if (!$zdl) { - return json(['status' => 0, 'message' => '所属代理账号异常,暂不能充值']); + $data = encrypt_data(['status' => 0, 'message' => 'Tài khoản đại lý có vấn đề, không thể nạp']); + return json(['Success' => 1, 'Data' => $data]); } if ($zdl['money'] < $money) { - return json(['status' => 0, 'message' => '所属代理余额不足,暂不能充值']); + $maxAllowed = intval($zdl['money']); + $data = encrypt_data([ + 'status' => 0, + 'code' => 'AGENT_LIMIT', + 'message' => 'Hạn mức nạp mỗi lần hiện tại là ' . number_format($maxAllowed, 0, '.', ',') . ' VND. Vui lòng nhập số tiền nhỏ hơn hoặc bằng mức trên.', + 'max_amount' => $maxAllowed, + ]); + return json(['Success' => 1, 'Data' => $data]); } $pay_list = config('pay_list'); @@ -122,23 +134,6 @@ trait epayPay try { $epay = new \pay\Epay(); - // 兼容用:老版 pay/leader 页跳转 URL - $payUrl = $epay->buildPayUrl( - $pay_orderid, - $pay_type, - sprintf('%.2f', $money), - $goods_name, - $notify_url, - $return_url, - 'uid:' . $user_id - ); - - if (!$payUrl) { - addLogToFile('epay 下单构造 URL 失败:order=' . $pay_orderid, 'epay_error', 'epay'); - $data = encrypt_data(['status' => 0, 'message' => '支付地址生成失败,请稍后再试']); - return json(['Success' => 1, 'Data' => $data]); - } - // 写 OG 侧订单 $order_id = Db::name('order_record')->insertGetId([ 'pay_channel_id' => 8, @@ -164,132 +159,85 @@ trait epayPay 'client' => $client, ]); - // 跨库写 epay.pre_order 并算 usdtpro 指纹金额 + // 调用 epay 的 mapi.php,让 epay 自己走完整个流程: + // 1) INSERT pre_order + // 2) Channel::submit 算通道 + // 3) Plugin::loadForSubmit → usdtpro_plugin::submit() 算 usdtpro 指纹金额并写入 pre_order + // 4) 返回 {type:'jump', url:'/pay/leader/TRADE_NO/'} + // 注意:usdtpro 指纹金额必须由 epay 侧算,OG 侧算会与 monitor 对账逻辑产生精度漂移,导致充值不到账 + $apiResult = $epay->createOrderApi( + $pay_orderid, + $pay_type, + sprintf('%.2f', $money), + $goods_name, + $notify_url, + $return_url, + 'uid:' . $user_id + ); + + if (!is_array($apiResult) || empty($apiResult['url'])) { + $errMsg = is_array($apiResult) ? (isset($apiResult['msg']) ? $apiResult['msg'] : json_encode($apiResult, JSON_UNESCAPED_UNICODE)) : 'mapi 无响应'; + addLogToFile('epay mapi 下单失败:order=' . $pay_orderid . ' err=' . $errMsg, 'epay_error', 'epay'); + $data = encrypt_data(['status' => 0, 'message' => 'Khởi tạo thanh toán thất bại: ' . $errMsg]); + return json(['Success' => 1, 'Data' => $data]); + } + + // epay 的 jump url 形如 https://pay.g7g7.top/pay/leader/2026041623400011168/ + $payUrl = $apiResult['url']; + $trade_no = ''; + if (preg_match('#/pay/leader/(\d+)/?#', $payUrl, $m)) { + $trade_no = $m[1]; + } + + // 默认返回结构(若后续读取 usdtpro 失败仍可回退跳转) $qrcode = null; - if ($pay_type === 'usdt' && $merchant_id > 0) { - $qrcode = $this->epay_create_usdt_pre_order( - $pay_orderid, - $money, - $goods_name, - $notify_url, - $return_url, - $merchant_id, - 'uid:' . $user_id + + if ($pay_type === 'usdt' && $trade_no) { + // 从 epay.pre_order 读 epay 已经算好的 usdtpro 指纹金额 + 通道地址 + $preOrder = Db::query( + "SELECT o.`trade_no`, o.`usdtpro`, o.`realmoney`, o.`addtime`, c.`config` + FROM `epay`.`pre_order` o + LEFT JOIN `epay`.`pre_channel` c ON c.`id` = o.`channel` + WHERE o.`trade_no` = :tn LIMIT 1", + ['tn' => $trade_no] ); + + if (!empty($preOrder) && !empty($preOrder[0]['usdtpro'])) { + $row = $preOrder[0]; + $cfg = json_decode($row['config'] ?? '{}', true) ?: []; + $timeout = intval($cfg['timeout'] ?? 600); + $expiresAt = strtotime($row['addtime']) + $timeout; + + $qrcode = [ + 'order_sn' => $pay_orderid, + 'trade_no' => $row['trade_no'], + 'usdt_address' => $cfg['address'] ?? '', + 'usdt_amount' => rtrim(rtrim(sprintf('%.5f', floatval($row['usdtpro'])), '0'), '.'), + 'vnd_amount' => $row['realmoney'], + 'rate' => strval($cfg['rate'] ?? ''), + 'expires_at' => $expiresAt, + 'timeout_sec' => $timeout, + ]; + addLogToFile('epay 下单读取 usdtpro 成功: order=' . $pay_orderid . ' trade_no=' . $trade_no . ' usdt=' . $qrcode['usdt_amount'], 'epay_request', 'epay'); + } else { + addLogToFile('epay 下单 pre_order 未取到 usdtpro: order=' . $pay_orderid . ' trade_no=' . $trade_no, 'epay_error', 'epay'); + } } $data = encrypt_data([ 'status' => 1, - 'message' => '发起支付成功', + 'message' => 'Khởi tạo thanh toán thành công', 'url' => $payUrl, // 兼容老前端 - 'qrcode' => $qrcode, // 新前端内嵌渲染用 + 'qrcode' => $qrcode, // 新前端内嵌渲染用(usdtpro 由 epay 权威给出) ]); return json(['Success' => 1, 'Data' => $data]); } catch (\Exception $e) { addLogToFile('epay 下单异常:' . $e->getMessage() . ' | file:' . $e->getFile() . ':' . $e->getLine(), 'epay_error', 'epay'); - $data = encrypt_data(['status' => 0, 'message' => '支付发起失败[' . $e->getMessage() . ']']); + $data = encrypt_data(['status' => 0, 'message' => 'Khởi tạo thanh toán thất bại: ' . $e->getMessage()]); return json(['Success' => 1, 'Data' => $data]); } } - /** - * 跨库创建 epay.pre_order 并算 usdtpro 指纹金额 - * - * 算法对齐 pay.g7g7.top/plugins/usdtpro/usdtpro_plugin.php::submit(): - * - 指纹金额 = round(realmoney / rate, 4) - * - 同通道+同金额+未超时订单存在则 +0.0001 递增错开 - */ - private function epay_create_usdt_pre_order($orderNo, $money, $goodsName, $notifyUrl, $returnUrl, $pid, $param = '') - { - // 查 usdtpro 通道(写死 plugin=usdtpro,status=1) - $channel = Db::query("SELECT `id`, `config`, `rate` FROM `epay`.`pre_channel` WHERE `plugin`='usdtpro' AND `status`=1 LIMIT 1"); - if (empty($channel)) { - addLogToFile('epay USDT 通道未启用:order=' . $orderNo, 'epay_error', 'epay'); - return null; - } - $channel = $channel[0]; - $channel_config = json_decode($channel['config'], true); - if (!$channel_config || empty($channel_config['address'])) { - addLogToFile('epay USDT 通道配置缺失 address:order=' . $orderNo, 'epay_error', 'epay'); - return null; - } - $address = $channel_config['address']; - $rate = floatval($channel_config['rate'] ?? 100000); - $timeout = intval($channel_config['timeout'] ?? 600); - - // usdt type id(按插件约定 usdt -> pre_type.id=7,但直接查一下更稳) - $typeRow = Db::query("SELECT `id` FROM `epay`.`pre_type` WHERE `name`='usdt' AND `status`=1 LIMIT 1"); - if (empty($typeRow)) { - addLogToFile('epay pre_type usdt 未启用', 'epay_error', 'epay'); - return null; - } - $type_id = intval($typeRow[0]['id']); - - // 算指纹金额(碰撞检测) - $unique_usdt = round($money / $rate, 4); - $max_try = 100; - while ($max_try-- > 0) { - $same = Db::query( - "SELECT 1 FROM `epay`.`pre_order` WHERE `channel`=:ch AND `usdtpro`=:amt AND UNIX_TIMESTAMP(`addtime`) + :win >= :now LIMIT 1", - [ - 'ch' => $channel['id'], - 'amt' => $unique_usdt, - 'win' => $timeout * 3, - 'now' => time(), - ] - ); - if (empty($same)) { - break; - } - $unique_usdt = round($unique_usdt + 0.0001, 4); - } - - // epay 侧的 trade_no(YmdHis + 5 随机) - $trade_no = date('YmdHis') . rand(11111, 99999); - $realmoney = sprintf('%.2f', $money); - $domain = parse_url($notifyUrl, PHP_URL_HOST) ?: ''; - - Db::execute( - "INSERT INTO `epay`.`pre_order` - (`trade_no`, `out_trade_no`, `uid`, `type`, `channel`, `name`, `money`, `realmoney`, `getmoney`, - `notify_url`, `return_url`, `param`, `addtime`, `date`, `domain`, `ip`, `status`, `version`, `usdtpro`) - VALUES - (:trade_no, :out_trade_no, :uid, :type, :channel, :name, :money, :realmoney, :getmoney, - :notify_url, :return_url, :param, NOW(), CURDATE(), :domain, :ip, 0, 1, :usdtpro)", - [ - 'trade_no' => $trade_no, - 'out_trade_no' => $orderNo, - 'uid' => $pid, - 'type' => $type_id, - 'channel' => $channel['id'], - 'name' => $goodsName, - 'money' => $realmoney, - 'realmoney' => $realmoney, - 'getmoney' => $realmoney, - 'notify_url' => $notifyUrl, - 'return_url' => $returnUrl, - 'param' => $param, - 'domain' => $domain, - 'ip' => Request::instance()->ip(), - 'usdtpro' => $unique_usdt, - ] - ); - - $expires_at = time() + $timeout; - addLogToFile('epay USDT 内嵌下单成功: order=' . $orderNo . ' trade_no=' . $trade_no . ' usdt=' . $unique_usdt, 'epay_request', 'epay'); - - return [ - 'order_sn' => $orderNo, - 'trade_no' => $trade_no, - 'usdt_address' => $address, - 'usdt_amount' => sprintf('%.4f', $unique_usdt), - 'vnd_amount' => $realmoney, - 'rate' => (string) $rate, - 'expires_at' => $expires_at, - 'timeout_sec' => $timeout, - ]; - } - /** * 易支付 - 订单状态轮询 *