```
feat(shopro_huifu): 支持汇付分账配置rule_origin双模式 支持汇付分账配置的两种模式: - rule_origin=01:接口发起模式,不带接收方列表,接收方在confirm时指定 - rule_origin=02:控台配置模式,需带acct_split_bunch_list固定接收方 更新SyncSplitReceivers命令支持--rule-origin选项,调整分账配置提交逻辑, 新增配置项split_config_rule_origin默认为01模式,并完善相关文档说明 BREAKING CHANGE: 分账配置流程调整,需重新配置相关参数 ```
这个提交包含在:
@@ -115,8 +115,8 @@ class QuerySplitConfig extends Command
|
||||
}
|
||||
$output->writeln('');
|
||||
$output->writeln('下一步(汇付实施/工单):');
|
||||
$output->writeln(' 1) v2/merchant/split/config 提交:rule_origin=01, div_flag=Y, apply_ratio≥业务比例(如30)');
|
||||
$output->writeln(' 2) 控台/接口绑定分账接收方 6666000245630872(进件通过 ≠ 分账关系已配)');
|
||||
$output->writeln(' 1) php think shopro_huifu:sync-split-receivers(rule_origin=01 仅开通分账,不带接收方列表)');
|
||||
$output->writeln(' 2) 进件子户在 confirm 的 acct_split_bunch 中指定;若仍 22000002 再核控台分账关系/实施工单');
|
||||
$output->writeln(' 3) 生效后再跑本命令,应出现 rule_origin;新订单测分账,勿重试 unconfirm_amt=0 的旧单');
|
||||
$output->writeln('完整 JSON: php think shopro_huifu:query-split-config --json');
|
||||
return 2;
|
||||
|
||||
@@ -16,10 +16,11 @@ class SyncSplitReceivers extends Command
|
||||
{
|
||||
$this->setName('shopro_huifu:sync-split-receivers')
|
||||
->addOption('dry-run', null, Option::VALUE_NONE, '仅预览将提交的接收方与参数,不调汇付')
|
||||
->addOption('init-only', null, Option::VALUE_NONE, '仅提交主商户分账开关(rule_origin=01),不带接收方列表')
|
||||
->addOption('init-only', null, Option::VALUE_NONE, '同默认:仅提交 split/config,不带 acct_split_bunch_list')
|
||||
->addOption('rule-origin', null, Option::VALUE_OPTIONAL, '01=接口发起(默认) 02=控台配置且提交接收方列表')
|
||||
->addOption('huifu-id', null, Option::VALUE_OPTIONAL, '主商户汇付号,默认 platform_huifu_id 或插件 huifu_id')
|
||||
->addOption('scene', null, Option::VALUE_OPTIONAL, '分账模式 scene,默认插件 split_config_scene(A5)')
|
||||
->setDescription('Submit v2/merchant/split/config and bind approved onboard huifu_ids as split receivers');
|
||||
->setDescription('Submit v2/merchant/split/config (01=open split; 02=with receiver list)');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
@@ -40,24 +41,35 @@ class SyncSplitReceivers extends Command
|
||||
$scene = $sceneOpt !== '' ? $sceneOpt : $service->resolveSplitConfigScene();
|
||||
$output->writeln('scene(分账模式): ' . $scene);
|
||||
|
||||
$ruleOriginOpt = trim((string) $input->getOption('rule-origin'));
|
||||
$ruleOrigin = $ruleOriginOpt !== '' ? $ruleOriginOpt : $service->resolveRuleOrigin([]);
|
||||
$output->writeln('rule_origin: ' . $ruleOrigin . ($ruleOrigin === '01' ? '(接口发起,confirm 传 acct_split_bunch)' : '(控台配置,须带接收方列表)'));
|
||||
|
||||
$dryRun = (bool) $input->getOption('dry-run');
|
||||
$initOnly = (bool) $input->getOption('init-only');
|
||||
$options = [
|
||||
'dry_run' => $dryRun,
|
||||
'main_huifu_id' => $main,
|
||||
'scene' => $scene,
|
||||
'rule_origin' => $ruleOrigin,
|
||||
];
|
||||
|
||||
if ($initOnly) {
|
||||
$output->writeln('模式: init-only(不带 acct_split_bunch_list)');
|
||||
$result = $service->submitSplitConfig([], $options);
|
||||
if ($initOnly || $ruleOrigin === '01') {
|
||||
$output->writeln('模式: 提交分账配置(rule_origin=01 不传 acct_split_bunch_list)');
|
||||
if ($ruleOrigin === '01') {
|
||||
$result = $service->syncAllApprovedReceivers($options);
|
||||
} else {
|
||||
$result = $service->submitSplitConfig([], $options);
|
||||
}
|
||||
} else {
|
||||
$output->writeln('模式: 全量(远端已绑 + 本地已进件 approved)');
|
||||
$output->writeln('模式: rule_origin=02,合并远端+本地进件后提交接收方列表');
|
||||
$result = $service->syncAllApprovedReceivers($options);
|
||||
}
|
||||
|
||||
if (!empty($result['dry_run'])) {
|
||||
$output->writeln('[DRY-RUN] 将提交接收方 ' . count($result['receivers'] ?? []) . ' 个');
|
||||
$localCount = count($result['receivers'] ?? []);
|
||||
$output->writeln('[DRY-RUN] 本地/合并进件 huifu_id 共 ' . $localCount . ' 个'
|
||||
. ($ruleOrigin === '01' ? '(01 模式不会写入 split/config 请求体)' : ''));
|
||||
$output->writeln(json_encode($result['data'] ?? [], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
return 0;
|
||||
}
|
||||
@@ -66,9 +78,12 @@ class SyncSplitReceivers extends Command
|
||||
if (isset($result['resp_code'])) {
|
||||
$output->writeln('resp_code: ' . $result['resp_code']);
|
||||
}
|
||||
if (isset($result['submitted'])) {
|
||||
$output->writeln('提交接收方数量: ' . $result['submitted']
|
||||
if (isset($result['submitted']) && ($result['rule_origin'] ?? $ruleOrigin) === '02') {
|
||||
$output->writeln('split/config 提交接收方: ' . $result['submitted']
|
||||
. '(远端已有 ' . ($result['remote'] ?? 0) . ',本地进件 ' . ($result['local'] ?? 0) . ')');
|
||||
} elseif (!empty($result['receivers'])) {
|
||||
$output->writeln('本地已进件子户(confirm 时作为 acct_split_bunch 接收方,非 split/config 明细): '
|
||||
. count($result['receivers']));
|
||||
}
|
||||
if (!empty($result['receivers'])) {
|
||||
foreach ($result['receivers'] as $hid) {
|
||||
|
||||
@@ -10,7 +10,10 @@ use think\Db;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* 主商户分账配置提交 + 进件子户绑定为分账接收方(v2/merchant/split/config)
|
||||
* 主商户分账配置提交(v2/merchant/split/config)
|
||||
*
|
||||
* rule_origin=01(接口发起):不得传 acct_split_bunch_list,接收方在 delaytrans/confirm 的 acct_split_bunch 指定
|
||||
* rule_origin=02(控台配置):须传 acct_split_bunch_list 固定接收方与比例
|
||||
*/
|
||||
class HuifuMerchantSplitService
|
||||
{
|
||||
@@ -117,12 +120,14 @@ class HuifuMerchantSplitService
|
||||
}
|
||||
|
||||
$applyRatio = (string) ($options['apply_ratio'] ?? $this->resolveApplyRatio());
|
||||
$ruleOrigin = trim((string) ($options['rule_origin'] ?? '01'));
|
||||
if (!in_array($ruleOrigin, ['01', '02'], true)) {
|
||||
$ruleOrigin = '01';
|
||||
}
|
||||
$ruleOrigin = $this->resolveRuleOrigin($options);
|
||||
|
||||
$receiverHuifuIds = $this->normalizeReceiverIds($receiverHuifuIds, $mainHuifuId);
|
||||
if ($ruleOrigin === '01' && $receiverHuifuIds !== [] && empty($options['force_receiver_list'])) {
|
||||
Log::write('[shopro_huifu] split/config rule_origin=01 忽略 acct_split_bunch_list('
|
||||
. count($receiverHuifuIds) . ' 个接收方仅在 confirm 时指定)', 'info');
|
||||
$receiverHuifuIds = [];
|
||||
}
|
||||
|
||||
$scene = trim((string) ($options['scene'] ?? $this->resolveSplitConfigScene()));
|
||||
if ($scene === '') {
|
||||
@@ -150,19 +155,21 @@ class HuifuMerchantSplitService
|
||||
$extend['async_return_url'] = $asyncUrl;
|
||||
}
|
||||
|
||||
if ($receiverHuifuIds !== []) {
|
||||
if ($ruleOrigin === '02') {
|
||||
if ($receiverHuifuIds === []) {
|
||||
throw new \InvalidArgumentException('rule_origin=02 时必须提供分账接收方列表');
|
||||
}
|
||||
$extend['acct_split_bunch_list'] = $this->encodeAcctSplitBunchList($receiverHuifuIds, $applyRatio);
|
||||
} elseif ($ruleOrigin === '02') {
|
||||
throw new \InvalidArgumentException('rule_origin=02 时必须提供分账接收方列表');
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'dry-run',
|
||||
'dry_run' => true,
|
||||
'receivers' => $receiverHuifuIds,
|
||||
'data' => ['extend' => $extend, 'huifu_id' => $mainHuifuId],
|
||||
'ok' => true,
|
||||
'message' => 'dry-run',
|
||||
'dry_run' => true,
|
||||
'receivers' => $receiverHuifuIds,
|
||||
'rule_origin' => $ruleOrigin,
|
||||
'data' => ['extend' => $extend, 'huifu_id' => $mainHuifuId],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -176,9 +183,9 @@ class HuifuMerchantSplitService
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量同步:合并远端 + 全部已进件子户,一次提交 split/config
|
||||
* 提交分账配置:01 仅开通开关;02 时合并远端+本地进件后带 acct_split_bunch_list
|
||||
*
|
||||
* @return array{ok:bool,message:string,submitted:int,receivers:string[],remote:int,local:int}
|
||||
* @return array{ok:bool,message:string,submitted:int,receivers:string[],remote:int,local:int,rule_origin?:string}
|
||||
*/
|
||||
public function syncAllApprovedReceivers(array $options = [])
|
||||
{
|
||||
@@ -186,31 +193,40 @@ class HuifuMerchantSplitService
|
||||
throw new \RuntimeException('汇付插件未启用');
|
||||
}
|
||||
|
||||
$ruleOrigin = $this->resolveRuleOrigin($options);
|
||||
$main = $this->resolveMainHuifuId();
|
||||
$local = $this->collectApprovedReceiverHuifuIds();
|
||||
$remote = $this->fetchRemoteReceiverHuifuIds($main);
|
||||
$merged = $this->normalizeReceiverIds(array_merge($remote, $local), $main);
|
||||
|
||||
$submit = $this->submitSplitConfig($merged, $options);
|
||||
$toSubmit = $ruleOrigin === '02' ? $merged : [];
|
||||
|
||||
$submit = $this->submitSplitConfig($toSubmit, $options);
|
||||
$ok = !empty($submit['ok']) && empty($submit['dry_run']);
|
||||
$respCode = is_array($submit['data'] ?? null) ? (string) ($submit['data']['resp_code'] ?? '') : '';
|
||||
$businessOk = $respCode === '' || $respCode === '00000000';
|
||||
|
||||
if ($ok && $businessOk && empty($options['dry_run'])) {
|
||||
$this->markOnboardBindByHuifuIds($merged, 'ok', $submit['message'] ?? 'submitted');
|
||||
if ($ruleOrigin === '02') {
|
||||
$this->markOnboardBindByHuifuIds($merged, 'ok', $submit['message'] ?? 'submitted');
|
||||
} else {
|
||||
$this->markOnboardBindByHuifuIds($local, 'ok', 'rule_origin=01 分账配置已提交,接收方在 confirm 指定');
|
||||
}
|
||||
} elseif (!$ok && empty($options['dry_run'])) {
|
||||
$this->markOnboardBindByHuifuIds($local, 'fail', $submit['message'] ?? 'submit failed');
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => !empty($submit['ok']) && ($businessOk || !empty($submit['dry_run'])),
|
||||
'message' => (string) ($submit['message'] ?? ''),
|
||||
'submitted' => count($merged),
|
||||
'receivers' => $merged,
|
||||
'remote' => count($remote),
|
||||
'local' => count($local),
|
||||
'resp_code' => $respCode,
|
||||
'data' => $submit['data'] ?? null,
|
||||
'ok' => !empty($submit['ok']) && ($businessOk || !empty($submit['dry_run'])),
|
||||
'message' => (string) ($submit['message'] ?? ''),
|
||||
'submitted' => $ruleOrigin === '02' ? count($merged) : 0,
|
||||
'receivers' => $merged,
|
||||
'remote' => count($remote),
|
||||
'local' => count($local),
|
||||
'resp_code' => $respCode,
|
||||
'rule_origin' => $ruleOrigin,
|
||||
'dry_run' => !empty($submit['dry_run']),
|
||||
'data' => $submit['data'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -240,12 +256,19 @@ class HuifuMerchantSplitService
|
||||
return ['ok' => false, 'message' => 'split_auto_bind_receiver=0', 'skipped' => true];
|
||||
}
|
||||
|
||||
$ruleOrigin = $this->resolveRuleOrigin($options);
|
||||
if ($ruleOrigin === '01' && $this->isSplitConfigEffective($main)) {
|
||||
$this->updateOnboardBindFields($row, 'skip', 'rule_origin=01,分账配置已生效');
|
||||
return ['ok' => true, 'message' => 'split config already effective', 'skipped' => true];
|
||||
}
|
||||
|
||||
$remote = $this->fetchRemoteReceiverHuifuIds($main);
|
||||
$local = $this->collectApprovedReceiverHuifuIds();
|
||||
$merged = $this->normalizeReceiverIds(array_merge($remote, $local, [$hid]), $main);
|
||||
$toSubmit = $ruleOrigin === '02' ? $merged : [];
|
||||
|
||||
try {
|
||||
$submit = $this->submitSplitConfig($merged, $options);
|
||||
$submit = $this->submitSplitConfig($toSubmit, $options);
|
||||
} catch (\Throwable $e) {
|
||||
$this->updateOnboardBindFields($row, 'fail', $e->getMessage());
|
||||
Log::error('[shopro_huifu] bind onboard split fail id=' . $row['id'] . ' ' . $e->getMessage());
|
||||
@@ -284,6 +307,26 @@ class HuifuMerchantSplitService
|
||||
return $scene !== '' ? $scene : 'A5';
|
||||
}
|
||||
|
||||
public function resolveRuleOrigin(array $options = [])
|
||||
{
|
||||
$ruleOrigin = trim((string) ($options['rule_origin'] ?? $this->config->get('split_config_rule_origin', '01')));
|
||||
if (!in_array($ruleOrigin, ['01', '02'], true)) {
|
||||
$ruleOrigin = '01';
|
||||
}
|
||||
return $ruleOrigin;
|
||||
}
|
||||
|
||||
public function isSplitConfigEffective($mainHuifuId = null)
|
||||
{
|
||||
$mainHuifuId = $mainHuifuId ?: $this->resolveMainHuifuId();
|
||||
$result = $this->client->queryMerchantSplitConfig($mainHuifuId);
|
||||
if (!$result['ok']) {
|
||||
return false;
|
||||
}
|
||||
$items = HuifuClient::parseSplitBillResult($result['data']['split_bill_result'] ?? null);
|
||||
return $items !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null JSON 字符串,供 extend.split_ext_info
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,8 @@ ALTER TABLE `__PREFIX__shopro_huifu_onboard`
|
||||
INSERT IGNORE INTO `__PREFIX__shopro_huifu_config` (`name`, `value`, `group`, `description`) VALUES
|
||||
('split_auto_bind_receiver', '1', 'split', '进件通过后自动调 v2/merchant/split/config 绑定分账接收方(0关1开)'),
|
||||
('split_config_async_url', '', 'split', '分账配置异步通知 virgo:// 地址,可选'),
|
||||
('split_config_scene', 'A5', 'split', '分账模式 scene:A5推广方佣金/02线上其他/00其他,须与汇付控台申请一致'),
|
||||
('split_config_rule_origin', '01', 'split', '分账规则来源 01接口发起(商城confirm传账) 02控台配置'),
|
||||
('split_config_scene', 'A5', 'split', '分账模式 scene:A5推广方佣金等,须与汇付申请一致(勿与02线上其他混淆)'),
|
||||
('split_config_ext_info', '', 'split', 'split_ext_info 整段 JSON,留空则>30%时用内置说明或下方三字段'),
|
||||
('split_config_busi_instruction', '', 'split', '业务模式说明 busi_instruction'),
|
||||
('split_config_capital_instruction', '', 'split', '资金流向说明 capital_instruction'),
|
||||
|
||||
在新工单中引用
屏蔽一个用户