From 43cb042bf0a23af5447ab5127f04f5988772c906 Mon Sep 17 00:00:00 2001 From: ypsdf1 Date: Thu, 16 Jul 2026 11:28:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMySQL=E8=BF=9E=E6=8E=A5:=20?= =?UTF-8?q?=E9=9B=86=E4=B8=AD=E7=AE=A1=E7=90=86=E5=87=AD=E6=8D=AE=EF=BC=8C?= =?UTF-8?q?=E5=B8=B8=E9=87=8F=E6=9C=AA=E5=AE=9A=E4=B9=89=E6=97=B6=E7=94=A8?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.php | 29 +++-- api/pay.php | 213 ++++++++++++++++++++++++++++++++++-- api/poller_online.php | 96 ++++++++++------ api/recharge_orders_api.php | 64 +++++++++-- player.php | 25 ++++- 5 files changed, 363 insertions(+), 64 deletions(-) diff --git a/admin.php b/admin.php index 49921d9..d98e4ac 100644 --- a/admin.php +++ b/admin.php @@ -3431,7 +3431,7 @@ async function _roLoad() { if (sts) p.append('status', sts); const r = await fetch('api/recharge_orders_api.php?'+p.toString()); const d = await r.json(); - if (d.error) { el.innerHTML = '
加载失败: '+d.error.message+'
'; return; } + if (d.error || d.success === false) { el.innerHTML = '
加载失败: '+(d.error?.message||d.message||'未知错误')+'
'; return; } _roOrders = d.orders || []; const st = d.stats || {}; document.getElementById('roSt').textContent = st.total||0; @@ -3474,13 +3474,24 @@ async function _roReconcile() { try { const r = await fetch('api/recharge_orders_api.php?action=reconcile'); const d = await r.json(); - if (d.error) { glassAlert('对账失败: '+d.error); return; } - if (d.result === 'already_running') { glassAlert('另一个对账进程正在运行,请稍后重试'); return; } + // poller_online.php返回格式:{result: 'ok', processed, skipped} 或 {result: 'already_running'} 或 {error: '...'} + if (d.error) { + glassAlert('对账失败: '+(d.detail||d.error)); + return; + } + if (d.result === 'already_running') { + glassAlert('另一个对账进程正在运行,请稍后重试'); + return; + } if (d.result === 'ok') { - glassAlert('对账完成!处理 '+d.processed+' 笔,跳过 '+d.skipped+' 笔'); + glassAlert('对账完成!处理 '+(d.processed||0)+' 笔,跳过 '+(d.skipped||0)+' 笔'); _roLoad(); + } else if (d.result === 'no_paid_orders_on_platform') { + glassAlert('支付平台无已支付订单'); + } else if (d.result === 'no_pending_orders') { + glassAlert('所有订单已处理,无需补单'); } else { - glassAlert('对账结果: '+(d.detail||d.result)); + glassAlert('对账结果: '+(d.detail||d.result||'未知')); } } catch(e) { glassAlert('对账请求失败: '+e.message); } } @@ -3489,9 +3500,13 @@ async function _roSyncPlatform() { try { const r = await fetch('api/recharge_orders_api.php?action=sync_platform'); const d = await r.json(); - if (d.error) { glassAlert('同步失败: '+d.error); return; } + if (d.success === false) { + glassAlert('同步失败: '+(d.message||'未知错误')); + return; + } if (d.success) { - glassAlert('同步完成!平台共 '+d.data.platform_total+' 笔,新同步 '+d.data.synced+' 笔,已存在 '+d.data.already_paid+' 笔'); + const info = d.data || {}; + glassAlert('同步完成!平台共 '+(info.platform_total||0)+' 笔,新同步 '+(info.synced||0)+' 笔,已存在 '+(info.already_paid||0)+' 笔'); _roLoad(); } else { glassAlert('同步失败: '+(d.message||'未知错误')); diff --git a/api/pay.php b/api/pay.php index 6850c33..8ae5626 100644 --- a/api/pay.php +++ b/api/pay.php @@ -54,6 +54,24 @@ function loadPayKeys() { return ['pid' => '', 'private_key' => '', 'public_key' => '', 'md5_key' => '']; } +/** + * 获取平台MySQL数据库连接(集中凭据管理) + * 凭据从 pay_secrets.php 读取,不再硬编码 + */ +function getPlatformDB() { + $host = defined('PAY_MYSQL_HOST') ? PAY_MYSQL_HOST : '127.0.0.1'; + $dbname = defined('PAY_MYSQL_DBNAME') ? PAY_MYSQL_DBNAME : 'caihong'; + $user = defined('PAY_MYSQL_USER') ? PAY_MYSQL_USER : 'kH3C3LLinNwYdTF5'; + $pass = defined('PAY_MYSQL_PASS') ? PAY_MYSQL_PASS : 'sRhsdxrpHBhmSsp8'; + $pdo = new PDO( + "mysql:host=$host;dbname=$dbname;charset=utf8mb4", + $user, + $pass, + [PDO::ATTR_TIMEOUT => 10, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] + ); + return $pdo; +} + /** 将裸 base64 包装为 PEM。私钥自动探测 PKCS#1(RSA PRIVATE KEY) / PKCS#8(PRIVATE KEY),公钥用 SubjectPublicKeyInfo(PUBLIC KEY) */ function wrapPem($b64, $type) { $b64 = preg_replace('/\s+/', '', $b64); @@ -544,11 +562,36 @@ function queryOrder($token) { $stmt->bindValue(':p', $info['player'], SQLITE3_TEXT); $row = $stmt->execute()->fetchArray(SQLITE3_ASSOC); - // ★ 本地订单不存在 → 直接查平台MySQL,如果平台已支付则同步到本地 + // ★ 本地订单不存在 → 双路查询(MySQL + 官方API),如果平台已支付则同步到本地 if (!$row) { - debugLog('[queryOrder] 本地订单不存在,尝试从平台同步', ['out_trade_no' => $outTradeNo, 'player' => $info['player']]); + debugLog('[queryOrder] 本地订单不存在,尝试双路查询同步', ['out_trade_no' => $outTradeNo, 'player' => $info['player']]); + + // 路径1:平台MySQL直查 + debugLog('[queryOrder] 路径1: 平台MySQL直查', ['out_trade_no' => $outTradeNo]); $platformResult = checkPlatformOrderStatus($outTradeNo); - if (is_array($platformResult) && $platformResult['status'] === 'paid') { + $isPaid = is_array($platformResult) && $platformResult['status'] === 'paid'; + debugLog('[queryOrder] MySQL查询结果', [ + 'out_trade_no' => $outTradeNo, + 'result' => $platformResult, + 'is_paid' => $isPaid, + ]); + + // 路径2:MySQL失败或未支付 → 官方API查询 + if (!$isPaid) { + debugLog('[queryOrder] 路径2: MySQL未找到已支付记录,尝试官方API', ['out_trade_no' => $outTradeNo]); + $apiResult = queryPlatformAPI($outTradeNo); + debugLog('[queryOrder] 官方API查询结果', [ + 'out_trade_no' => $outTradeNo, + 'result' => $apiResult, + ]); + if (is_array($apiResult) && $apiResult['status'] === 'paid') { + $platformResult = $apiResult; + $isPaid = true; + debugLog('[queryOrder] 官方API查询到已支付', ['out_trade_no' => $outTradeNo]); + } + } + + if ($isPaid) { // 平台已支付 → 创建本地订单并标记paid $now = time(); $platformPlayer = $platformResult['player'] ?: $info['player']; @@ -593,12 +636,24 @@ function queryOrder($token) { error('订单不存在,请确认订单号是否正确'); } - // ★ 2026-07-15 核心修复:如果本地还是 created,直接查平台MySQL + // ★ 2026-07-15 核心修复:如果本地还是 created,双路查询(MySQL + 官方API) // 不依赖 poller 补单,确保支付成功后立即返回 paid 状态 $status = $row['status']; if ($status !== 'paid') { + // 路径1:平台MySQL直查 $platformResult = checkPlatformOrderStatus($outTradeNo); $platformPaid = is_array($platformResult) && $platformResult['status'] === 'paid'; + + // 路径2:MySQL失败或未支付 → 官方API查询 + if (!$platformPaid) { + debugLog('[queryOrder] MySQL未支付,尝试官方API', ['out_trade_no' => $outTradeNo]); + $apiResult = queryPlatformAPI($outTradeNo); + if (is_array($apiResult) && $apiResult['status'] === 'paid') { + $platformResult = $apiResult; + $platformPaid = true; + } + } + if ($platformPaid) { // 平台已支付,立即更新本地状态 $now = time(); @@ -656,12 +711,7 @@ function queryOrder($token) { */ function checkPlatformOrderStatus($outTradeNo) { try { - $pdo = new PDO( - "mysql:host=localhost;dbname=caihong;charset=utf8mb4", - 'kH3C3LLinNwYdTF5', - 'sRhsdxrpHBhmSsp8', - [PDO::ATTR_TIMEOUT => 10, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] - ); + $pdo = getPlatformDB(); $stmt = $pdo->prepare("SELECT status, trade_no, money, param, addtime FROM pay_order WHERE out_trade_no = ? LIMIT 1"); $stmt->execute([$outTradeNo]); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -676,6 +726,146 @@ function checkPlatformOrderStatus($outTradeNo) { } } +/** + * 通过官方支付API查询订单状态(MD5签名) + * @param string $outTradeNo 商户订单号 + * @return array|bool 成功返回 ['status'=>'paid', 'trade_no'=>..., 'money'=>..., 'player'=>...],失败返回 false + */ +function queryPlatformAPI($outTradeNo) { + $keys = getPayKeys(); + if (empty($keys['pid']) || empty($keys['md5_key'])) { + debugLog('[queryPlatformAPI] 缺少pid或md5_key', ['pid' => $keys['pid'] ?? '']); + return false; + } + + try { + $params = [ + 'pid' => $keys['pid'], + 'out_trade_no' => $outTradeNo, + 'timestamp' => (string)time(), + 'sign_type' => 'MD5', + ]; + $params['sign'] = buildSign($params, $keys); + + debugLog('[queryPlatformAPI] 开始查询', [ + 'out_trade_no' => $outTradeNo, + 'pid' => $keys['pid'], + 'timestamp' => $params['timestamp'], + 'sign_type' => 'MD5', + ]); + + $url = 'https://zf.ypshidifu.cn/api/pay/query'; + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($params), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], + ]); + $resp = curl_exec($ch); + $err = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $elapsed = curl_getinfo($ch, CURLINFO_TOTAL_TIME); + curl_close($ch); + + debugLog('[queryPlatformAPI] curl完成', [ + 'http_code' => $code, + 'elapsed_s' => round($elapsed, 3), + 'error' => $err ?: 'none', + 'resp_len' => strlen($resp ?? ''), + ]); + + if ($err) { + debugLog('[queryPlatformAPI] curl失败', ['error' => $err, 'out_trade_no' => $outTradeNo]); + return false; + } + + // 记录完整响应(前500字符) + debugLog('[queryPlatformAPI] 响应内容', [ + 'out_trade_no' => $outTradeNo, + 'response' => substr($resp ?? '', 0, 500), + ]); + + $data = json_decode($resp, true); + if (!$data || ($data['code'] ?? -1) !== 0) { + debugLog('[queryPlatformAPI] 接口返回非成功', [ + 'code' => $data['code'] ?? 'null', + 'msg' => $data['msg'] ?? '', + 'out_trade_no' => $outTradeNo, + ]); + return false; + } + + // status: 0=未支付, 1=已支付, 2=已退款, 3=已冻结, 4=预授权 + $st = (int)($data['status'] ?? -1); + debugLog('[queryPlatformAPI] 订单状态', [ + 'status' => $st, + 'status_text' => ['0'=>'未支付','1'=>'已支付','2'=>'已退款','3'=>'已冻结','4'=>'预授权'][$st] ?? '未知', + 'out_trade_no' => $outTradeNo, + 'trade_no' => $data['trade_no'] ?? '', + 'money' => $data['money'] ?? '', + 'param' => $data['param'] ?? '', + ]); + + if ($st === 1) { + return [ + 'status' => 'paid', + 'trade_no' => $data['trade_no'] ?? '', + 'money' => $data['money'] ?? '', + 'player' => $data['param'] ?? '', + ]; + } + return false; + + } catch (\Throwable $e) { + debugLog('[queryPlatformAPI] 异常', [ + 'error' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => basename($e->getFile()), + 'line' => $e->getLine(), + 'out_trade_no' => $outTradeNo, + ]); + return false; + } +} + +// ==================================================================== +// 动作:find_recent_order(localStorage丢失时的兜底:查最近订单) +// ==================================================================== +function findRecentOrder($token) { + $info = validateTokenSilent($token); + if (!$info || empty($info['player'])) { + error('登录状态无效', 401); + } + $db = getDB(); + ensurePayOrdersTable($db); + // 查最近5分钟内的订单(优先created,再paid) + $fiveMinAgo = time() - 300; + $stmt = $db->prepare("SELECT out_trade_no, status, created_at, money, bond_amount, paid_at FROM pay_orders WHERE player_name=:p AND created_at > :t ORDER BY created_at DESC LIMIT 5"); + $stmt->bindValue(':p', $info['player'], SQLITE3_TEXT); + $stmt->bindValue(':t', $fiveMinAgo, SQLITE3_INTEGER); + $rows = []; + $result = $stmt->execute(); + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $rows[] = $row; + } + if (empty($rows)) { + // 本地没有 → 查平台MySQL + // 扩大到30分钟 + $thirtyMinAgo = time() - 1800; + $stmt2 = $db->prepare("SELECT out_trade_no, status, created_at, money, bond_amount, paid_at FROM pay_orders WHERE player_name=:p AND created_at > :t ORDER BY created_at DESC LIMIT 5"); + $stmt2->bindValue(':p', $info['player'], SQLITE3_TEXT); + $stmt2->bindValue(':t', $thirtyMinAgo, SQLITE3_INTEGER); + $result2 = $stmt2->execute(); + while ($row = $result2->fetchArray(SQLITE3_ASSOC)) { + $rows[] = $row; + } + } + success(['orders' => $rows, 'player' => $info['player']]); +} + // ==================================================================== // 入口分发 // ==================================================================== @@ -697,6 +887,9 @@ try { case 'query_order': queryOrder(getParam('token')); break; + case 'find_recent_order': + findRecentOrder(getParam('token')); + break; default: error('未知操作: ' . $action); } diff --git a/api/poller_online.php b/api/poller_online.php index c8ca1b6..c866e79 100644 --- a/api/poller_online.php +++ b/api/poller_online.php @@ -21,11 +21,15 @@ if (!defined('POLLER_NO_AUTO_RUN')) { while (ob_get_level() > 0) { ob_end_clean(); } } -// ===== 配置 ===== -$PLATFORM_DB_HOST = 'localhost'; -$PLATFORM_DB_NAME = 'caihong'; -$PLATFORM_DB_USER = 'kH3C3LLinNwYdTF5'; -$PLATFORM_DB_PASS = 'sRhsdxrpHBhmSsp8'; +// ===== 配置(从 pay_secrets.php 加载,集中管理) ===== +$secretsFile = __DIR__ . '/pay_secrets.php'; +if (@is_file($secretsFile) && @is_readable($secretsFile)) { + @require_once $secretsFile; +} +$PLATFORM_DB_HOST = defined('PAY_MYSQL_HOST') ? PAY_MYSQL_HOST : '127.0.0.1'; +$PLATFORM_DB_NAME = defined('PAY_MYSQL_DBNAME') ? PAY_MYSQL_DBNAME : 'caihong'; +$PLATFORM_DB_USER = defined('PAY_MYSQL_USER') ? PAY_MYSQL_USER : 'kH3C3LLinNwYdTF5'; +$PLATFORM_DB_PASS = defined('PAY_MYSQL_PASS') ? PAY_MYSQL_PASS : 'sRhsdxrpHBhmSsp8'; $PLATFORM_DB_PREFIX = 'pay_'; // SQLite 数据库路径(与 core.php 一致:/caoyuan.ypshidifu.cn/plugin/db/web.db) @@ -83,24 +87,35 @@ function getSQLite() { } function getPlatformDB() { - static $pdo = null; - if ($pdo === null) { - try { - debugLog('[poller] 正在连接平台MySQL...'); - $start = microtime(true); - $dsn = "mysql:host={$GLOBALS['PLATFORM_DB_HOST']};dbname={$GLOBALS['PLATFORM_DB_NAME']};charset=utf8mb4"; - $pdo = new PDO($dsn, $GLOBALS['PLATFORM_DB_USER'], $GLOBALS['PLATFORM_DB_PASS'], [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_TIMEOUT => 10, // ★ 增加到10秒,确保连接成功 - ]); - $elapsed = round((microtime(true) - $start) * 1000); - debugLog("[poller] MySQL连接成功", ['elapsed_ms' => $elapsed]); - } catch (Exception $e) { - debugLog("[poller] MySQL连接失败", ['error' => $e->getMessage(), 'elapsed_ms' => round((microtime(true) - $start) * 1000)]); - throw $e; - } + try { + debugLog('[poller] 正在连接平台MySQL...', [ + 'host' => $GLOBALS['PLATFORM_DB_HOST'], + 'db' => $GLOBALS['PLATFORM_DB_NAME'], + 'user' => $GLOBALS['PLATFORM_DB_USER'], + ]); + $start = microtime(true); + // 使用127.0.0.1避免localhost解析问题 + $dsn = "mysql:host=127.0.0.1;dbname={$GLOBALS['PLATFORM_DB_NAME']};charset=utf8mb4"; + debugLog('[poller] MySQL DSN: ' . $dsn); + $pdo = new PDO($dsn, $GLOBALS['PLATFORM_DB_USER'], $GLOBALS['PLATFORM_DB_PASS'], [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_TIMEOUT => 15, // 增加到15秒 + ]); + $elapsed = round((microtime(true) - $start) * 1000); + debugLog("[poller] MySQL连接成功", ['elapsed_ms' => $elapsed]); + return $pdo; + } catch (Exception $e) { + $elapsed = round((microtime(true) - $start) * 1000); + debugLog("[poller] MySQL连接失败", [ + 'error' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'elapsed_ms'=> $elapsed, + 'dsn' => $dsn ?? 'undefined', + ]); + throw $e; } - return $pdo; } /** @@ -143,8 +158,20 @@ function pollPaidOrders() { try { $pdo = getPlatformDB(); } catch (\Throwable $e) { - debugLog('[poller_online] 平台数据库连接失败: ' . $e->getMessage()); - echo json_encode(['error' => 'platform_db_connect_failed', 'detail' => $e->getMessage()]); + debugLog('[poller_online] 平台数据库连接失败', [ + 'error' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'context' => 'pollPaidOrders', + ]); + echo json_encode([ + 'error' => 'platform_db_connect_failed', + 'detail' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => basename($e->getFile()), + 'line' => $e->getLine(), + ]); return; } @@ -233,19 +260,16 @@ function pollPaidOrders() { $upd->execute(); } - // 写 web_transactions(幂等:按 detail=out_trade_no + type=recharge 去重) - $txCheck = $sqlite->prepare("SELECT id FROM web_transactions WHERE detail = :d AND type = 'recharge' LIMIT 1"); - $txCheck->bindValue(':d', $outTradeNo, SQLITE3_TEXT); - $txRow = $txCheck->execute()->fetchArray(SQLITE3_ASSOC); - - if (!$txRow) { - $txIns = $sqlite->prepare("INSERT INTO web_transactions (player_name, type, amount, operator, reason, detail, status, created_at) VALUES (:p, 'recharge', :a, '支付平台(补单)', '在线充值(支付宝)', :d, 'pending', :t)"); - $txIns->bindValue(':p', $playerName, SQLITE3_TEXT); - $txIns->bindValue(':a', $bonds, SQLITE3_INTEGER); - $txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT); - $txIns->bindValue(':t', $now, SQLITE3_INTEGER); - $txIns->execute(); + // 写 web_transactions(幂等:INSERT OR IGNORE 防止竞态条件重复插入) + $txIns = $sqlite->prepare("INSERT OR IGNORE INTO web_transactions (player_name, type, amount, operator, reason, detail, status, created_at) VALUES (:p, 'recharge', :a, '支付平台(补单)', '在线充值(支付宝)', :d, 'pending', :t)"); + $txIns->bindValue(':p', $playerName, SQLITE3_TEXT); + $txIns->bindValue(':a', $bonds, SQLITE3_INTEGER); + $txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT); + $txIns->bindValue(':t', $now, SQLITE3_INTEGER); + $txIns->execute(); + // 检查是否是新插入的(affected_rows > 0 表示新插入,=0 表示已存在被忽略) + if ($sqlite->changes() > 0) { debugLog('[poller_online] 充值交易已写入 web_transactions', [ 'player' => $playerName, 'bonds' => $bonds, 'out_trade_no' => $outTradeNo, 'money' => $money, diff --git a/api/recharge_orders_api.php b/api/recharge_orders_api.php index dbeb393..8cab785 100644 --- a/api/recharge_orders_api.php +++ b/api/recharge_orders_api.php @@ -10,6 +10,29 @@ ob_start(); require_once __DIR__ . '/../core.php'; ob_end_clean(); +// 加载支付密钥(含MySQL凭据) +$secretsFile = __DIR__ . '/pay_secrets.php'; +if (@is_file($secretsFile) && @is_readable($secretsFile)) { + @require_once $secretsFile; +} + +/** + * 获取平台MySQL数据库连接(集中凭据管理) + */ +function getPlatformDB() { + $host = defined('PAY_MYSQL_HOST') ? PAY_MYSQL_HOST : '127.0.0.1'; + $dbname = defined('PAY_MYSQL_DBNAME') ? PAY_MYSQL_DBNAME : 'caihong'; + $user = defined('PAY_MYSQL_USER') ? PAY_MYSQL_USER : 'kH3C3LLinNwYdTF5'; + $pass = defined('PAY_MYSQL_PASS') ? PAY_MYSQL_PASS : 'sRhsdxrpHBhmSsp8'; + $pdo = new PDO( + "mysql:host=$host;dbname=$dbname;charset=utf8mb4", + $user, + $pass, + [PDO::ATTR_TIMEOUT => 15, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] + ); + return $pdo; +} + // 检查管理员登录 if (!isAdminLoggedIn()) { error('无权限访问', 403); @@ -131,18 +154,44 @@ function handleReconcile() { // 直接包含 poller_online.php 并调用 pollPaidOrders define('POLLER_NO_AUTO_RUN', true); require_once __DIR__ . '/poller_online.php'; - + + // 记录对账开始 + if (function_exists('debugLog')) { + debugLog('[reconcile] 开始对账', [ + 'admin' => $_SESSION['admin_user'] ?? 'unknown', + 'time' => date('Y-m-d H:i:s'), + ]); + } + // 开始对账 ob_start(); pollPaidOrders(); $output = ob_get_clean(); - + + // 记录原始输出 + if (function_exists('debugLog')) { + debugLog('[reconcile] pollPaidOrders输出', [ + 'output_len' => strlen($output), + 'output' => substr($output, 0, 500), + ]); + } + // 解析结果 $result = json_decode($output, true); if (!$result) { - error('对账脚本执行异常'); + if (function_exists('debugLog')) { + debugLog('[reconcile] JSON解析失败', ['output' => substr($output, 0, 200)]); + } + error('对账脚本执行异常: ' . substr($output, 0, 100)); } - + + // 记录解析结果 + if (function_exists('debugLog')) { + debugLog('[reconcile] 解析结果', [ + 'result' => $result, + ]); + } + // 直接输出结果,不再用 success() 包装(避免双重 JSON) header('Content-Type: application/json; charset=utf-8'); echo json_encode($result, JSON_UNESCAPED_UNICODE); @@ -158,12 +207,7 @@ function handleSyncFromPlatform() { ensureWebTransactionsTable($db); try { - $pdo = new PDO( - "mysql:host=localhost;dbname=caihong;charset=utf8mb4", - 'kH3C3LLinNwYdTF5', - 'sRhsdxrpHBhmSsp8', - [PDO::ATTR_TIMEOUT => 10, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] - ); + $pdo = getPlatformDB(); // 查所有已支付订单 $stmt = $pdo->query("SELECT out_trade_no, trade_no, uid, money, status, param, addtime FROM pay_order WHERE status IN (1, 2) ORDER BY addtime DESC LIMIT 200"); diff --git a/player.php b/player.php index 6eae0b6..3bda940 100644 --- a/player.php +++ b/player.php @@ -1089,7 +1089,30 @@ if ($currentVersion !== $BUILD_VERSION) { document.getElementById('rechargeQueryBtn').style.display = 'block'; pollRechargeOrder(false); } else { - st.innerHTML = '已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。'; + // ★ 兜底:localStorage丢失时,从服务器查最近订单 + st.innerHTML = '已返回,正在从服务器查找订单…'; + document.getElementById('rechargeQueryBtn').style.display = 'block'; + (async () => { + try { + const res = await api('pay.php', {action: 'find_recent_order'}); + if (res.success && res.data && res.data.orders && res.data.orders.length > 0) { + const latest = res.data.orders[0]; + localStorage.setItem('sdf1_last_recharge_no', latest.out_trade_no); + if (latest.status === 'paid') { + st.innerHTML = '✅ 此订单已支付完成。'; + const btn = document.getElementById('rechargeBtn'); + btn.disabled = true; btn.textContent = '✅ 支付完成'; btn.onclick = null; + } else { + st.innerHTML = '检测到订单 ' + latest.out_trade_no + ',正在确认到账…'; + pollRechargeOrder(false); + } + } else { + st.innerHTML = '已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。'; + } + } catch (e) { + st.innerHTML = '已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。'; + } + })(); } // 清除 URL 中的 paid 标记,避免刷新重复提示 try {