feat(shopro_huifu): 新增分账配置依赖检查命令并完善online_busi_type支持

- 新增ProbeSplitConfigDeps命令用于检查split/config提交前的依赖项
  (online_busi_type、运营媒介等)
- 在SyncSplitReceivers命令中添加--online-busi-type选项
- 更新QuerySplitConfig显示online_busi_type信息并调整后续步骤提示
- 完善HuifuMerchantSplitService中的online_busi_type解析逻辑
- 添加extended_material_list配置支持
- 在数据库升级脚本中增加新的配置项
```
这个提交包含在:
可乐加冰
2026-10-13 11:17:00 +08:00
父节点 ae5aac26ac
当前提交 7767298a43
共有 6 个文件被更改,包括 202 次插入 和 4 次删除

查看文件

@@ -0,0 +1,91 @@
<?php
namespace addons\shopro_huifu\command;
use addons\shopro_huifu\library\Bridge;
use addons\shopro_huifu\library\HuifuClient;
use addons\shopro_huifu\library\HuifuConfig;
use addons\shopro_huifu\library\split\HuifuMerchantSplitService;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
/**
* 分账 split/config 提交前依赖项检查(online_busi_type、运营媒介等)
*/
class ProbeSplitConfigDeps extends Command
{
protected function configure()
{
$this->setName('shopro_huifu:probe-split-config-deps')
->addOption('huifu-id', null, Option::VALUE_OPTIONAL, '主商户号')
->setDescription('Check split/config prerequisites before sync-split-receivers');
}
protected function execute(Input $input, Output $output)
{
if (!Bridge::isEnabled()) {
$output->writeln('[FAIL] 汇付插件未启用');
return 1;
}
$service = new HuifuMerchantSplitService();
$main = trim((string) $input->getOption('huifu-id'));
if ($main === '') {
$main = $service->resolveMainHuifuId();
}
$ratio = floatval($service->resolveApplyRatio());
$cfg = HuifuConfig::getInstance();
$output->writeln('主商户 huifu_id: ' . $main);
$output->writeln('apply_ratio: ' . $ratio . '%');
$output->writeln('scene(配置): ' . $service->resolveSplitConfigScene());
$output->writeln('rule_origin(配置): ' . $service->resolveRuleOrigin([]));
$output->writeln('');
$onlineBusi = $service->resolveOnlineBusiType(['main_huifu_id' => $main], $main);
$cfgBusi = trim((string) $cfg->get('split_config_online_busi_type', ''));
$output->writeln('--- online_busi_type (>30% 必填) ---');
$output->writeln('插件配置 split_config_online_busi_type: ' . ($cfgBusi !== '' ? $cfgBusi : '(空)'));
$output->writeln('汇付商户详情/合并结果: ' . ($onlineBusi !== '' ? $onlineBusi : '(未查到)'));
if ($ratio > 30 && $onlineBusi === '') {
$output->writeln('[FAIL] 缺少 online_busi_type,请向渠道/实施索取编码或查 KYC 编码表后写入配置');
} else {
$output->writeln('[OK] online_busi_type');
}
$output->writeln('');
$mediaJson = trim((string) $cfg->get('split_config_online_media_json', ''));
$output->writeln('--- online_media_info_list (>20% 必填) ---');
$output->writeln('split_config_online_media_json: ' . ($mediaJson !== '' ? '已配置(' . strlen($mediaJson) . ' 字符)' : '(空)'));
if ($service->hasOnlineMediaForSplit($ratio)) {
$output->writeln('[OK] 运营媒介可组装');
} elseif ($service->needsOnlineMediaInfo($ratio)) {
$output->writeln('[FAIL] 缺少运营媒介,请 php think shopro_huifu:upload-split-media ... --save-config');
}
$output->writeln('');
$extMat = trim((string) $cfg->get('split_config_extended_material_json', ''));
$output->writeln('--- extended_material_list (按编码表可选/必填) ---');
$output->writeln('split_config_extended_material_json: ' . ($extMat !== '' ? '已配置' : '(空,若下一报错要求补充材料再填)'));
$client = new HuifuClient();
$basic = $client->queryMerchantBasicdata($main, true);
if ($basic['ok']) {
$scan = HuifuClient::collectNestedKeys($basic['data'] ?? [], [
'online_busi_type', 'delay_flag', 'mcc', 'wx_mcc', 'ali_mcc', 'scene_type',
]);
if ($scan !== []) {
$output->writeln('');
$output->writeln('商户详情相关字段: ' . json_encode($scan, JSON_UNESCAPED_UNICODE));
}
}
$output->writeln('');
$output->writeln('预览 split/config 请求体: php think shopro_huifu:sync-split-receivers --dry-run');
$output->writeln('正式提交: php think shopro_huifu:sync-split-receivers --online-busi-type=你的编码');
return ($ratio > 30 && $onlineBusi === '') ? 1 : 0;
}
}

查看文件

@@ -100,13 +100,21 @@ class QuerySplitConfig extends Command
$output->writeln('delay_flag(延迟入账): ' . ($delay !== '' ? $delay : '(未返回)'));
$output->writeln('forced_delay_flag: ' . ($forced !== '' ? $forced : '(未返回)'));
} else {
$scan = HuifuClient::collectNestedKeys($bd, ['delay_flag', 'forced_delay_flag', 'rule_origin', 'div_flag']);
$scan = HuifuClient::collectNestedKeys($bd, [
'delay_flag', 'forced_delay_flag', 'rule_origin', 'div_flag', 'online_busi_type',
]);
if ($scan !== []) {
$output->writeln('详情嵌套: ' . json_encode($scan, JSON_UNESCAPED_UNICODE));
} else {
$output->writeln('未读到 delay_flag(可 --json 看 basicdata 全文)');
}
}
$busiType = trim((string) (HuifuClient::collectNestedKeys($bd, ['online_busi_type'])['online_busi_type'] ?? ''));
if ($busiType !== '') {
$output->writeln('online_busi_type(进件/业务资料): ' . $busiType);
} else {
$output->writeln('[提示] 40% 分账 split/config 须 online_busi_type,主商户详情未返回时可问汇付渠道或查 KYC 编码表');
}
if ($bCode !== '' && $bCode !== '00000000') {
$output->writeln('basicdata resp_code=' . $bCode . ' ' . ($bd['resp_desc'] ?? ''));
}
@@ -115,7 +123,7 @@ class QuerySplitConfig extends Command
}
$output->writeln('');
$output->writeln('下一步(汇付实施/工单):');
$output->writeln(' 1) php think shopro_huifu:sync-split-receivers(rule_origin=01 仅开通分账,不带接收方列表)');
$output->writeln(' 1) 配齐 online_busi_type + 运营媒介后 php think shopro_huifu:sync-split-receivers');
$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');

查看文件

@@ -20,6 +20,7 @@ class SyncSplitReceivers extends Command
->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)')
->addOption('online-busi-type', null, Option::VALUE_OPTIONAL, '线上业务类型编码 online_busi_type(40%分账必填)')
->setDescription('Submit v2/merchant/split/config (01=open split; 02=with receiver list)');
}
@@ -44,6 +45,11 @@ class SyncSplitReceivers extends Command
$ruleOriginOpt = trim((string) $input->getOption('rule-origin'));
$ruleOrigin = $ruleOriginOpt !== '' ? $ruleOriginOpt : $service->resolveRuleOrigin([]);
$output->writeln('rule_origin: ' . $ruleOrigin . ($ruleOrigin === '01' ? '(接口发起,confirm 传 acct_split_bunch)' : '(控台配置,须带接收方列表)'));
$onlineBusiOpt = trim((string) $input->getOption('online-busi-type'));
$onlineBusiType = $onlineBusiOpt !== ''
? $onlineBusiOpt
: $service->resolveOnlineBusiType(['main_huifu_id' => $main], $main);
$output->writeln('online_busi_type: ' . ($onlineBusiType !== '' ? $onlineBusiType : '(未配置,40%分账将失败)'));
$dryRun = (bool) $input->getOption('dry-run');
$initOnly = (bool) $input->getOption('init-only');
@@ -51,8 +57,12 @@ class SyncSplitReceivers extends Command
'dry_run' => $dryRun,
'main_huifu_id' => $main,
'scene' => $scene,
'rule_origin' => $ruleOrigin,
'rule_origin' => $ruleOrigin,
'online_busi_type' => $onlineBusiType !== '' ? $onlineBusiType : null,
];
if ($onlineBusiType === '') {
unset($options['online_busi_type']);
}
try {
if ($initOnly || $ruleOrigin === '01') {

查看文件

@@ -161,6 +161,22 @@ class HuifuMerchantSplitService
);
}
$onlineBusiType = $this->resolveOnlineBusiType($options, $mainHuifuId);
if ($onlineBusiType !== '') {
$extend['online_busi_type'] = $onlineBusiType;
} elseif ($this->needsOnlineBusiType($ratioFloat) && empty($options['skip_online_busi_check'])) {
throw new \InvalidArgumentException(
'分账比例>30%须填写 online_busi_type(线上业务类型编码,8位如 H7999AL)。'
. '请配置 split_config_online_busi_type、传 --online-busi-type=,或确认主商户进件资料已含该字段;'
. '编码表见汇付《线上业务类型编码及补充材料说明》或问渠道/实施(酒类小程序须选与营业执照一致项)'
);
}
$extendedMaterial = $this->buildExtendedMaterialListJson($options);
if ($extendedMaterial !== null && $extendedMaterial !== '') {
$extend['extended_material_list'] = $extendedMaterial;
}
$asyncUrl = trim((string) ($options['async_return_url'] ?? $this->config->get('split_config_async_url', '')));
if ($asyncUrl !== '') {
$extend['async_return_url'] = $asyncUrl;
@@ -400,6 +416,76 @@ class HuifuMerchantSplitService
return floatval($applyRatio) > 20.0;
}
public function needsOnlineBusiType($applyRatio)
{
return floatval($applyRatio) > 30.0;
}
public function hasOnlineMediaForSplit($applyRatio, array $options = [])
{
try {
$json = $this->buildOnlineMediaInfoListJson($options, floatval($applyRatio));
return $json !== null && $json !== '';
} catch (\Throwable $e) {
return false;
}
}
public function resolveOnlineBusiType(array $options = [], $mainHuifuId = null)
{
$fromOpt = trim((string) ($options['online_busi_type'] ?? ''));
if ($fromOpt !== '') {
return $fromOpt;
}
$fromCfg = trim((string) $this->config->get('split_config_online_busi_type', ''));
if ($fromCfg !== '') {
return $fromCfg;
}
if (!empty($options['skip_merchant_online_busi_lookup'])) {
return '';
}
$mainHuifuId = $mainHuifuId ?: $this->resolveMainHuifuId();
try {
$basic = $this->client->queryMerchantBasicdata($mainHuifuId);
if (!empty($basic['ok']) && is_array($basic['data'] ?? null)) {
$found = HuifuClient::collectNestedKeys($basic['data'], ['online_busi_type']);
$v = trim((string) ($found['online_busi_type'] ?? ''));
if ($v !== '') {
return $v;
}
}
} catch (\Throwable $e) {
Log::write('[shopro_huifu] split/config online_busi_type lookup fail: ' . $e->getMessage(), 'info');
}
return '';
}
/**
* @return string|null JSON 数组字符串
*/
protected function buildExtendedMaterialListJson(array $options = [])
{
if (isset($options['extended_material_list'])) {
$raw = $options['extended_material_list'];
if (is_array($raw)) {
return json_encode($raw, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
$raw = trim((string) $raw);
if ($raw !== '') {
return $raw;
}
}
$cfg = trim((string) $this->config->get('split_config_extended_material_json', ''));
if ($cfg === '') {
return null;
}
$decoded = json_decode($cfg, true);
if (!is_array($decoded)) {
throw new \InvalidArgumentException('split_config_extended_material_json 不是合法 JSON 数组');
}
return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* @return string|null JSON 字符串(数组),供 extend.online_media_info_list
*/

查看文件

@@ -22,4 +22,6 @@ INSERT IGNORE INTO `__PREFIX__shopro_huifu_config` (`name`, `value`, `group`, `d
('split_config_media_service_page', '', 'split', '商品明细页 file_id'),
('split_config_media_order_page', '', 'split', '下单页 file_id'),
('split_config_media_pay_page', '', 'split', '支付页 file_id'),
('split_config_media_authorize_materials', '', 'split', '主体不一致时的授权材料 file_id');
('split_config_media_authorize_materials', '', 'split', '主体不一致时的授权材料 file_id'),
('split_config_online_busi_type', '', 'split', '线上业务类型编码(分账>30%必填,如H7999AL,见汇付KYC编码表)'),
('split_config_extended_material_json', '', 'split', 'extended_material_list JSON 数组,按编码表要求的补充材料 file_id');

查看文件

@@ -27,6 +27,7 @@ return [
'addons\shopro_huifu\command\QuerySplitConfig',
'addons\shopro_huifu\command\SyncSplitReceivers',
'addons\shopro_huifu\command\UploadSplitMedia',
'addons\shopro_huifu\command\ProbeSplitConfigDeps',
'addons\shopro_huifu\command\DiagnoseFreezeFlow',
'addons\shopro_huifu\command\OnboardPoll',
'addons\shopro_huifu\command\OnboardAutoSubmit',