feat(epay): 内嵌二维码模式 + 订单状态轮询接口
- 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=超时)
This commit is contained in:
@@ -27,7 +27,26 @@ use think\Request;
|
|||||||
trait epayPay
|
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()
|
public function epay_online_order()
|
||||||
{
|
{
|
||||||
@@ -97,10 +116,13 @@ trait epayPay
|
|||||||
$notify_url = $pay_list['epay']['notify_url'] ?? '';
|
$notify_url = $pay_list['epay']['notify_url'] ?? '';
|
||||||
$return_url = $pay_list['epay']['callback_url'] ?? '';
|
$return_url = $pay_list['epay']['callback_url'] ?? '';
|
||||||
$goods_name = $pay_list['epay']['goods_name'] ?? '账户充值';
|
$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 {
|
try {
|
||||||
$epay = new \pay\Epay();
|
$epay = new \pay\Epay();
|
||||||
|
|
||||||
|
// 兼容用:老版 pay/leader 页跳转 URL
|
||||||
$payUrl = $epay->buildPayUrl(
|
$payUrl = $epay->buildPayUrl(
|
||||||
$pay_orderid,
|
$pay_orderid,
|
||||||
$pay_type,
|
$pay_type,
|
||||||
@@ -117,7 +139,7 @@ trait epayPay
|
|||||||
return json(['Success' => 1, 'Data' => $data]);
|
return json(['Success' => 1, 'Data' => $data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写订单
|
// 写 OG 侧订单
|
||||||
$order_id = Db::name('order_record')->insertGetId([
|
$order_id = Db::name('order_record')->insertGetId([
|
||||||
'pay_channel_id' => 8,
|
'pay_channel_id' => 8,
|
||||||
'pay_channel_name' => 'epay',
|
'pay_channel_name' => 'epay',
|
||||||
@@ -142,7 +164,26 @@ trait epayPay
|
|||||||
'client' => $client,
|
'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]);
|
return json(['Success' => 1, 'Data' => $data]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
addLogToFile('epay 下单异常:' . $e->getMessage() . ' | file:' . $e->getFile() . ':' . $e->getLine(), 'epay_error', 'epay');
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 易支付 - 异步回调处理
|
* 易支付 - 异步回调处理
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user