修复: 订单不存在问题-平台MySQL直查+同步机制+管理后台手动同步
- queryOrder: 本地订单不存在时直查平台MySQL,找到则自动同步到本地 - handleNotify: 回调找不到本地订单时从平台MySQL补建 - checkPlatformOrderStatus: 超时3s→10s,返回更多字段(trade_no/money/player) - recharge_orders_api: 新增sync_platform action,管理员手动从平台同步全部已支付订单 - admin.php: 充值对账页新增「从平台同步」按钮 - 解决: 游戏已发钱但前端/后台显示订单不存在的问题 关闭 #1069 #1070
This commit is contained in:
@@ -3403,6 +3403,7 @@ async function loadRechargeOrders(el) {
|
||||
<option value="200">200条</option>
|
||||
</select>
|
||||
<button class="btn btn-blue" onclick="_roReconcile()">🔄 立即对账</button>
|
||||
<button class="btn btn-yellow" onclick="_roSyncPlatform()" style="margin-left:4px">⬇️ 从平台同步</button>
|
||||
</div>
|
||||
<div id="roList"><div class="ro-empty">加载中...</div></div>
|
||||
</div>`;
|
||||
@@ -3483,6 +3484,20 @@ async function _roReconcile() {
|
||||
}
|
||||
} catch(e) { glassAlert('对账请求失败: '+e.message); }
|
||||
}
|
||||
async function _roSyncPlatform() {
|
||||
if (!await glassConfirm('从支付平台同步所有已支付订单到本地数据库,同时写入充值流水供Java拉取。是否继续?')) return;
|
||||
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) {
|
||||
glassAlert('同步完成!平台共 '+d.data.platform_total+' 笔,新同步 '+d.data.synced+' 笔,已存在 '+d.data.already_paid+' 笔');
|
||||
_roLoad();
|
||||
} else {
|
||||
glassAlert('同步失败: '+(d.message||'未知错误'));
|
||||
}
|
||||
} catch(e) { glassAlert('同步请求失败: '+e.message); }
|
||||
}
|
||||
document.addEventListener('keydown', function(e) { if (e.key==='Escape') _roCloseDtl(); });
|
||||
|
||||
async function loadCashierManage(el) {
|
||||
|
||||
+88
-9
@@ -381,8 +381,36 @@ function handleNotify() {
|
||||
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
if (!$row) {
|
||||
debugLog('[pay notify] 订单不存在: ' . $outTradeNo);
|
||||
// 订单未知也返回 success,避免平台死循环重试无效通知
|
||||
debugLog('[pay notify] 本地订单不存在: ' . $outTradeNo . ',尝试从平台同步');
|
||||
// 本地没有订单 → 尝试从平台MySQL补建
|
||||
$platformResult = checkPlatformOrderStatus($outTradeNo);
|
||||
if (is_array($platformResult) && $platformResult['status'] === 'paid') {
|
||||
$now = time();
|
||||
$platformPlayer = $platformResult['player'] ?? ($params['param'] ?? 'unknown');
|
||||
$bonds = PAY_TEST_BONDS;
|
||||
$ins = $db->prepare("INSERT OR IGNORE INTO pay_orders (out_trade_no, trade_no, player_name, money, bond_amount, status, name, submit_params, created_at, paid_at) VALUES (:no, :tn, :p, :m, :b, 'paid', :n, '', :t, :t)");
|
||||
$ins->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$ins->bindValue(':tn', $platformResult['trade_no'] ?? '', SQLITE3_TEXT);
|
||||
$ins->bindValue(':p', $platformPlayer, SQLITE3_TEXT);
|
||||
$ins->bindValue(':m', $platformResult['money'] ?? '0.01', SQLITE3_TEXT);
|
||||
$ins->bindValue(':b', $bonds, SQLITE3_INTEGER);
|
||||
$ins->bindValue(':n', PAY_TEST_NAME, SQLITE3_TEXT);
|
||||
$ins->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$ins->execute();
|
||||
|
||||
// 写 web_transactions
|
||||
$txIns = $db->prepare("INSERT INTO web_transactions (player_name, type, amount, operator, reason, detail, status, created_at) VALUES (:p, 'recharge', :a, '支付平台(callback同步)', '在线充值(支付宝)', :d, 'pending', :t)");
|
||||
$txIns->bindValue(':p', $platformPlayer, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':a', $bonds, SQLITE3_INTEGER);
|
||||
$txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$txIns->execute();
|
||||
|
||||
debugLog('[pay notify] callback同步成功', ['out_trade_no' => $outTradeNo, 'player' => $platformPlayer]);
|
||||
echo 'success';
|
||||
exit;
|
||||
}
|
||||
// 平台也没有 → 返回success避免死循环
|
||||
echo 'success';
|
||||
exit;
|
||||
}
|
||||
@@ -515,16 +543,63 @@ function queryOrder($token) {
|
||||
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':p', $info['player'], SQLITE3_TEXT);
|
||||
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
|
||||
// ★ 本地订单不存在 → 直接查平台MySQL,如果平台已支付则同步到本地
|
||||
if (!$row) {
|
||||
error('订单不存在');
|
||||
debugLog('[queryOrder] 本地订单不存在,尝试从平台同步', ['out_trade_no' => $outTradeNo, 'player' => $info['player']]);
|
||||
$platformResult = checkPlatformOrderStatus($outTradeNo);
|
||||
if (is_array($platformResult) && $platformResult['status'] === 'paid') {
|
||||
// 平台已支付 → 创建本地订单并标记paid
|
||||
$now = time();
|
||||
$platformPlayer = $platformResult['player'] ?: $info['player'];
|
||||
$bonds = PAY_TEST_BONDS;
|
||||
$money = $platformResult['money'] ?? PAY_TEST_MONEY;
|
||||
$ins = $db->prepare("INSERT OR IGNORE INTO pay_orders (out_trade_no, trade_no, player_name, money, bond_amount, status, name, submit_params, created_at, paid_at) VALUES (:no, :tn, :p, :m, :b, 'paid', :n, '', :t, :t)");
|
||||
$ins->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$ins->bindValue(':tn', $platformResult['trade_no'] ?? '', SQLITE3_TEXT);
|
||||
$ins->bindValue(':p', $platformPlayer, SQLITE3_TEXT);
|
||||
$ins->bindValue(':m', $money, SQLITE3_TEXT);
|
||||
$ins->bindValue(':b', $bonds, SQLITE3_INTEGER);
|
||||
$ins->bindValue(':n', PAY_TEST_NAME, SQLITE3_TEXT);
|
||||
$ins->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$ins->execute();
|
||||
|
||||
// 写 web_transactions
|
||||
$txCheck = $db->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 = $db->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', $platformPlayer, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':a', $bonds, SQLITE3_INTEGER);
|
||||
$txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$txIns->execute();
|
||||
}
|
||||
|
||||
debugLog('[queryOrder] 平台同步成功', ['out_trade_no' => $outTradeNo, 'player' => $platformPlayer]);
|
||||
success([
|
||||
'out_trade_no' => $outTradeNo,
|
||||
'status' => 'paid',
|
||||
'money' => $money,
|
||||
'bonds' => $bonds,
|
||||
'paid_at' => $now,
|
||||
'created_at' => $now,
|
||||
], '已支付,游戏内债券发放中(若未到账请稍候或联系管理员)');
|
||||
return;
|
||||
}
|
||||
// 平台也没有 → 真的不存在
|
||||
debugLog('[queryOrder] 平台也无此订单', ['out_trade_no' => $outTradeNo]);
|
||||
error('订单不存在,请确认订单号是否正确');
|
||||
}
|
||||
|
||||
// ★ 2026-07-15 核心修复:如果本地还是 created,直接查平台MySQL
|
||||
// 不依赖 poller 补单,确保支付成功后立即返回 paid 状态
|
||||
$status = $row['status'];
|
||||
if ($status !== 'paid') {
|
||||
$platformStatus = checkPlatformOrderStatus($outTradeNo);
|
||||
if ($platformStatus === 'paid') {
|
||||
$platformResult = checkPlatformOrderStatus($outTradeNo);
|
||||
$platformPaid = is_array($platformResult) && $platformResult['status'] === 'paid';
|
||||
if ($platformPaid) {
|
||||
// 平台已支付,立即更新本地状态
|
||||
$now = time();
|
||||
$upd = $db->prepare("UPDATE pay_orders SET status='paid', paid_at=:t WHERE out_trade_no=:no AND status != 'paid'");
|
||||
@@ -585,15 +660,19 @@ function checkPlatformOrderStatus($outTradeNo) {
|
||||
"mysql:host=localhost;dbname=caihong;charset=utf8mb4",
|
||||
'kH3C3LLinNwYdTF5',
|
||||
'sRhsdxrpHBhmSsp8',
|
||||
[PDO::ATTR_TIMEOUT => 3, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
[PDO::ATTR_TIMEOUT => 10, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
$stmt = $pdo->prepare("SELECT status FROM pay_order WHERE out_trade_no = ? AND status IN (1, 2) LIMIT 1");
|
||||
$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);
|
||||
return $row ? 'paid' : 'not_found';
|
||||
if (!$row) return 'not_found';
|
||||
if (in_array($row['status'], [1, 2])) {
|
||||
return ['status' => 'paid', 'trade_no' => $row['trade_no'], 'money' => $row['money'], 'player' => $row['param'] ?? ''];
|
||||
}
|
||||
return 'not_paid';
|
||||
} catch (\Throwable $e) {
|
||||
debugLog('[checkPlatformOrderStatus] 查询失败', ['error' => $e->getMessage(), 'out_trade_no' => $outTradeNo]);
|
||||
return 'not_found';
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+107
-1
@@ -27,6 +27,9 @@ switch ($action) {
|
||||
case 'reconcile':
|
||||
handleReconcile();
|
||||
break;
|
||||
case 'sync_platform':
|
||||
handleSyncFromPlatform();
|
||||
break;
|
||||
default:
|
||||
error('未知操作');
|
||||
}
|
||||
@@ -147,8 +150,111 @@ function handleReconcile() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 pay_orders 表存在(从 pay.php 复制)
|
||||
* 从平台MySQL同步所有已支付订单到本地
|
||||
*/
|
||||
function handleSyncFromPlatform() {
|
||||
$db = getDB();
|
||||
ensurePayOrdersTable($db);
|
||||
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]
|
||||
);
|
||||
|
||||
// 查所有已支付订单
|
||||
$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");
|
||||
$platformOrders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$synced = 0;
|
||||
$alreadyPaid = 0;
|
||||
$errors = [];
|
||||
$now = time();
|
||||
|
||||
foreach ($platformOrders as $po) {
|
||||
$outTradeNo = $po['out_trade_no'];
|
||||
$player = $po['param'] ?? 'unknown';
|
||||
$tradeNo = $po['trade_no'] ?? '';
|
||||
$money = (string)$po['money'];
|
||||
$bonds = ($money >= '1.0') ? (int)($money * 10) : max(1, (int)($money * 100));
|
||||
|
||||
// 查本地是否已有
|
||||
$check = $db->prepare("SELECT id, status FROM pay_orders WHERE out_trade_no = :no");
|
||||
$check->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$localRow = $check->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
|
||||
if ($localRow && $localRow['status'] === 'paid') {
|
||||
$alreadyPaid++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($localRow) {
|
||||
// 已有记录但未标记paid → 更新
|
||||
$upd = $db->prepare("UPDATE pay_orders SET status='paid', trade_no=:tn, paid_at=:t WHERE out_trade_no=:no AND status != 'paid'");
|
||||
$upd->bindValue(':tn', $tradeNo, SQLITE3_TEXT);
|
||||
$upd->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$upd->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$upd->execute();
|
||||
} else {
|
||||
// 本地无记录 → 创建
|
||||
$ins = $db->prepare("INSERT OR IGNORE INTO pay_orders (out_trade_no, trade_no, player_name, money, bond_amount, status, name, submit_params, created_at, paid_at) VALUES (:no, :tn, :p, :m, :b, 'paid', :n, '', :t, :t)");
|
||||
$ins->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
|
||||
$ins->bindValue(':tn', $tradeNo, SQLITE3_TEXT);
|
||||
$ins->bindValue(':p', $player, SQLITE3_TEXT);
|
||||
$ins->bindValue(':m', $money, SQLITE3_TEXT);
|
||||
$ins->bindValue(':b', $bonds, SQLITE3_INTEGER);
|
||||
$ins->bindValue(':n', '债券充值', SQLITE3_TEXT);
|
||||
$ins->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$ins->execute();
|
||||
}
|
||||
|
||||
// 写 web_transactions(幂等)
|
||||
$txCheck = $db->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 = $db->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', $player, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':a', $bonds, SQLITE3_INTEGER);
|
||||
$txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
|
||||
$txIns->bindValue(':t', $now, SQLITE3_INTEGER);
|
||||
$txIns->execute();
|
||||
}
|
||||
|
||||
$synced++;
|
||||
}
|
||||
|
||||
success([
|
||||
'platform_total' => count($platformOrders),
|
||||
'synced' => $synced,
|
||||
'already_paid' => $alreadyPaid,
|
||||
], "同步完成:平台{$synced}笔已同步,{$alreadyPaid}笔已存在");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
error('同步失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 web_transactions 表存在
|
||||
*/
|
||||
function ensureWebTransactionsTable($db) {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_name TEXT NOT NULL,
|
||||
type TEXT,
|
||||
amount INTEGER DEFAULT 0,
|
||||
operator TEXT,
|
||||
reason TEXT,
|
||||
detail TEXT,
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at INTEGER
|
||||
)");
|
||||
@$db->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_web_tx_detail_type ON web_transactions(detail, type)");
|
||||
}
|
||||
function ensurePayOrdersTable($db) {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS pay_orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
Reference in New Issue
Block a user