修复MySQL连接: 集中管理凭据,常量未定义时用默认值兜底
This commit is contained in:
@@ -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 = '<div class="ro-empty">加载失败: '+d.error.message+'</div>'; return; }
|
||||
if (d.error || d.success === false) { el.innerHTML = '<div class="ro-empty">加载失败: '+(d.error?.message||d.message||'未知错误')+'</div>'; 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||'未知错误'));
|
||||
|
||||
+203
-10
@@ -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);
|
||||
}
|
||||
|
||||
+60
-36
@@ -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,
|
||||
|
||||
+54
-10
@@ -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");
|
||||
|
||||
+24
-1
@@ -1089,7 +1089,30 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
document.getElementById('rechargeQueryBtn').style.display = 'block';
|
||||
pollRechargeOrder(false);
|
||||
} else {
|
||||
st.innerHTML = '<span style="color:var(--yellow)">已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。</span>';
|
||||
// ★ 兜底:localStorage丢失时,从服务器查最近订单
|
||||
st.innerHTML = '<span style="color:var(--accent)">已返回,正在从服务器查找订单…</span>';
|
||||
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 = '<span style="color:var(--green)">✅ 此订单已支付完成。</span>';
|
||||
const btn = document.getElementById('rechargeBtn');
|
||||
btn.disabled = true; btn.textContent = '✅ 支付完成'; btn.onclick = null;
|
||||
} else {
|
||||
st.innerHTML = '<span style="color:var(--accent)">检测到订单 ' + latest.out_trade_no + ',正在确认到账…</span>';
|
||||
pollRechargeOrder(false);
|
||||
}
|
||||
} else {
|
||||
st.innerHTML = '<span style="color:var(--yellow)">已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。</span>';
|
||||
}
|
||||
} catch (e) {
|
||||
st.innerHTML = '<span style="color:var(--yellow)">已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。</span>';
|
||||
}
|
||||
})();
|
||||
}
|
||||
// 清除 URL 中的 paid 标记,避免刷新重复提示
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user