From 93ea9cdc411bb97c0ac52230eb91ba8e12cd50de Mon Sep 17 00:00:00 2001 From: testadmin Date: Thu, 16 Apr 2026 23:13:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(epay):=20=E5=86=85=E5=B5=8C=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E6=A8=A1=E5=BC=8F=20+=20=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=BD=AE=E8=AF=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - epay_online_order: 直接跨库写 epay.pre_order,自算 usdtpro 指纹金额(对齐 usdtpro_plugin::submit 算法) - 返回值新增 qrcode 字段(address/amount/expires_at),前端内嵌渲染,无需跳转 pay.g7g7.top - 保留 url 字段兼容老调用方 - 新增 epay_order_status: 前端轮询订单状态(0=待支付/1=已到账/2=超时) --- .../onlinechip/controller/traits/epayPay.php | 215 +++++++++++++++++- 1 file changed, 212 insertions(+), 3 deletions(-) diff --git a/application/onlinechip/controller/traits/epayPay.php b/application/onlinechip/controller/traits/epayPay.php index 2b26970..6bb3f41 100644 --- a/application/onlinechip/controller/traits/epayPay.php +++ b/application/onlinechip/controller/traits/epayPay.php @@ -27,7 +27,26 @@ use think\Request; trait epayPay { /** - * 易支付 - 发起充值下单 + * 易支付 - 发起充值下单(内嵌二维码模式) + * + * 直接跨库写 epay.pre_order + 计算 usdtpro 指纹金额, + * 前端内嵌渲染二维码,无需跳转 pay.g7g7.top。 + * + * 返回的 Data 结构: + * { + * status: 1|0 + * message: 文案 + * url: 兼容字段(老前端仍可 window.open 跳转 /pay/leader/trade_no/) + * qrcode: { 新字段,前端内嵌渲染 + * order_sn: OG 侧订单号(用于轮询) + * usdt_address: TRC20 收款地址 + * usdt_amount: 含指纹尾数的精确金额(如 10.0003) + * vnd_amount: VND 原始金额 + * rate: 汇率 + * expires_at: 订单超时绝对时间戳 + * timeout_sec: 总超时秒数 + * } + * } */ public function epay_online_order() { @@ -97,10 +116,13 @@ trait epayPay $notify_url = $pay_list['epay']['notify_url'] ?? ''; $return_url = $pay_list['epay']['callback_url'] ?? ''; $goods_name = $pay_list['epay']['goods_name'] ?? '账户充值'; + $epay_api = $pay_list['epay']['api_url'] ?? 'https://pay.g7g7.top'; + $merchant_id = intval($pay_list['epay']['pid'] ?? 0); try { $epay = new \pay\Epay(); + // 兼容用:老版 pay/leader 页跳转 URL $payUrl = $epay->buildPayUrl( $pay_orderid, $pay_type, @@ -117,7 +139,7 @@ trait epayPay return json(['Success' => 1, 'Data' => $data]); } - // 写订单 + // 写 OG 侧订单 $order_id = Db::name('order_record')->insertGetId([ 'pay_channel_id' => 8, 'pay_channel_name' => 'epay', @@ -142,7 +164,26 @@ trait epayPay 'client' => $client, ]); - $data = encrypt_data(['status' => 1, 'message' => '发起支付成功,即将跳转...', 'url' => $payUrl]); + // 跨库写 epay.pre_order 并算 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 + ); + } + + $data = encrypt_data([ + 'status' => 1, + 'message' => '发起支付成功', + 'url' => $payUrl, // 兼容老前端 + 'qrcode' => $qrcode, // 新前端内嵌渲染用 + ]); return json(['Success' => 1, 'Data' => $data]); } catch (\Exception $e) { addLogToFile('epay 下单异常:' . $e->getMessage() . ' | file:' . $e->getFile() . ':' . $e->getLine(), 'epay_error', 'epay'); @@ -151,6 +192,174 @@ trait epayPay } } + /** + * 跨库创建 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, + ]; + } + + /** + * 易支付 - 订单状态轮询 + * + * 前端轮询该接口,判断是否到账或已超时。 + * 返回 Data 结构: + * { status: 0|1|2, message, money, paid_time } + * 0=待支付, 1=已到账, 2=已超时 + */ + public function epay_order_status() + { + $post = Request::instance()->post(); + if (isset($post['encryptData'])) { + $post = decrypt_data($post); + if (!$post) { + return json(['status' => 0, 'message' => '数据解密失败']); + } + } + + $order_sn = trim($post['order_sn'] ?? ''); + $user_id = intval($post['user_id'] ?? 0); + + if ($order_sn === '' || $user_id <= 0) { + $data = encrypt_data(['status' => 0, 'message' => '参数错误']); + return json(['Success' => 1, 'Data' => $data]); + } + + $order = Db::name('order_record') + ->where([ + 'order_sn' => $order_sn, + 'user_id' => $user_id, + 'pay_channel_id' => 8, + ]) + ->find(); + + if (!$order) { + $data = encrypt_data(['status' => 0, 'message' => '订单不存在']); + return json(['Success' => 1, 'Data' => $data]); + } + + // status=3 代表已回调结算完成(见 epay_recharge_settle) + if (intval($order['status']) == 3) { + $data = encrypt_data([ + 'status' => 1, + 'message' => '充值成功', + 'money' => $order['money'], + 'paid_time' => $order['back_time'], + ]); + return json(['Success' => 1, 'Data' => $data]); + } + + // 超时判定:从 epay 配置拿 timeout,兜底 600 + $pay_list = config('pay_list'); + $timeout = intval($pay_list['epay']['timeout'] ?? 600); + if (time() - intval($order['create_time']) > $timeout) { + $data = encrypt_data([ + 'status' => 2, + 'message' => '订单超时', + 'money' => $order['money'], + ]); + return json(['Success' => 1, 'Data' => $data]); + } + + $data = encrypt_data([ + 'status' => 0, + 'message' => '等待支付', + 'money' => $order['money'], + ]); + return json(['Success' => 1, 'Data' => $data]); + } + /** * 易支付 - 异步回调处理 *