feat(shopro_huifu): 添加分账配置提交功能和接收方绑定机制 - 实现 shouldDeferWalletUntilGatewaySplitResolves 方法,确保分销/区域结算事件早于 split_timing 时等待分账结束再入钱包 - 新增 SyncSplitReceivers 命令用于提交商户分账配置并绑定已审核通过的汇付号为分账接收方 - 扩展 HuifuClient 添加 submitMerchantSplitConfig 接口实现 v2/merchant/split/config 调用 - 创建 HuifuMerchantSplitService 处理主商户分账配置提交和进件子户绑定为分账接收方逻辑 - 在进件成功后自动调用 split/config 接口绑定分账接收方 - 添加数据库表结构升级脚本,增加分账绑定状态相关字段 - 注册新的命令行工具到应用配置中 ```
319 行
12 KiB
PHP
319 行
12 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro\library\split;
|
||
|
||
use addons\shopro\library\commission\Config as CommissionConfig;
|
||
use addons\shopro\library\commission\Reward as CommissionRewardLibrary;
|
||
use addons\shopro\library\regional_agent\Config as RegionalConfig;
|
||
use addons\shopro\library\regional_agent\Reward as RegionalRewardLibrary;
|
||
use addons\shopro\model\Order;
|
||
use addons\shopro\model\commission\Order as CommissionOrderModel;
|
||
use addons\shopro\model\commission\Reward as CommissionRewardModel;
|
||
use addons\shopro\model\regional_agent\Order as RegionalOrderModel;
|
||
use addons\shopro\model\regional_agent\Reward as RegionalRewardModel;
|
||
use addons\shopro\model\split\Receiver;
|
||
use addons\shopro\model\split\SplitOrder;
|
||
use addons\shopro\model\split\SplitOrderReceiver;
|
||
|
||
/**
|
||
* 支付通道分账与钱包佣金:汇付可按人混合(已进件走通道、未进件进钱包);其它通道整单成功则不入余额
|
||
*/
|
||
class SplitWalletSettlement
|
||
{
|
||
public static function shouldDeferCommissionWallet($order, $event)
|
||
{
|
||
$commissionEvent = (new CommissionConfig())->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);
|
||
}
|
||
|
||
/**
|
||
* 分销/区域结算事件(如 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;
|
||
}
|
||
if (self::isSplitSuccessful($order['id'])) {
|
||
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 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) {
|
||
if ($commissionOrder['commission_event'] !== $event && $event !== 'admin') {
|
||
continue;
|
||
}
|
||
$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($event, $reward);
|
||
} else {
|
||
$commissionLib->runCommissionReward($event, $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) {
|
||
if ($regionalOrder['commission_event'] !== $event) {
|
||
continue;
|
||
}
|
||
$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, $event);
|
||
} else {
|
||
$regionalLib->settleReward($reward, $event);
|
||
}
|
||
}
|
||
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) {
|
||
if ($commissionOrder['commission_event'] === $event || $event === 'admin') {
|
||
$commissionLib->runCommissionRewardByOrderWithoutWallet($event, $commissionOrder);
|
||
}
|
||
}
|
||
|
||
$regionalLib = new RegionalRewardLibrary();
|
||
$regionalOrders = RegionalOrderModel::where('order_id', $orderId)
|
||
->where('commission_reward_status', RegionalRewardLibrary::STATUS_WAITING)
|
||
->select();
|
||
foreach ($regionalOrders as $regionalOrder) {
|
||
if ($regionalOrder['commission_event'] === $event) {
|
||
$regionalLib->settleByRegionalOrderWithoutWallet($regionalOrder->id, $event);
|
||
}
|
||
}
|
||
}
|
||
|
||
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) {
|
||
if ($commissionOrder['commission_event'] === $event || $event === 'admin') {
|
||
$commissionLib->runCommissionRewardByOrder($event, $commissionOrder);
|
||
}
|
||
}
|
||
|
||
$regionalLib = new RegionalRewardLibrary();
|
||
$regionalOrders = RegionalOrderModel::where('order_id', $orderId)
|
||
->where('commission_reward_status', RegionalRewardLibrary::STATUS_WAITING)
|
||
->select();
|
||
foreach ($regionalOrders as $regionalOrder) {
|
||
if ($regionalOrder['commission_event'] === $event) {
|
||
$regionalLib->settleByRegionalOrder($regionalOrder->id, $event);
|
||
}
|
||
}
|
||
}
|
||
}
|