文件
可乐加冰 b5142ff780 ```
feat(commission): 完善分销订单支付时数据同步机制

- 新增 syncExistingCommissionOrderOnPay 方法处理已存在分销单的支付同步场景
- 添加 encodeCommissionRulesSnapshot 和 commissionRulesSnapshotEquals 辅助方法
- 优化佣金规则快照存储方式,统一JSON编码格式
- 增加已入账奖励数量检查,防止对已结算订单进行错误同步

fix(wallet-settlement): 调整分账和佣金结算的余额处理逻辑

- 新增 shouldSettleCommissionViaWalletHook 和 shouldSettleRegionalViaWalletHook
  方法控制分账成功后的钱包入账行为
- 修改 shouldDeferRegionalWallet 调用为 shouldSettleRegionalViaWalletHook
- 修复分账成功时仍可能重复入钱包的问题
- 统一佣金和区域代理的钩子结算判断逻辑

refactor(hook): 优化佣金钩子触发条件和订单查询逻辑

- 在 CommissionHook 中使用新的钱包结算判断方法
- 修正 orderFinish 事件中的订单项遍历逻辑
- 增加订单有效性检查,避免空值异常
- 统一各事件中的结算条件判断

test(sql): 更新分账佣金验证SQL脚本

- 修正注释说明,明确分账成功不入余额的控制逻辑
- 补充区域代理佣金缺失汇付接收方的查询
- 重构单笔订单深度核验脚本,提供更详细的订单分析信息
- 增加分账成功与钱包流水的对比检查逻辑
```
2026-10-22 11:44:24 +08:00

336 行
10 KiB
PHP

