第二次更新

This commit is contained in:
2026-09-10 20:56:46 +08:00
parent 4d6a7e7aac
commit caeb4d6760
310 changed files with 2427 additions and 382 deletions
@@ -5,12 +5,15 @@
// | OilCard::share 创建 yoshop_partners_order 后 push 'partners_queue',本命令消费:
// | 1. 每个 order_id 用 RedisLock 串行化,锁内重读订单状态
// | 2. 生成企油通全平台唯一编号(确定性派生,可安全重试)
// | 3. 调 7.7 createAndSendQuota;支付宝企业码通道下「受理成功即完成发放」
// | 4. 发放成功 -> oil_status=1 / oil_status_zsy=2,投 notify_queue 回调下单平台
// | 3. 调 7.7 createAndSendQuota
// | 4. 受理成功 -> oil_status_zsy=1「发放中」,交给 qyt:status 轮询 7.8 定结果
// |
// | ⚠️ 7.7 是异步接口,返回成功只代表【受理】(文档 §5 第 7 条)。本命令不再直接
// | 判定发放成功,也不再投 notify_queue —— 那是 QytStatusQueue 拿到 7.8 结果后的事。
// |
// | 并发/原子性:
// | · oil_status_zsy 只在 7.7 结果确定后离开 0;空闲时 reconcile() 补投丢失的队列消息
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才回调,天然幂等
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才继续,天然幂等
// | · batch_no 确定性派生 -> 崩溃重试用同一值,依赖企油通按 batch_no 幂等去重
// | 启动:php think partners:queue(可多进程;RedisLock 保证同一订单串行)
// +----------------------------------------------------------------------
@@ -18,6 +21,7 @@ namespace app\console\command;
use app\common\lock\RedisLock;
use app\common\model\partners\Order as OrderModel;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytOil;
@@ -82,25 +86,57 @@ class PartnersQueue extends Command
return;
}
// 1. 确定性派生编号(含企业前缀),重试时保持一致
$seed = $order['partners_order_no'] ?: (string) $order['order_id'];
$batchNo = QytOil::genNo('B', $seed);
$waybillNo = QytOil::genNo('W', $seed);
$waybillNoCode = QytOil::genNo('C', $seed);
// 用下单时固化在订单上的企业,而不是当前默认企业:
// 之后改了合作商绑定或切了默认企业,在途订单仍按原企业发放
$enterprise = EnterpriseModel::detail((int) $order['qyt_enterprise_id']);
if (!$enterprise) {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update(['oil_status_zsy' => 3, 'oil_text' => '本单绑定的企油通企业不存在,无法发放', 'update_time' => time()]);
$failed && $this->refundBalance($order);
return;
}
$qyt->useEnterprise($enterprise);
// 1. 编号:库里已生成过就直接沿用,绝不重算。
// no_prefix 现在可在后台修改,若重试时重新派生,batch_no 会变成另一个值,
// 上游会当成新批次 -> 同一订单被发放两次。
// 种子里带上重发次数:后台「重新发放」会清空编号并把 qyt_retry_num +1,
// 这样重发能拿到全新的 batch_no(否则企油通按老批次幂等,7.8 永远返回那条旧的失败记录)。
$retryNum = (int) $order['qyt_retry_num'];
$seed = ($order['partners_order_no'] ?: (string) $order['order_id'])
. ($retryNum > 0 ? 'R' . $retryNum : '');
$batchNo = $order['batch_no'] ?: $qyt->no('B', $seed);
$waybillNo = $order['waybill_no'] ?: $qyt->no('W', $seed);
$waybillNoCode = $order['waybill_no_code'] ?: $qyt->no('C', $seed);
$name = trim((string) $order['driver_name']);
$idCard = trim((string) $order['id_card']);
if ($name === '' || $order['mobile'] === '') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update(['oil_status_zsy' => 3, 'oil_text' => '缺司机姓名/手机号,无法发放', 'update_time' => time()]);
$failed && $this->refundBalance($order);
return;
}
// 通道切换后,下单时不需要身份证的老单可能落到实名通道上:
// 直接判失败退款,不做无意义重试(重试也永远补不到身份证号)
if ($qyt->requireIdCard() && $idCard === '') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update([
'oil_status_zsy' => 3,
'oil_text' => '当前通道(' . $qyt->channelName() . ')要求实名,该单缺司机身份证号',
'update_time' => time(),
]);
$failed && $this->refundBalance($order);
return;
}
// 2. 先落编号(状态不动,仍为 0)
// 2. 先落编号 + 本单实际使用的支付通道(状态不动,仍为 0)
// 通道随单落库:切换通道后老单仍能追溯当时走的是哪条通道
Db::name('partners_order')->where('order_id', $orderId)->update([
'batch_no' => $batchNo,
'waybill_no' => $waybillNo,
'waybill_no_code' => $waybillNoCode,
'oil_text' => '发放中',
'passageway' => $qyt->channelKey(),
'oil_text' => '发放中(' . $qyt->channelName() . ')',
'update_time' => time(),
]);
@@ -111,6 +147,7 @@ class PartnersQueue extends Command
'waybill_no_code' => $waybillNoCode,
'mobile' => $order['mobile'],
'name' => $name,
'id_card' => $idCard,
'amount' => $order['total_price'],
]);
@@ -137,24 +174,20 @@ class PartnersQueue extends Command
return;
}
// 4. 受理成功 = 发放成功(支付宝企业码通道);条件跃迁 0 -> 2,胜出者回调下单平台
// 4. 受理成功【不等于】发放成功:7.7 是异步接口,真实结果要用 7.8 按 batch_no 查。
// 这里只把状态推进到「发放中」,由 qyt:status 轮询 7.8 后才决定成功/失败并回调下游。
$affected = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
'oil_status' => 1,
'oil_status_zsy' => 2,
'oil_time' => time(),
'oil_status_zsy' => 1,
'shareNo' => $waybillNoCode,
'oil_text' => $res['data']['msg'] ?? '发放成功',
'oil_text' => '已受理,等待企油通发放结果'
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已受理处理)'),
'qyt_query_time' => 0,
'qyt_query_num' => 0,
'update_time' => time(),
]);
if ($affected) {
$this->queueService->push('notify_queue', json_encode([
'order_id' => $orderId,
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$qyt->saveLog("7.7 发放成功 order={$orderId} batch_no={$batchNo}"
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已发放处理)'));
$qyt->saveLog("7.7 已受理 order={$orderId} batch_no={$batchNo} 通道={$qyt->channelName()}"
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已受理处理)'));
}
} catch (\Throwable $e) {
// 不改状态(仍为 0),交由 reconcile 兜底重投