订单号固定格式改递增序列(00000000→CYZJ00000001起);补单器改定向补单:查本地活跃完整订单号精确匹配平台,不再按前缀批量扫
This commit is contained in:
+13
-12
@@ -204,8 +204,10 @@ if (!function_exists('payComposeOrderNo')) {
|
||||
if (!function_exists('payMakeFixedOrderNo')) {
|
||||
/**
|
||||
* 固定订单号格式分支:前缀码 + 订单号格式模板。
|
||||
* 模板 [] 去掉;纯数字 → 每个 0 换成随机数字(如 00000000 → 48392017);
|
||||
* 模板非纯数字或为空 → 回退保底 6 位递增序列 000001、000002…(持久化计数器防撞单)。
|
||||
* 模板 [] 去掉;
|
||||
* - 纯数字(如 00000000)→ 按【模板位数】递增序列:00000001、00000002…
|
||||
* (计数器持久化在 db/pay_order_seq.txt,flock 防撞单;当前无订单则从 1 开始);
|
||||
* - 非纯数字或为空 → 保底 6 位递增序列 000001、000002…。
|
||||
* @param array $cfg pay.md 配置区
|
||||
* @return string
|
||||
*/
|
||||
@@ -214,15 +216,11 @@ if (!function_exists('payMakeFixedOrderNo')) {
|
||||
$tpl = isset($cfg['订单号格式']) ? (string)$cfg['订单号格式'] : '';
|
||||
$tpl = str_replace(array('[', ']', "\u{FF3B}", "\u{FF3D}", ' ', "\t"), '', $tpl);
|
||||
if ($tpl !== '' && ctype_digit($tpl)) {
|
||||
$out = '';
|
||||
$n = strlen($tpl);
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$out .= ($tpl[$i] === '0') ? (string)mt_rand(0, 9) : $tpl[$i];
|
||||
}
|
||||
$no = $p . $out;
|
||||
// ★ 纯数字模板:按模板位数递增序列(00000000 → CYZJ00000001, CYZJ00000002…)
|
||||
$no = payMakeSequentialOrderNo($tpl);
|
||||
} else {
|
||||
// 非纯数字模板(或空)→ 保底 6 位递增序列
|
||||
$no = payMakeSequentialOrderNo();
|
||||
$no = payMakeSequentialOrderNo('000000');
|
||||
}
|
||||
$max = defined('PAY_ORDER_MAX_LEN') ? (int)PAY_ORDER_MAX_LEN : 64;
|
||||
if ($max > 0 && strlen($no) > $max) $no = substr($no, 0, $max);
|
||||
@@ -232,13 +230,16 @@ if (!function_exists('payMakeFixedOrderNo')) {
|
||||
|
||||
if (!function_exists('payMakeSequentialOrderNo')) {
|
||||
/**
|
||||
* 保底 6 位递增序列:前缀 + 000001、000002…(纯数字模板不可用时的固定格式回退)。
|
||||
* 递增序列订单号:前缀 + 模板位数的序列(000001、000002…,默认 6 位)。
|
||||
* 计数器持久化在 db/pay_order_seq.txt(flock 排他锁读写改,防并发撞单);
|
||||
* 计数器不可写时退化为「前缀+日期时间+3位随机」,保底不与历史单撞号。
|
||||
*
|
||||
* @param string $tpl 位数模板(纯数字,如 '00000000'),决定序列补零位数
|
||||
* @return string
|
||||
*/
|
||||
function payMakeSequentialOrderNo() {
|
||||
function payMakeSequentialOrderNo($tpl = '000000') {
|
||||
$p = payOrderPrefix();
|
||||
$width = strlen(ctype_digit($tpl) ? $tpl : '000000');
|
||||
$dir = dirname(__DIR__) . '/db';
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
$file = $dir . '/pay_order_seq.txt';
|
||||
@@ -260,7 +261,7 @@ if (!function_exists('payMakeSequentialOrderNo')) {
|
||||
if ($n <= 0) {
|
||||
return $p . date('ymdHis') . str_pad((string)mt_rand(0, 999), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return $p . str_pad((string)$n, 6, '0', STR_PAD_LEFT);
|
||||
return $p . str_pad((string)$n, $width, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-11
@@ -215,25 +215,37 @@ function pollPaidOrders() {
|
||||
// 仍不过滤 notify:平台可能已标记 notify=1 但本地 DB 未更新(HTTP 回调被
|
||||
// CF WAF 拦截),是否补单以本地 pay_orders 状态为准。
|
||||
$prefix = $PLATFORM_DB_PREFIX;
|
||||
// ★ 2026-10-06:扫描前缀改为读配置(原来写死 'RE')
|
||||
// ★ 2026-10-07:前缀第一来源改为站点根 pay.md「前缀码」;扫描改为「当前前缀+历史前缀」
|
||||
// 双条件(payOrderPrefixLikeAny),手工改过前缀码后,改动前已生成的在途单仍能补回。
|
||||
$stmt = $pdo->prepare("SELECT out_trade_no, trade_no, uid, money, status, notify, param, version, addtime FROM `{$prefix}order` WHERE (" . payOrderPrefixLikeAny() . ") AND status IN (1, 2) ORDER BY addtime ASC LIMIT 50");
|
||||
// ★ 2026-10-07 改为【定向补单】:不再按前缀批量扫平台共享库,
|
||||
// 而是先取本地 pay_orders 里所有未补单(非 paid)的【完整订单号】,
|
||||
// 再让平台只按这些完整订单号精确匹配。这样就算生产/测试共用 CYZJ 前缀,
|
||||
// 测试服脚本也只会查它自己本地活跃的订单号,抓不到生产服的单。
|
||||
$activeStmt = $sqlite->prepare("SELECT out_trade_no FROM pay_orders WHERE status != 'paid' AND out_trade_no IS NOT NULL AND out_trade_no != ''");
|
||||
$activeStmt->execute();
|
||||
$activeNos = array();
|
||||
while ($a = $activeStmt->fetchArray(SQLITE3_ASSOC)) {
|
||||
$activeNos[] = (string)$a['out_trade_no'];
|
||||
}
|
||||
if (empty($activeNos)) {
|
||||
echo json_encode(['result' => 'no_local_active_orders']);
|
||||
return;
|
||||
}
|
||||
// 平台侧按完整订单号精确匹配(IN 子句,最多取前 200 个,足够覆盖并发场景)
|
||||
$activeNos = array_slice($activeNos, 0, 200);
|
||||
$inList = "'" . implode("','", array_map('addslashes', $activeNos)) . "'";
|
||||
$stmt = $pdo->prepare("SELECT out_trade_no, trade_no, uid, money, status, notify, param, version, addtime FROM `{$prefix}order` WHERE out_trade_no IN (" . $inList . ") AND status IN (1, 2)");
|
||||
$stmt->execute();
|
||||
$allOrders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($allOrders)) {
|
||||
echo json_encode(['result' => 'no_paid_orders_on_platform']);
|
||||
echo json_encode(['result' => 'no_paid_orders_on_platform', 'active_local' => count($activeNos)]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 只保留本方订单(前缀兜底校验,与创单端同一份配置),再过滤掉本地已处理(paid)的
|
||||
// 只保留「本地确实在跟踪、且还没补单」的订单(二次过滤,防本地已被补单/已支付)
|
||||
$orders = [];
|
||||
$foreign = 0;
|
||||
foreach ($allOrders as $o) {
|
||||
$outNo = (string)$o['out_trade_no'];
|
||||
if (!payOrderIsOurs($outNo)) {
|
||||
$foreign++; // 非本方订单:不建单、不写流水、不改它的 notify
|
||||
if (!in_array($outNo, $activeNos, true)) {
|
||||
continue;
|
||||
}
|
||||
$check = $sqlite->prepare("SELECT status FROM pay_orders WHERE out_trade_no = :no");
|
||||
@@ -245,7 +257,7 @@ function pollPaidOrders() {
|
||||
}
|
||||
|
||||
if (empty($orders)) {
|
||||
echo json_encode(['result' => 'no_pending_orders', 'platform_paid_count' => count($allOrders), 'foreign_skipped' => $foreign]);
|
||||
echo json_encode(['result' => 'no_pending_orders', 'active_local' => count($activeNos)]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,7 +349,7 @@ function pollPaidOrders() {
|
||||
'result' => 'ok',
|
||||
'processed' => $processed,
|
||||
'skipped' => $skipped,
|
||||
'foreign_skipped' => $foreign,
|
||||
'foreign_skipped' => 0,
|
||||
'total' => count($orders),
|
||||
'elapsed_ms' => $elapsed,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user