From 7767298a43cda83e06631fa3d8e82875a426490c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=AF=E4=B9=90=E5=8A=A0=E5=86=B0?= <11869599+addie66@user.noreply.gitee.com> Date: Tue, 13 Oct 2026 11:17:00 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(shopro=5Fhuifu):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=88=86=E8=B4=A6=E9=85=8D=E7=BD=AE=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=91=BD=E4=BB=A4=E5=B9=B6=E5=AE=8C=E5=96=84?= =?UTF-8?q?online=5Fbusi=5Ftype=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ProbeSplitConfigDeps命令用于检查split/config提交前的依赖项 (online_busi_type、运营媒介等) - 在SyncSplitReceivers命令中添加--online-busi-type选项 - 更新QuerySplitConfig显示online_busi_type信息并调整后续步骤提示 - 完善HuifuMerchantSplitService中的online_busi_type解析逻辑 - 添加extended_material_list配置支持 - 在数据库升级脚本中增加新的配置项 ``` --- .../command/ProbeSplitConfigDeps.php | 91 +++++++++++++++++++ .../shopro_huifu/command/QuerySplitConfig.php | 12 ++- .../command/SyncSplitReceivers.php | 12 ++- .../split/HuifuMerchantSplitService.php | 86 ++++++++++++++++++ .../shopro_huifu/upgrade_huifu_split_bind.sql | 4 +- application/command.php | 1 + 6 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 addons/shopro_huifu/command/ProbeSplitConfigDeps.php diff --git a/addons/shopro_huifu/command/ProbeSplitConfigDeps.php b/addons/shopro_huifu/command/ProbeSplitConfigDeps.php new file mode 100644 index 0000000..197c416 --- /dev/null +++ b/addons/shopro_huifu/command/ProbeSplitConfigDeps.php @@ -0,0 +1,91 @@ +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; + } +} diff --git a/addons/shopro_huifu/command/QuerySplitConfig.php b/addons/shopro_huifu/command/QuerySplitConfig.php index d3cc809..6d4361b 100644 --- a/addons/shopro_huifu/command/QuerySplitConfig.php +++ b/addons/shopro_huifu/command/QuerySplitConfig.php @@ -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'); diff --git a/addons/shopro_huifu/command/SyncSplitReceivers.php b/addons/shopro_huifu/command/SyncSplitReceivers.php index 2bf10f2..576b3c2 100644 --- a/addons/shopro_huifu/command/SyncSplitReceivers.php +++ b/addons/shopro_huifu/command/SyncSplitReceivers.php @@ -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') { diff --git a/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php b/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php index 8f3af42..81a6835 100644 --- a/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php +++ b/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php @@ -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 */ diff --git a/addons/shopro_huifu/upgrade_huifu_split_bind.sql b/addons/shopro_huifu/upgrade_huifu_split_bind.sql index d2a4248..8f97e60 100644 --- a/addons/shopro_huifu/upgrade_huifu_split_bind.sql +++ b/addons/shopro_huifu/upgrade_huifu_split_bind.sql @@ -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'); diff --git a/application/command.php b/application/command.php index 9edec18..90ec519 100644 --- a/application/command.php +++ b/application/command.php @@ -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',