```
feat(commission): 完善分销订单支付时数据同步机制 - 新增 syncExistingCommissionOrderOnPay 方法处理已存在分销单的支付同步场景 - 添加 encodeCommissionRulesSnapshot 和 commissionRulesSnapshotEquals 辅助方法 - 优化佣金规则快照存储方式,统一JSON编码格式 - 增加已入账奖励数量检查,防止对已结算订单进行错误同步 fix(wallet-settlement): 调整分账和佣金结算的余额处理逻辑 - 新增 shouldSettleCommissionViaWalletHook 和 shouldSettleRegionalViaWalletHook 方法控制分账成功后的钱包入账行为 - 修改 shouldDeferRegionalWallet 调用为 shouldSettleRegionalViaWalletHook - 修复分账成功时仍可能重复入钱包的问题 - 统一佣金和区域代理的钩子结算判断逻辑 refactor(hook): 优化佣金钩子触发条件和订单查询逻辑 - 在 CommissionHook 中使用新的钱包结算判断方法 - 修正 orderFinish 事件中的订单项遍历逻辑 - 增加订单有效性检查,避免空值异常 - 统一各事件中的结算条件判断 test(sql): 更新分账佣金验证SQL脚本 - 修正注释说明,明确分账成功不入余额的控制逻辑 - 补充区域代理佣金缺失汇付接收方的查询 - 重构单笔订单深度核验脚本,提供更详细的订单分析信息 - 增加分账成功与钱包流水的对比检查逻辑 ```
这个提交包含在:
@@ -14,6 +14,8 @@ use addons\shopro\model\commission\Order as OrderModel;
|
||||
|
||||
use addons\shopro\model\commission\Reward as RewardModel;
|
||||
|
||||
use addons\shopro\library\commission\Reward as RewardLibrary;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -193,7 +195,7 @@ class Commission
|
||||
|
||||
if ($commissionOrder) {
|
||||
|
||||
return $commissionOrder;
|
||||
return $this->syncExistingCommissionOrderOnPay($commissionOrder);
|
||||
|
||||
}
|
||||
|
||||
@@ -225,17 +227,7 @@ class Commission
|
||||
|
||||
'commission_price_type' => $this->_config->getCommissionPriceType(),
|
||||
|
||||
'commission_rules' => json_encode([
|
||||
|
||||
'rules' => $this->goods->getCommissionRules(),
|
||||
|
||||
'differential_enable' => $this->goods->isDifferentialEnable() ? 1 : 0,
|
||||
|
||||
'management_enable' => $this->goods->isManagementEnable() ? 1 : 0,
|
||||
|
||||
'repurchase_enable' => $this->goods->isRepurchaseEnable() ? 1 : 0,
|
||||
|
||||
]),
|
||||
'commission_rules' => $this->encodeCommissionRulesSnapshot(),
|
||||
|
||||
'commission_event' => $this->_config->getCommissionEvent(),
|
||||
|
||||
@@ -279,6 +271,128 @@ class Commission
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 支付时若已存在分销单(测试/脏数据/提前写入),与当前订单项不一致则同步并重算待发佣金
|
||||
*/
|
||||
protected function syncExistingCommissionOrderOnPay($commissionOrder)
|
||||
{
|
||||
$agentId = $this->selfBuy
|
||||
? $this->buyer->user->id
|
||||
: ($this->buyer->getParentAgentId() ?: $this->buyer->user->id);
|
||||
|
||||
$newAmount = round(floatval($this->amount), 2);
|
||||
$oldAmount = round(floatval($commissionOrder->amount), 2);
|
||||
$newPriceType = $this->_config->getCommissionPriceType();
|
||||
$newRulesJson = $this->encodeCommissionRulesSnapshot();
|
||||
|
||||
$needsSync = (
|
||||
abs($newAmount - $oldAmount) >= 0.01
|
||||
|| intval($commissionOrder->order_id) !== intval($this->item['order_id'])
|
||||
|| intval($commissionOrder->agent_id) !== intval($agentId)
|
||||
|| (string) $commissionOrder->commission_price_type !== (string) $newPriceType
|
||||
|| !$this->commissionRulesSnapshotEquals($commissionOrder->commission_rules, $newRulesJson)
|
||||
);
|
||||
|
||||
if (!$needsSync) {
|
||||
return $commissionOrder;
|
||||
}
|
||||
|
||||
$accountedCount = RewardModel::where('commission_order_id', $commissionOrder->id)
|
||||
->where('status', RewardLibrary::COMMISSION_REWARD_STATUS_ACCOUNTED)
|
||||
->count();
|
||||
if ($accountedCount > 0) {
|
||||
\think\Log::warning(sprintf(
|
||||
'[commission] skip sync commission_order id=%d order_item_id=%d: %d accounted reward(s), old_amount=%s new_amount=%s',
|
||||
$commissionOrder->id,
|
||||
$this->item['id'],
|
||||
$accountedCount,
|
||||
$oldAmount,
|
||||
$newAmount
|
||||
));
|
||||
return $commissionOrder;
|
||||
}
|
||||
|
||||
RewardModel::where('commission_order_id', $commissionOrder->id)
|
||||
->where('status', RewardLibrary::COMMISSION_REWARD_STATUS_WAITING)
|
||||
->delete();
|
||||
|
||||
if (
|
||||
intval($commissionOrder->commission_order_status) === OrderModel::COMMISSION_ORDER_STATUS_YES
|
||||
&& abs($newAmount - $oldAmount) >= 0.01
|
||||
) {
|
||||
$orderAgent = new Agent($commissionOrder->agent_id);
|
||||
if ($orderAgent->isAgentAvaliable()) {
|
||||
$delta = bcsub((string) $newAmount, (string) $oldAmount, 2);
|
||||
if (bccomp($delta, '0', 2) > 0) {
|
||||
$orderAgent->agent->setInc('order_money', $delta);
|
||||
} elseif (bccomp($delta, '0', 2) < 0) {
|
||||
$dec = ltrim($delta, '-');
|
||||
if (bccomp((string) $orderAgent->agent->order_money, $dec, 2) < 0) {
|
||||
$orderAgent->agent->order_money = 0;
|
||||
} else {
|
||||
$orderAgent->agent->setDec('order_money', $dec);
|
||||
}
|
||||
}
|
||||
$orderAgent->agent->save();
|
||||
}
|
||||
}
|
||||
|
||||
$commissionOrder->self_buy = intval($this->selfBuy);
|
||||
$commissionOrder->order_id = intval($this->item['order_id']);
|
||||
$commissionOrder->buyer_id = $this->buyer->user->id;
|
||||
$commissionOrder->goods_id = $this->item['goods_id'];
|
||||
$commissionOrder->agent_id = intval($agentId);
|
||||
$commissionOrder->amount = $newAmount;
|
||||
$commissionOrder->commission_price_type = $newPriceType;
|
||||
$commissionOrder->commission_rules = $newRulesJson;
|
||||
$commissionOrder->commission_event = $this->_config->getCommissionEvent();
|
||||
if (intval($commissionOrder->commission_reward_status) !== RewardLibrary::COMMISSION_REWARD_STATUS_WAITING) {
|
||||
$commissionOrder->commission_reward_status = RewardLibrary::COMMISSION_REWARD_STATUS_WAITING;
|
||||
$commissionOrder->commission_time = null;
|
||||
}
|
||||
$commissionOrder->save();
|
||||
|
||||
\think\Log::info(sprintf(
|
||||
'[commission] synced commission_order id=%d order_item_id=%d amount %s -> %s',
|
||||
$commissionOrder->id,
|
||||
$this->item['id'],
|
||||
$oldAmount,
|
||||
$newAmount
|
||||
));
|
||||
|
||||
return $commissionOrder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function encodeCommissionRulesSnapshot()
|
||||
{
|
||||
return json_encode([
|
||||
'rules' => $this->goods->getCommissionRules(),
|
||||
'differential_enable' => $this->goods->isDifferentialEnable() ? 1 : 0,
|
||||
'management_enable' => $this->goods->isManagementEnable() ? 1 : 0,
|
||||
'repurchase_enable' => $this->goods->isRepurchaseEnable() ? 1 : 0,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function commissionRulesSnapshotEquals($stored, $newJson)
|
||||
{
|
||||
$left = json_decode($stored ?: '{}', true);
|
||||
$right = json_decode($newJson ?: '{}', true);
|
||||
if (!is_array($left)) {
|
||||
$left = [];
|
||||
}
|
||||
if (!is_array($right)) {
|
||||
$right = [];
|
||||
}
|
||||
|
||||
return json_encode($left, JSON_UNESCAPED_UNICODE) === json_encode($right, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* 执行分销计划:关系链、级差、管理奖可同时发放
|
||||
|
||||
@@ -292,7 +292,7 @@ class Commission
|
||||
$agent->setInc('order_count', 1);
|
||||
|
||||
if ($this->_config->getCommissionEvent() === 'payed') {
|
||||
if (!SplitWalletSettlement::shouldDeferRegionalWallet($order, 'payed')) {
|
||||
if (SplitWalletSettlement::shouldSettleRegionalViaWalletHook($order, 'payed')) {
|
||||
(new Reward())->settleByRegionalOrder($regionalOrder->id, 'payed');
|
||||
}
|
||||
}
|
||||
@@ -311,7 +311,11 @@ class Commission
|
||||
|
||||
$rewardLib = new Reward();
|
||||
foreach ($regionalOrders as $regionalOrder) {
|
||||
if ($regionalOrder['commission_event'] === $event) {
|
||||
if ($regionalOrder['commission_event'] !== $event) {
|
||||
continue;
|
||||
}
|
||||
$order = \addons\shopro\model\Order::get($regionalOrder->order_id);
|
||||
if ($order && SplitWalletSettlement::shouldSettleRegionalViaWalletHook($order, $event)) {
|
||||
$rewardLib->settleByRegionalOrder($regionalOrder->id, $event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,38 @@ class SplitWalletSettlement
|
||||
return self::shouldDeferWalletUntilGatewaySplitResolves($order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销钩子是否应把待发佣金写入余额(false = 等分账 finalize 或已走通道,避免与汇付/微信分账重复)
|
||||
*/
|
||||
public static function shouldSettleCommissionViaWalletHook($order, $event)
|
||||
{
|
||||
$commissionEvent = (new CommissionConfig())->getCommissionEvent();
|
||||
if ($commissionEvent !== $event && $event !== 'admin') {
|
||||
return false;
|
||||
}
|
||||
if (self::shouldDeferWalletUntilGatewaySplitResolves($order)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域代理钩子是否应把待发佣金写入余额
|
||||
*/
|
||||
public static function shouldSettleRegionalViaWalletHook($order, $event)
|
||||
{
|
||||
$regionalEvent = (new RegionalConfig())->getCommissionEvent();
|
||||
if ($regionalEvent !== $event && $event !== 'admin') {
|
||||
return false;
|
||||
}
|
||||
if (self::shouldDeferWalletUntilGatewaySplitResolves($order)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销/区域结算事件(如 payed)早于 split_timing(如 confirm)时,仍应等分账结束再入钱包
|
||||
*/
|
||||
@@ -54,8 +86,9 @@ class SplitWalletSettlement
|
||||
if (!$config->isEnable() || !$config->shouldSkipWallet()) {
|
||||
return false;
|
||||
}
|
||||
// 分账已成功:佣金应由 finalizeAfterSplitAttempt 处理(或已标记通道到账),钩子不得再入余额
|
||||
if (self::isSplitSuccessful($order['id'])) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (class_exists('\\addons\\shopro_huifu\\library\\Bridge')
|
||||
|
||||
在新工单中引用
屏蔽一个用户