diff --git a/addons/shopro_huifu/command/OnboardAutoSubmit.php b/addons/shopro_huifu/command/OnboardAutoSubmit.php
new file mode 100644
index 0000000..d087d48
--- /dev/null
+++ b/addons/shopro_huifu/command/OnboardAutoSubmit.php
@@ -0,0 +1,39 @@
+setName('shopro_huifu:onboard-auto-submit')
+ ->addOption('limit', 'l', Option::VALUE_OPTIONAL, '每轮最多处理条数', 30)
+ ->addOption('dry-run', null, Option::VALUE_NONE, '只扫描不调用汇付')
+ ->setDescription('Auto submit Huifu onboard: new drafts immediately, failed retry after cooldown');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $dryRun = (bool) $input->getOption('dry-run');
+ $limit = intval($input->getOption('limit'));
+ $output->writeln('=== 汇付进件自动提交 ===' . ($dryRun ? ' [dry-run]' : ''));
+
+ $stats = (new HuifuOnboardAutoSubmit())->run($limit, $dryRun);
+ $output->writeln('扫描: ' . $stats['scanned']);
+ $output->writeln('首次提交: ' . $stats['submit_now']);
+ $output->writeln('失败重提: ' . $stats['retry']);
+ $output->writeln('跳过: ' . $stats['skipped']);
+ $output->writeln('执行失败: ' . $stats['failed']);
+ foreach ($stats['details'] as $line) {
+ $output->writeln(' · ' . $line);
+ }
+
+ return $stats['failed'] > 0 ? 1 : 0;
+ }
+}
diff --git a/addons/shopro_huifu/install.sql b/addons/shopro_huifu/install.sql
index 4ef2a93..96f9dbe 100644
--- a/addons/shopro_huifu/install.sql
+++ b/addons/shopro_huifu/install.sql
@@ -105,5 +105,7 @@ INSERT IGNORE INTO `__PREFIX__shopro_huifu_config` (`name`, `value`, `group`, `d
('onboard_cash_type', 'D1', 'onboard', '取现类型'),
('onboard_cash_fix_amt', '0.03', 'onboard', '取现固定手续费'),
('onboard_cash_fee_rate', '0', 'onboard', '取现费率'),
-('onboard_skill_source', 'hfps/1.3.5;hfms/1.0.2', 'onboard', '进件SDK skill_source');
+('onboard_skill_source', 'hfps/1.3.5;hfms/1.0.2', 'onboard', '进件SDK skill_source'),
+('onboard_auto_submit', '1', 'onboard', '定时自动提交汇付进件(0关1开)'),
+('onboard_auto_retry_hours', '24', 'onboard', '进件失败后自动重提间隔(小时)');
diff --git a/addons/shopro_huifu/library/onboard/HuifuOnboardAdminView.php b/addons/shopro_huifu/library/onboard/HuifuOnboardAdminView.php
index 5aa4d3c..7120d13 100644
--- a/addons/shopro_huifu/library/onboard/HuifuOnboardAdminView.php
+++ b/addons/shopro_huifu/library/onboard/HuifuOnboardAdminView.php
@@ -302,6 +302,15 @@ class HuifuOnboardAdminView
return $parts ? implode('、', $parts) : '无进件角色';
}
+ /**
+ * 分销/区域公司业务审核是否已通过(status=normal)
+ */
+ public static function isBusinessApproved(array $data)
+ {
+ $biz = self::businessStatus($data);
+ return ($biz['status'] ?? '') === 'normal';
+ }
+
protected static function businessStatus(array $data)
{
$roleType = $data['role_type'] ?? '';
diff --git a/addons/shopro_huifu/library/onboard/HuifuOnboardAutoSubmit.php b/addons/shopro_huifu/library/onboard/HuifuOnboardAutoSubmit.php
new file mode 100644
index 0000000..3d4586a
--- /dev/null
+++ b/addons/shopro_huifu/library/onboard/HuifuOnboardAutoSubmit.php
@@ -0,0 +1,146 @@
+ 0,
+ 'submit_now' => 0,
+ 'retry' => 0,
+ 'skipped' => 0,
+ 'failed' => 0,
+ 'details' => [],
+ ];
+
+ if (!HuifuOnboardGate::isOnboardRequired()) {
+ $stats['details'][] = 'skip: 汇付进件未启用';
+ return $stats;
+ }
+
+ if (!$this->isAutoSubmitEnabled()) {
+ $stats['details'][] = 'skip: onboard_auto_submit=0';
+ return $stats;
+ }
+
+ $limit = max(1, min(100, (int) $limit));
+ $retryAfter = $this->getRetryAfterSeconds();
+ $service = new HuifuOnboardService();
+ $now = time();
+
+ $rows = Onboard::where('onboard_status', 'in', [
+ Onboard::STATUS_DRAFT,
+ Onboard::STATUS_FAILED,
+ Onboard::STATUS_REJECTED,
+ ])->order('id asc')->limit($limit * 3)->select();
+
+ foreach ($rows as $row) {
+ if ($stats['submit_now'] + $stats['retry'] >= $limit) {
+ break;
+ }
+ $stats['scanned']++;
+ $data = $row->toArray();
+ $action = $this->resolveAction($data, $now, $retryAfter);
+ if ($action === null) {
+ $stats['skipped']++;
+ continue;
+ }
+
+ $payload = json_decode($row['payload'] ?? '', true);
+ if (!$payload || !is_array($payload)) {
+ $stats['skipped']++;
+ $stats['details'][] = 'id=' . $row['id'] . ' skip: 无进件资料 payload';
+ continue;
+ }
+
+ $line = 'id=' . $row['id'] . ' uid=' . $row['user_id'] . ' ' . $row['role_type']
+ . ' action=' . $action;
+ if ($dryRun) {
+ $stats[$action === 'submit_now' ? 'submit_now' : 'retry']++;
+ $stats['details'][] = $line . ' (dry-run)';
+ continue;
+ }
+
+ try {
+ if ($action === 'submit_now') {
+ $service->submitFromDraft($row['user_id'], $row['role_type'], $row['role_ref_id']);
+ $stats['submit_now']++;
+ } else {
+ $service->resubmit($row['user_id'], $row['role_type'], $row['role_ref_id'], $payload);
+ $stats['retry']++;
+ }
+ $stats['details'][] = $line . ' ok';
+ } catch (\Throwable $e) {
+ $stats['failed']++;
+ $msg = HuifuOnboardError::translate($e->getMessage());
+ Log::error('[shopro_huifu] onboard auto-submit fail ' . $line . ' ' . $e->getMessage());
+ $stats['details'][] = $line . ' fail: ' . $msg;
+ }
+ }
+
+ return $stats;
+ }
+
+ /**
+ * @return string|null submit_now|retry
+ */
+ protected function resolveAction(array $data, $now, $retryAfter)
+ {
+ if (!HuifuOnboardAdminView::isBusinessApproved($data)) {
+ return null;
+ }
+
+ $status = (string) ($data['onboard_status'] ?? '');
+ $updatedAt = intval($data['updatetime'] ?? 0);
+
+ if ($status === Onboard::STATUS_DRAFT) {
+ if ($this->hasSubmittedToHuifu($data)) {
+ return null;
+ }
+ return 'submit_now';
+ }
+
+ if (in_array($status, [Onboard::STATUS_FAILED, Onboard::STATUS_REJECTED], true)) {
+ if ($updatedAt <= 0 || ($now - $updatedAt) < $retryAfter) {
+ return null;
+ }
+ return 'retry';
+ }
+
+ return null;
+ }
+
+ protected function hasSubmittedToHuifu(array $data)
+ {
+ if (!empty($data['basic_req_seq_id']) || !empty($data['busi_req_seq_id'])) {
+ return true;
+ }
+ if (!empty($data['huifu_id'])) {
+ return true;
+ }
+ return false;
+ }
+
+ protected function isAutoSubmitEnabled()
+ {
+ return HuifuConfig::getInstance()->get('onboard_auto_submit', '1') !== '0';
+ }
+
+ protected function getRetryAfterSeconds()
+ {
+ $hours = (float) HuifuConfig::getInstance()->get('onboard_auto_retry_hours', '24');
+ if ($hours <= 0) {
+ $hours = 24;
+ }
+ return (int) round($hours * 3600);
+ }
+}
diff --git a/addons/shopro_huifu/public/assets/js/backend/shopro_huifu/config.js b/addons/shopro_huifu/public/assets/js/backend/shopro_huifu/config.js
index 1243ae2..ba277d8 100644
--- a/addons/shopro_huifu/public/assets/js/backend/shopro_huifu/config.js
+++ b/addons/shopro_huifu/public/assets/js/backend/shopro_huifu/config.js
@@ -25,7 +25,9 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe
onboard_settle_cycle: 'D1',
onboard_settle_batch_no: '300',
onboard_settle_fee_rate: '0.05',
- onboard_notify_url: ''
+ onboard_notify_url: '',
+ onboard_auto_submit: '1',
+ onboard_auto_retry_hours: '24'
}
},
mounted: function () {
diff --git a/addons/shopro_huifu/upgrade_huifu_onboard_auto.sql b/addons/shopro_huifu/upgrade_huifu_onboard_auto.sql
new file mode 100644
index 0000000..9c48e8f
--- /dev/null
+++ b/addons/shopro_huifu/upgrade_huifu_onboard_auto.sql
@@ -0,0 +1,4 @@
+-- 汇付进件自动提交配置(已有站点执行一次)
+INSERT IGNORE INTO `__PREFIX__shopro_huifu_config` (`name`, `value`, `group`, `description`) VALUES
+('onboard_auto_submit', '1', 'onboard', '定时自动提交汇付进件(0关1开)'),
+('onboard_auto_retry_hours', '24', 'onboard', '进件失败后自动重提间隔(小时)');
diff --git a/application/admin/view/shopro_huifu/config/index.html b/application/admin/view/shopro_huifu/config/index.html
index 3cb954a..186a940 100644
--- a/application/admin/view/shopro_huifu/config/index.html
+++ b/application/admin/view/shopro_huifu/config/index.html
@@ -52,6 +52,13 @@
+
+
+ 需配置 crontab: shopro_huifu:onboard-auto-submit
+
+
+
+
只读
diff --git a/application/admin/view/shopro_huifu/onboard/index.html b/application/admin/view/shopro_huifu/onboard/index.html
index 3cf659b..aa31cae 100644
--- a/application/admin/view/shopro_huifu/onboard/index.html
+++ b/application/admin/view/shopro_huifu/onboard/index.html
@@ -21,7 +21,7 @@
汇付个人进件
- 先审核进件资料,业务审核通过后再提交汇付
+ 业务审核通过后由定时任务自动提交汇付;失败满 24 小时自动重提(可手动「提交汇付/重提」)
diff --git a/application/command.php b/application/command.php
index bef3885..afc97df 100644
--- a/application/command.php
+++ b/application/command.php
@@ -26,6 +26,7 @@ return [
'addons\shopro_huifu\command\ProbeWechatOnboarding',
'addons\shopro_huifu\command\QuerySplitConfig',
'addons\shopro_huifu\command\OnboardPoll',
+ 'addons\shopro_huifu\command\OnboardAutoSubmit',
'addons\shopro_huifu\command\ImportBankBranch',
'addons\shopro_wechat_trade\command\InitOrderPath',
'addons\shopro_wechat_trade\command\ProbeOrderPath',
diff --git a/public/assets/js/backend/shopro_huifu/config.js b/public/assets/js/backend/shopro_huifu/config.js
index 5f82ecd..8c14254 100644
--- a/public/assets/js/backend/shopro_huifu/config.js
+++ b/public/assets/js/backend/shopro_huifu/config.js
@@ -24,7 +24,9 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe
onboard_required: '1',
onboard_settle_cycle: 'D1',
onboard_settle_batch_no: '300',
- onboard_notify_url: ''
+ onboard_notify_url: '',
+ onboard_auto_submit: '1',
+ onboard_auto_retry_hours: '24'
}
},
mounted: function () {