From b66b74b4f2c86f53b0c583480c2712d5023eb005 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 10:35:02 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(shopro=5Fhuifu):=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=B1=87=E4=BB=98=E5=88=86=E8=B4=A6=E9=85=8D=E7=BD=AE?= =?UTF-8?q?rule=5Forigin=E5=8F=8C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持汇付分账配置的两种模式: - rule_origin=01:接口发起模式,不带接收方列表,接收方在confirm时指定 - rule_origin=02:控台配置模式,需带acct_split_bunch_list固定接收方 更新SyncSplitReceivers命令支持--rule-origin选项,调整分账配置提交逻辑, 新增配置项split_config_rule_origin默认为01模式,并完善相关文档说明 BREAKING CHANGE: 分账配置流程调整,需重新配置相关参数 ``` --- .../shopro_huifu/command/QuerySplitConfig.php | 4 +- .../command/SyncSplitReceivers.php | 33 +++++-- .../split/HuifuMerchantSplitService.php | 95 ++++++++++++++----- .../shopro_huifu/upgrade_huifu_split_bind.sql | 3 +- 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/addons/shopro_huifu/command/QuerySplitConfig.php b/addons/shopro_huifu/command/QuerySplitConfig.php index 337b483..d3cc809 100644 --- a/addons/shopro_huifu/command/QuerySplitConfig.php +++ b/addons/shopro_huifu/command/QuerySplitConfig.php @@ -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; diff --git a/addons/shopro_huifu/command/SyncSplitReceivers.php b/addons/shopro_huifu/command/SyncSplitReceivers.php index e952a2a..9e32833 100644 --- a/addons/shopro_huifu/command/SyncSplitReceivers.php +++ b/addons/shopro_huifu/command/SyncSplitReceivers.php @@ -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) { diff --git a/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php b/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php index 443d50b..493107d 100644 --- a/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php +++ b/addons/shopro_huifu/library/split/HuifuMerchantSplitService.php @@ -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 */ diff --git a/addons/shopro_huifu/upgrade_huifu_split_bind.sql b/addons/shopro_huifu/upgrade_huifu_split_bind.sql index 3661e52..7922b40 100644 --- a/addons/shopro_huifu/upgrade_huifu_split_bind.sql +++ b/addons/shopro_huifu/upgrade_huifu_split_bind.sql @@ -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'),