1、合作商余额查询
2、分油账户司机可用额度
This commit is contained in:
@@ -8,6 +8,8 @@ use think\console\Input;
|
||||
use think\console\Output;
|
||||
use app\store\model\order\Record as RecordModel;
|
||||
use app\common\service\SybOil;
|
||||
use app\common\service\QytWaybill;
|
||||
use app\common\lock\RedisLock;
|
||||
use think\facade\Db;
|
||||
|
||||
class CallbackQueue extends Command
|
||||
@@ -427,6 +429,58 @@ class CallbackQueue extends Command
|
||||
$SybOil->saveLogStore('企油通消费回调处理成功:' . json_encode($c, 320));
|
||||
}
|
||||
break;
|
||||
case "qyt_waybill_result":
|
||||
// 企油通 3.10「发放结果通知」:一个批次一条,整批明细都在 res.list 里。
|
||||
// 判定与状态跃迁跟 7.8 轮询共用 QytWaybill,保证两条路结论一致、互相幂等。
|
||||
$batchNo = (string)($res['batch_no'] ?? '');
|
||||
$batchStatus = (int)($res['status'] ?? 0);
|
||||
$notifyTime = !empty($res['notify_time']) ? (strtotime((string)$res['notify_time']) ?: time()) : time();
|
||||
$list = (array)($res['list'] ?? []);
|
||||
$stat = ['success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0, 'conflict' => 0, 'miss' => 0];
|
||||
|
||||
foreach ($list as $row) {
|
||||
$code = trim((string)($row['waybill_no_code'] ?? ''));
|
||||
if ($code === '') {
|
||||
$stat['miss']++;
|
||||
continue;
|
||||
}
|
||||
$order = Db::name('partners_order')->where('waybill_no_code', $code)->find();
|
||||
if (!$order) {
|
||||
// 同一个 appId 下可能还挂着别的接入系统提交的批次,匹配不到不算异常
|
||||
$stat['miss']++;
|
||||
continue;
|
||||
}
|
||||
// 留痕:这一单到底收没收到过推送、收了几次
|
||||
QytWaybill::touchNotify((int)$order['order_id'], $notifyTime);
|
||||
|
||||
// 每单串行,避免与 qyt:status 轮询同时跃迁
|
||||
$lock = new RedisLock('qyt:status:' . $order['order_id'], 60);
|
||||
if (!$lock->acquireLock()) {
|
||||
// 轮询正在处理这一单,跳过即可:两条路的结论一样
|
||||
$stat['skipped']++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// 锁内重读,拿到最新状态
|
||||
$fresh = Db::name('partners_order')->where('order_id', $order['order_id'])->find();
|
||||
$judged = QytWaybill::judge($batchStatus, [
|
||||
'handle_status' => (int)($row['handle_status'] ?? 0),
|
||||
'error_msg' => (string)($row['error_msg'] ?? ''),
|
||||
'remark' => (string)($row['remark'] ?? ''),
|
||||
]);
|
||||
$stat[QytWaybill::apply($fresh ?: $order, $judged, '3.10 通知')]++;
|
||||
} finally {
|
||||
$lock->releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
$SybOil->saveLogStore('3.10 发放结果通知处理完成 batch_no=' . $batchNo
|
||||
. ' 批次状态=' . $batchStatus . ' 明细=' . count($list)
|
||||
. ' 成功=' . $stat['success'] . ' 失败=' . $stat['failed']
|
||||
. ' 待处理=' . $stat['pending'] . ' 已是终态=' . $stat['skipped']
|
||||
. ' 未匹配=' . $stat['miss']
|
||||
. ($stat['conflict'] ? ' 【冲突=' . $stat['conflict'] . ',需人工核对】' : ''));
|
||||
break;
|
||||
default:
|
||||
$SybOil->saveLogStore('队列数据处理异常,未知:【'.$message.'】');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace app\console\command;
|
||||
|
||||
use app\common\service\MallOil;
|
||||
use app\common\service\PartnersOil;
|
||||
use app\common\service\QueueService;
|
||||
use think\console\Command;
|
||||
@@ -82,6 +83,17 @@ class NotifyQueue extends Command
|
||||
if(!$order){
|
||||
return false;
|
||||
}
|
||||
// 小程序(商城)单没有下游下单平台可回调:把状态回写到 yoshop_order_goods,
|
||||
// 小程序端的订单列表/详情、后台订单页的基础列都读那张表。直接算成功,不重试。
|
||||
if (MallOil::isMall($order)) {
|
||||
MallOil::syncGoods($order, (string)($data['optType'] ?? ''));
|
||||
// 逐笔标记已处理,否则后台「重推加油结果」会一直认为这些流水没通知过
|
||||
if (!empty($data['consume_id'])) {
|
||||
Db::name('qyt_consume')->where('id', (int)$data['consume_id'])
|
||||
->update(['notify_status' => 1, 'notify_time' => time()]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
$partners = partnersModel::detail($order['partners_id']);
|
||||
if(!$partners){
|
||||
return false;
|
||||
|
||||
@@ -22,7 +22,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\MallOil;
|
||||
use app\common\service\QueueService;
|
||||
use app\common\service\QytOil;
|
||||
use think\console\Command;
|
||||
@@ -48,12 +48,13 @@ class PartnersQueue extends Command
|
||||
{
|
||||
$this->queueService = new QueueService();
|
||||
while (true) {
|
||||
$message = $this->queueService->pop('partners_queue');
|
||||
// 阻塞式取队列:有单立刻开始下发,不用等轮询间隔。
|
||||
// 5 秒超时只是为了让空闲时也能跑到下面的兜底巡检(内部已限频 60s)。
|
||||
$message = $this->queueService->bpop('partners_queue', 5);
|
||||
if ($message) {
|
||||
$this->processMessage($message);
|
||||
} else {
|
||||
$this->reconcile();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,18 +199,14 @@ class PartnersQueue extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* 发放失败 -> 回补合作商余额(仅在状态跃迁胜出时调用,天然幂等)
|
||||
* 发放失败 -> 原路退款(仅在状态跃迁胜出时调用,天然幂等)
|
||||
* · 合作商单 -> 回补 partners.balance
|
||||
* · 商城单 -> 退回下单用户余额(按实付折算),并把失败状态回写 order_goods
|
||||
*/
|
||||
protected function refundBalance($order)
|
||||
{
|
||||
PartnersBalance::refund(
|
||||
(int) $order['partners_id'],
|
||||
(float) $order['total_price'],
|
||||
(string) $order['partners_order_no'],
|
||||
(int) $order['order_id'],
|
||||
(int) $order['store_id']
|
||||
);
|
||||
(new QytOil())->saveLog("发放失败已回补合作商余额 order={$order['order_id']} amount={$order['total_price']}");
|
||||
MallOil::giveBack($order, (float) $order['total_price'], '发放失败');
|
||||
MallOil::syncGoods($order);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | 企油通「运单发放结果」轮询 (7.8 waybillBatch)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 为什么需要它:7.7 运单油费下发是【异步】接口,返回成功只代表受理,
|
||||
// | 真实发放结果必须按 batch_no 调 7.8 查(《企油通接口对接文档.md》§5 第 7 条)。
|
||||
// | 为什么需要它:7.7 运单油费下发是【异步】接口,返回成功只代表受理。
|
||||
// |
|
||||
// | 拿最终结果有两条路,判定逻辑共用 app\common\service\QytWaybill:
|
||||
// | · 推:3.10 发放结果通知 -> public/qytwaybill.php -> callback:queue(主路径)
|
||||
// | · 拉:本命令按 batch_no 调 7.8 查询(安全网)
|
||||
// |
|
||||
// | 接上推送后本命令不会下线:上游未配置接收地址不推送、推送重试 5 次后放弃、
|
||||
// | 我方接收端宕机或发版重启,这三种情况都只能靠轮询兜底。此时退避间隔会自动放长,
|
||||
// | 见 intervalSql()。
|
||||
// |
|
||||
// | 状态流转(oil_status_zsy):
|
||||
// | 0 未提交 --PartnersQueue 调7.7受理--> 1 发放中 --本命令查7.8--> 2 成功 / 3 失败
|
||||
@@ -27,9 +34,9 @@ namespace app\console\command;
|
||||
|
||||
use app\common\lock\RedisLock;
|
||||
use app\common\model\qyt\Enterprise as EnterpriseModel;
|
||||
use app\common\service\PartnersBalance;
|
||||
use app\common\service\QueueService;
|
||||
use app\common\service\QytConfig;
|
||||
use app\common\service\QytOil;
|
||||
use app\common\service\QytWaybill;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
@@ -40,15 +47,12 @@ class QytStatusQueue extends Command
|
||||
/** 每轮扫描的最大订单数 */
|
||||
const BATCH_LIMIT = 100;
|
||||
|
||||
/** 空转时的休眠秒数 */
|
||||
const IDLE_SLEEP = 5;
|
||||
/** 空转时的休眠秒数(只是一条轻量 SQL,不打上游,可以压到 1 秒) */
|
||||
const IDLE_SLEEP = 1;
|
||||
|
||||
/** 查询次数达到该值后仍未定论,视为异常滞留,写告警日志 */
|
||||
const STUCK_NUM = 100;
|
||||
|
||||
/** @var QueueService */
|
||||
protected $queueService;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('qyt:status')
|
||||
@@ -57,7 +61,7 @@ class QytStatusQueue extends Command
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$this->queueService = new QueueService();
|
||||
// 通知下游 / 同步小程序端都在 QytWaybill 里做,这里只负责查结果
|
||||
while (true) {
|
||||
$handled = 0;
|
||||
try {
|
||||
@@ -94,11 +98,38 @@ class QytStatusQueue extends Command
|
||||
|
||||
/**
|
||||
* 退避间隔(秒)的 SQL 表达式:查得越久间隔越长,避免高频打上游
|
||||
* 前 10 次 30s,10~30 次 120s,之后 600s
|
||||
*
|
||||
* 为什么前几次要这么密:7.7 受理后第一次 7.8 几乎必然返回「待处理」,
|
||||
* 如果第二次要等 30 秒,哪怕企油通第 8 秒就发完了,用户也得等到第 35 秒。
|
||||
* 绝大多数单在前 20 秒内就有结果,所以把这一段压到 3 秒一次,
|
||||
* 代价是每单多打上游几次;拖久的单自动退到长间隔,总 QPS 不会失控。
|
||||
*
|
||||
* 注意:若已接入 3.10 发放结果通知(后台「企油通对接设置」里打开开关),
|
||||
* 走的是上面那条「安全网」曲线,不是下面这条抢时间的曲线。
|
||||
*
|
||||
* 次数 <6 -> 3s (覆盖 0~18s,正常单在这里出结果)
|
||||
* 次数 <16 -> 10s (覆盖至约 2 分钟)
|
||||
* 次数 <26 -> 30s (覆盖至约 7 分钟)
|
||||
* 次数 <46 -> 120s (覆盖至约 47 分钟)
|
||||
* 之后 -> 600s
|
||||
*/
|
||||
protected function intervalSql(): string
|
||||
{
|
||||
return 'CASE WHEN qyt_query_num < 10 THEN 30 WHEN qyt_query_num < 30 THEN 120 ELSE 600 END';
|
||||
// 接上 3.10 发放结果通知后,结果以推送为主,轮询退为安全网:
|
||||
// 只覆盖「上游没配接收地址 / 推送重试 5 次仍失败 / 我方接收端当时不可用」
|
||||
// 这三种情况,没必要再抢在推送前面把上游打一遍。
|
||||
if (!empty(QytConfig::get()['waybill_notify'])) {
|
||||
return 'CASE'
|
||||
. ' WHEN qyt_query_num < 3 THEN 60'
|
||||
. ' WHEN qyt_query_num < 8 THEN 300'
|
||||
. ' ELSE 900 END';
|
||||
}
|
||||
return 'CASE'
|
||||
. ' WHEN qyt_query_num < 6 THEN 3'
|
||||
. ' WHEN qyt_query_num < 16 THEN 10'
|
||||
. ' WHEN qyt_query_num < 26 THEN 30'
|
||||
. ' WHEN qyt_query_num < 46 THEN 120'
|
||||
. ' ELSE 600 END';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,31 +174,16 @@ class QytStatusQueue extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
// 判定与状态跃迁跟 3.10 推送共用 QytWaybill,两条路结论必须一致
|
||||
$batchStatus = (int) ($res['data']['status'] ?? 0);
|
||||
$row = QytOil::pickWaybill($res['data'], (string) $order['waybill_no_code']);
|
||||
$row = QytWaybill::pick($res['data'], (string) $order['waybill_no_code']);
|
||||
$judged = QytWaybill::judge($batchStatus, $row);
|
||||
|
||||
// 整批处理失败,且明细里没有本条 -> 判失败
|
||||
if (!$row && $batchStatus === 5) {
|
||||
$this->markFailed($order, '企油通返回整批处理失败', $qyt);
|
||||
return;
|
||||
}
|
||||
// 明细里还没有本条:批次还在排队,继续等
|
||||
if (!$row) {
|
||||
if ($judged['result'] === QytWaybill::R_PENDING) {
|
||||
$this->alertIfStuck($order, $qyt);
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($row['handle_status']) {
|
||||
case 2: // 成功
|
||||
$this->markSuccess($order, $row['remark'] ?: '发放成功', $qyt);
|
||||
break;
|
||||
case 3: // 失败
|
||||
$this->markFailed($order, $row['error_msg'] ?: '企油通发放失败', $qyt);
|
||||
break;
|
||||
default: // 1 待处理,或未知值 -> 继续等
|
||||
$this->alertIfStuck($order, $qyt);
|
||||
break;
|
||||
}
|
||||
QytWaybill::apply($order, $judged, '7.8 轮询');
|
||||
} catch (\Throwable $e) {
|
||||
$this->log("order={$orderId} 轮询异常:" . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
} finally {
|
||||
@@ -175,60 +191,6 @@ class QytStatusQueue extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发放成功:条件跃迁 1 -> 2,胜出者才回调下单平台
|
||||
*/
|
||||
protected function markSuccess(array $order, string $text, QytOil $qyt): void
|
||||
{
|
||||
$affected = Db::name('partners_order')
|
||||
->where('order_id', $order['order_id'])
|
||||
->where('oil_status_zsy', 1)
|
||||
->update([
|
||||
'oil_status' => 1,
|
||||
'oil_status_zsy' => 2,
|
||||
'oil_time' => time(),
|
||||
'oil_text' => $text,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
if (!$affected) {
|
||||
return;
|
||||
}
|
||||
$this->queueService->push('notify_queue', json_encode([
|
||||
'order_id' => $order['order_id'],
|
||||
'type' => 'add',
|
||||
'optType' => 'share_oil',
|
||||
'retry_count' => 0,
|
||||
], 320));
|
||||
$qyt->saveLog("7.8 确认发放成功 order={$order['order_id']} batch_no={$order['batch_no']}"
|
||||
. " 查询次数={$order['qyt_query_num']}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发放失败:条件跃迁 1 -> 3,胜出者才退款(保证只退一次)
|
||||
*/
|
||||
protected function markFailed(array $order, string $reason, QytOil $qyt): void
|
||||
{
|
||||
$affected = Db::name('partners_order')
|
||||
->where('order_id', $order['order_id'])
|
||||
->where('oil_status_zsy', 1)
|
||||
->update([
|
||||
'oil_status_zsy' => 3,
|
||||
'oil_text' => '发放失败:' . $reason,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
if (!$affected) {
|
||||
return;
|
||||
}
|
||||
PartnersBalance::refund(
|
||||
(int) $order['partners_id'],
|
||||
(float) $order['total_price'],
|
||||
(string) $order['partners_order_no'],
|
||||
(int) $order['order_id'],
|
||||
(int) $order['store_id']
|
||||
);
|
||||
$qyt->saveLog("7.8 确认发放失败 order={$order['order_id']} 原因={$reason},已回补合作商余额 {$order['total_price']}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 长期拿不到结果时告警。只写日志,不改状态、不退款
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user