<?php
namespace addons\shopro\library\regional_agent;
use addons\shopro\model\Order as OrderModel;
use addons\shopro\model\User as UserModel;
use addons\shopro\model\regional_agent\Agent as AgentModel;
use addons\shopro\model\regional_agent\Level as LevelModel;
use addons\shopro\model\regional_agent\Order as RegionalOrderModel;
use addons\shopro\model\regional_agent\Reward as RewardModel;
use addons\shopro\library\split\SplitWalletSettlement;
/**
* 区域代理分佣
*/
class Commission
{
protected $_config;
public function __construct()
{
$this->_config = new Config();
}
/**
* 订单支付后处理区域代理分佣
*/
public function handleOrderPayed($order)
{
if (!$this->_config->isEnable()) {
return false;
}
if ($order['type'] === 'score' || empty($order['province_id'])) {
return false;
}
$order = OrderModel::with('item')->where('id', $order['id'])->find();
if (!$order || !$order['item']) {
return false;
}
$agents = $this->matchRegionalAgents($order);
if (empty($agents)) {
return false;
}
foreach ($order['item'] as $item) {
if (!$this->isGoodsRegionalEnabled($item['goods_id'])) {
continue;
}
$this->createRegionalCommissionsForItem($order, $item, $agents);
}
return true;
}
/**
* 按收货地址匹配省/市/区县代理
*/
public function matchRegionalAgents($order)
{
$matched = [];
$areaFields = [
1 => 'province_id',
2 => 'city_id',
3 => 'area_id',
];
$levels = LevelModel::order('area_level asc')->select();
if (!$levels || count($levels) === 0) {
$levels = [
['area_level' => 1],
['area_level' => 2],
['area_level' => 3],
];
}
foreach ($levels as $levelRow) {
$areaLevel = intval($levelRow['area_level']);
$field = $areaFields[$areaLevel] ?? null;
if (!$field) {
continue;
}
$areaId = $order[$field] ?? null;
if (empty($areaId)) {
continue;
}
$agent = AgentModel::findByArea($areaId, $areaLevel);
if ($agent) {
// 校验代理绑定的用户是否存在,防止脏数据导致分佣结算崩溃
$agentUser = UserModel::get($agent['user_id']);
if (!$agentUser) {
\think\Log::error("[regional_agent] agent id={$agent['id']} user_id={$agent['user_id']} not found in fa_user, skipped");
continue;
}
$matched[] = [
'agent' => $agent,
'area_id' => $areaId,
'area_level' => $areaLevel,
];
}
}
return $matched;
}
protected function isGoodsRegionalEnabled($goodsId)
{
$row = \think\Db::name('shopro_commission_goods')->where('goods_id', $goodsId)->find();
if (!$row) {
return true;
}
return boolval($row['regional_enable'] ?? 1);
}
protected function createRegionalCommissionsForItem($order, $item, $agents)
{
$amount = floatval($item['pay_price']);
if ($amount <= 0) {
return false;
}
$mode = $this->_config->getCommissionMode();
if ($mode === 'exclusive') {
$agents = $this->filterExclusiveAgents($agents);
}
if (empty($agents)) {
return false;
}
$commissionMap = $this->calculateCommissionMap($agents, $amount, $mode);
foreach ($agents as $agentData) {
$agent = $agentData['agent'];
$commission = $commissionMap[$agent['id']] ?? 0;
if ($commission <= 0) {
continue;
}
$this->persistRegionalCommission($order, $item, $agentData, $amount, $commission);
}
return true;
}
protected function filterExclusiveAgents($agents)
{
if (empty($agents)) {
return [];
}
usort($agents, function ($a, $b) {
return $b['area_level'] - $a['area_level'];
});
return [$agents[0]];
}
protected function calculateCommissionMap($agents, $amount, $mode)
{
$map = [];
$levelRows = [];
foreach ($agents as $agentData) {
$level = LevelModel::get($agentData['agent']['level']);
if (!$level) {
continue;
}
$effective = $this->resolveEffectiveCommission($agentData['agent'], $level);
$levelRows[] = [
'agent_id' => $agentData['agent']['id'],
'area_level' => $agentData['area_level'],
'commission' => $effective,
];
}
if (empty($levelRows)) {
return $map;
}
if ($mode === 'differential') {
return $this->calculateDifferentialMap($levelRows, $amount);
}
foreach ($levelRows as $row) {
$map[$row['agent_id']] = $this->calculateFullCommission($row['commission'], $amount);
}
return $map;
}
protected function calculateDifferentialMap($levelRows, $amount)
{
$types = [];
foreach ($levelRows as $row) {
$types[$row['commission']['commission_type']] = true;
}
if (count($types) !== 1) {
$map = [];
foreach ($levelRows as $row) {
$map[$row['agent_id']] = $this->calculateFullCommission($row['commission'], $amount);
}
return $map;
}
usort($levelRows, function ($a, $b) {
return $a['area_level'] - $b['area_level'];
});
$map = [];
$prevValue = 0;
$type = key($types);
foreach ($levelRows as $row) {
$commission = $row['commission'];
if ($type === 'rate') {
$current = floatval($commission['commission_rate']);
$effective = max(0, $current - $prevValue);
$map[$row['agent_id']] = round($amount * $effective * 0.01, 2);
$prevValue = $current;
} else {
$current = floatval($commission['commission_money']);
$effective = max(0, $current - $prevValue);
$map[$row['agent_id']] = round($effective, 2);
$prevValue = $current;
}
}
return $map;
}
/**
* 单个代理有自定义佣金时优先,否则跟随等级通用配置
*/
protected function resolveEffectiveCommission($agent, $level)
{
$type = $agent['commission_type'] ?? null;
if (!empty($type)) {
return [
'commission_type' => $type,
'commission_rate' => floatval($agent['commission_rate'] ?? 0),
'commission_money' => floatval($agent['commission_money'] ?? 0),
];
}
return [
'commission_type' => $level['commission_type'],
'commission_rate' => floatval($level['commission_rate']),
'commission_money' => floatval($level['commission_money']),
];
}
protected function calculateFullCommission($commission, $amount)
{
if ($commission['commission_type'] === 'rate') {
return round($amount * floatval($commission['commission_rate']) * 0.01, 2);
}
return round(floatval($commission['commission_money']), 2);
}
protected function persistRegionalCommission($order, $item, $agentData, $amount, $commission)
{
$agent = $agentData['agent'];
$exists = RegionalOrderModel::where('order_item_id', $item['id'])
->where('agent_id', $agent['id'])
->find();
if ($exists) {
return $exists;
}
$regionalOrder = RegionalOrderModel::create([
'order_id' => $order['id'],
'order_item_id' => $item['id'],
'buyer_id' => $order['user_id'],
'agent_id' => $agent['id'],
'agent_user_id' => $agent['user_id'],
'area_id' => $agentData['area_id'],
'area_level' => $agentData['area_level'],
'amount' => $amount,
'commission_event' => $this->_config->getCommissionEvent(),
'commission_reward_status' => 0,
]);
RewardModel::create([
'order_id' => $order['id'],
'order_item_id' => $item['id'],
'buyer_id' => $order['user_id'],
'regional_order_id' => $regionalOrder->id,
'agent_id' => $agent['id'],
'agent_user_id' => $agent['user_id'],
'type' => 'money',
'commission' => $commission,
'original_commission' => $commission,
'status' => 0,
]);
$agent->setInc('order_money', $amount);
$agent->setInc('order_count', 1);
if ($this->_config->getCommissionEvent() === 'payed') {
if (SplitWalletSettlement::shouldSettleRegionalViaWalletHook($order, 'payed')) {
(new Reward())->settleByRegionalOrder($regionalOrder->id, 'payed');
}
}
return $regionalOrder;
}
/**
* 订单确认收货/完成时结算
*/
public function settleByEvent($event, $orderItemId)
{
$regionalOrders = RegionalOrderModel::where('order_item_id', $orderItemId)
->where('commission_reward_status', 0)
->select();
$rewardLib = new Reward();
foreach ($regionalOrders as $regionalOrder) {
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);
}
}
}
/**
* 退款退回区域代理佣金
*/
public function refundByOrderItem($orderItemId)
{
$regionalOrders = RegionalOrderModel::where('order_item_id', $orderItemId)->select();
$rewardLib = new Reward();
foreach ($regionalOrders as $regionalOrder) {
$rewardLib->backByRegionalOrder($regionalOrder->id, 'refund');
}
}
}