getCommissionEvent(); if ($commissionEvent !== $event && $event !== 'admin') { return false; } return self::shouldDeferWalletUntilGatewaySplitResolves($order); } public static function shouldDeferRegionalWallet($order, $event) { $regionalEvent = (new RegionalConfig())->getCommissionEvent(); if ($regionalEvent !== $event && $event !== 'admin') { return false; } 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)时,仍应等分账结束再入钱包 */ protected static function shouldDeferWalletUntilGatewaySplitResolves($order) { if (is_numeric($order)) { $order = Order::get($order); } if (!$order) { return false; } $config = new Config(); if (!$config->isEnable() || !$config->shouldSkipWallet()) { return false; } // 分账已成功:佣金应由 finalizeAfterSplitAttempt 处理(或已标记通道到账),钩子不得再入余额 if (self::isSplitSuccessful($order['id'])) { return true; } if (class_exists('\\addons\\shopro_huifu\\library\\Bridge') && \addons\shopro_huifu\library\Bridge::shouldHandleSplit($order)) { return true; } if (in_array($config->getMode(), ['wechat', 'both'], true) && $order['pay_type'] === 'wechat') { return true; } return false; } /** * 当前订单是否应等分账结果再决定钱包入账 */ public static function shouldDeferForGatewaySplit($order, $event) { if (is_numeric($order)) { $order = Order::get($order); } if (!$order) { return false; } $config = new Config(); if (!$config->isEnable() || !$config->shouldSkipWallet()) { return false; } if ($config->getTiming() !== $event) { return false; } if (class_exists('\\addons\\shopro_huifu\\library\\Bridge') && \addons\shopro_huifu\library\Bridge::shouldHandleSplit($order)) { return true; } if (in_array($config->getMode(), ['wechat', 'both'], true) && $order['pay_type'] === 'wechat') { return true; } return false; } public static function isSplitSuccessful($orderId) { return SplitOrder::where('order_id', $orderId) ->where('status', 'in', [2, 3]) ->count() > 0; } /** * 分账尝试结束后结算佣金(汇付成功=按人混合;其它成功=整单不入钱包;失败=整单入钱包) */ public static function finalizeAfterSplitAttempt($orderId, $event, $splitExecutedOk = false) { if (!self::shouldDeferForGatewaySplit($orderId, $event)) { return; } if (self::isSplitSuccessful($orderId)) { if (self::shouldSettleHuifuPerReceiver($orderId)) { self::settleOrderHybridHuifuGatewayAndWallet($orderId, $event); } else { self::settleOrderWithoutWallet($orderId, $event); } } else { self::settleOrderToWallet($orderId, $event); } } /** * 汇付延时分账:仅出现在分账明细中的推广员/区域公司视为通道已分,其余佣金进钱包 */ public static function settleOrderHybridHuifuGatewayAndWallet($orderId, $event) { $gateway = self::collectGatewaySplitUserIds($orderId); $commissionLib = new CommissionRewardLibrary(); $commissionOrders = CommissionOrderModel::where('order_id', $orderId) ->where('commission_reward_status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) ->select(); foreach ($commissionOrders as $commissionOrder) { $settleEvent = (string) ($commissionOrder['commission_event'] ?? $event); $rewards = CommissionRewardModel::where('commission_order_id', $commissionOrder['id']) ->where('status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) ->select(); foreach ($rewards as $reward) { $userId = (int) $reward['agent_id']; if ($userId > 0 && in_array($userId, $gateway['promoter'], true)) { $commissionLib->runCommissionRewardWithoutWallet($settleEvent, $reward); } else { $commissionLib->runCommissionReward($settleEvent, $reward); } } self::markCommissionOrderSettledIfDone($commissionOrder); } $regionalLib = new RegionalRewardLibrary(); $regionalOrders = RegionalOrderModel::where('order_id', $orderId) ->where('commission_reward_status', RegionalRewardLibrary::STATUS_WAITING) ->select(); foreach ($regionalOrders as $regionalOrder) { $settleEvent = (string) ($regionalOrder['commission_event'] ?? $event); $rewards = RegionalRewardModel::where('regional_order_id', $regionalOrder['id']) ->where('status', RegionalRewardLibrary::STATUS_WAITING) ->select(); foreach ($rewards as $reward) { $userId = (int) $reward['agent_user_id']; if ($userId > 0 && in_array($userId, $gateway['regional_agent'], true)) { $regionalLib->settleRewardWithoutWallet($reward, $settleEvent); } else { $regionalLib->settleReward($reward, $settleEvent); } } self::markRegionalOrderSettledIfDone($regionalOrder); } } protected static function shouldSettleHuifuPerReceiver($orderId) { $order = Order::get($orderId); if (!$order) { return false; } return class_exists('\\addons\\shopro_huifu\\library\\Bridge') && \addons\shopro_huifu\library\Bridge::shouldHandleSplit($order); } /** * @return array{promoter:int[],regional_agent:int[]} */ protected static function collectGatewaySplitUserIds($orderId) { $promoter = []; $regional = []; $splitOrder = SplitOrder::where('order_id', $orderId) ->where('status', 'in', [2, 3]) ->order('id', 'desc') ->find(); if (!$splitOrder) { return ['promoter' => [], 'regional_agent' => []]; } $rows = SplitOrderReceiver::where('split_order_id', $splitOrder['id'])->select(); foreach ($rows as $row) { $roleType = (string) ($row['role_type'] ?? ''); if (!in_array($roleType, ['promoter', 'regional_agent'], true)) { continue; } $receiverId = (int) ($row['receiver_id'] ?? 0); if ($receiverId <= 0) { continue; } $receiver = Receiver::get($receiverId); if (!$receiver) { continue; } $roleUserId = (int) ($receiver['role_id'] ?? 0); if ($roleUserId <= 0) { continue; } if ($roleType === 'promoter') { $promoter[$roleUserId] = $roleUserId; } else { $regional[$roleUserId] = $roleUserId; } } return [ 'promoter' => array_values($promoter), 'regional_agent' => array_values($regional), ]; } protected static function markCommissionOrderSettledIfDone($commissionOrder) { $waiting = CommissionRewardModel::where('commission_order_id', $commissionOrder['id']) ->where('status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) ->count(); if ($waiting > 0) { return; } if ($commissionOrder['commission_reward_status'] !== CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) { return; } $commissionOrder->commission_reward_status = CommissionRewardLibrary::COMMISSION_REWARD_STATUS_ACCOUNTED; $commissionOrder->commission_time = time(); $commissionOrder->save(); } protected static function markRegionalOrderSettledIfDone($regionalOrder) { $waiting = RegionalRewardModel::where('regional_order_id', $regionalOrder['id']) ->where('status', RegionalRewardLibrary::STATUS_WAITING) ->count(); if ($waiting > 0) { return; } if ($regionalOrder['commission_reward_status'] !== RegionalRewardLibrary::STATUS_WAITING) { return; } $regionalOrder->commission_reward_status = RegionalRewardLibrary::STATUS_ACCOUNTED; $regionalOrder->commission_time = time(); $regionalOrder->save(); } public static function settleOrderWithoutWallet($orderId, $event) { $commissionLib = new CommissionRewardLibrary(); $commissionOrders = CommissionOrderModel::where('order_id', $orderId) ->where('commission_reward_status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) ->select(); foreach ($commissionOrders as $commissionOrder) { $settleEvent = (string) ($commissionOrder['commission_event'] ?? $event); $commissionLib->runCommissionRewardByOrderWithoutWallet($settleEvent, $commissionOrder); } $regionalLib = new RegionalRewardLibrary(); $regionalOrders = RegionalOrderModel::where('order_id', $orderId) ->where('commission_reward_status', RegionalRewardLibrary::STATUS_WAITING) ->select(); foreach ($regionalOrders as $regionalOrder) { $settleEvent = (string) ($regionalOrder['commission_event'] ?? $event); $regionalLib->settleByRegionalOrderWithoutWallet($regionalOrder->id, $settleEvent); } } public static function settleOrderToWallet($orderId, $event) { $commissionLib = new CommissionRewardLibrary(); $commissionOrders = CommissionOrderModel::where('order_id', $orderId) ->where('commission_reward_status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_WAITING) ->select(); foreach ($commissionOrders as $commissionOrder) { $settleEvent = (string) ($commissionOrder['commission_event'] ?? $event); $commissionLib->runCommissionRewardByOrder($settleEvent, $commissionOrder); } $regionalLib = new RegionalRewardLibrary(); $regionalOrders = RegionalOrderModel::where('order_id', $orderId) ->where('commission_reward_status', RegionalRewardLibrary::STATUS_WAITING) ->select(); foreach ($regionalOrders as $regionalOrder) { $settleEvent = (string) ($regionalOrder['commission_event'] ?? $event); $regionalLib->settleByRegionalOrder($regionalOrder->id, $settleEvent); } } /** * 汇付 confirm 重试前:佣金已入平台钱包则禁止再次分账(防双发) */ public static function getHuifuRetryBlockReason($orderId) { $orderId = intval($orderId); if ($orderId <= 0) { return null; } if (class_exists('\\addons\\shopro\\library\\split\\SplitCloseService') && (new SplitCloseService())->isClosed($orderId)) { return '该订单已标记线下结案,不可重试汇付分账'; } $wallet = self::sumOrderWalletCommission($orderId); if (bccomp($wallet, '0.00', 2) <= 0) { return null; } $splitOk = self::isSplitSuccessful($orderId); if ($splitOk) { return '网关分账已成功且存在钱包佣金流水,禁止重试(疑似双轨),请标记结案或人工对账'; } return '佣金已入平台钱包兜底,禁止再次汇付 confirm(防双发);若需通道分账请先对账并标记结案'; } /** * @return array{settlement_source:string,settlement_source_text:string,wallet_sum:string,split_success:bool,retry_blocked:bool,retry_block_reason:string} */ public static function diagnoseOrderSettlement($orderId) { $orderId = intval($orderId); $walletSum = self::sumOrderWalletCommission($orderId); $splitOk = self::isSplitSuccessful($orderId); $retryReason = self::getHuifuRetryBlockReason($orderId); $source = 'pending'; if ($splitOk && bccomp($walletSum, '0.00', 2) > 0) { $source = 'hybrid'; } elseif ($splitOk) { $source = 'gateway'; } elseif (bccomp($walletSum, '0.00', 2) > 0) { $source = 'wallet'; } $textMap = [ 'pending' => '待结算/无钱包流水', 'gateway' => '汇付通道(未入平台钱包)', 'wallet' => '平台钱包兜底', 'hybrid' => '通道+钱包混合(请核对)', ]; return [ 'settlement_source' => $source, 'settlement_source_text' => $textMap[$source] ?? $source, 'wallet_sum' => $walletSum, 'split_success' => $splitOk, 'retry_blocked' => $retryReason !== null, 'retry_block_reason' => $retryReason ?: '', ]; } protected static function sumOrderWalletCommission($orderId) { $orderId = intval($orderId); if ($orderId <= 0) { return '0.00'; } $sum = '0.00'; $crIds = CommissionRewardModel::where('order_id', $orderId) ->where('status', CommissionRewardLibrary::COMMISSION_REWARD_STATUS_ACCOUNTED) ->column('id'); if ($crIds) { $part = Db::name('shopro_user_wallet_log') ->where('item_id', 'in', $crIds) ->where('wallet_type', 'money') ->where('type', 'commission_income') ->sum('wallet'); $sum = bcadd($sum, number_format((float) $part, 2, '.', ''), 2); } $regionalOrderIds = RegionalOrderModel::where('order_id', $orderId)->column('id'); if ($regionalOrderIds) { $rrIds = RegionalRewardModel::where('regional_order_id', 'in', $regionalOrderIds) ->where('status', RegionalRewardLibrary::STATUS_ACCOUNTED) ->column('id'); if ($rrIds) { $part = Db::name('shopro_user_wallet_log') ->where('item_id', 'in', $rrIds) ->where('wallet_type', 'money') ->where('type', 'regional_agent_income') ->sum('wallet'); $sum = bcadd($sum, number_format((float) $part, 2, '.', ''), 2); } } return $sum; } }