```
feat(shopro_huifu): 添加汇付进件自动提交功能 - 新增自动提交开关配置项,支持定时自动提交汇付进件 - 添加失败重提间隔配置,支持失败后指定时间自动重试 - 创建OnboardAutoSubmit命令类,实现自动提交逻辑 - 在进件页面更新提示信息,说明自动提交流程 - 同步更新数据库配置表结构和升级脚本 ```
这个提交包含在:
@@ -46,6 +46,13 @@
|
||||
<el-form-item label="结算周期"><el-input v-model="form.onboard_settle_cycle" placeholder="D1"></el-input></el-form-item>
|
||||
<el-form-item label="结算批次号"><el-input v-model="form.onboard_settle_batch_no"></el-input></el-form-item>
|
||||
<el-form-item label="进件通知URL"><el-input v-model="form.onboard_notify_url" readonly></el-input></el-form-item>
|
||||
<el-form-item label="自动提交进件">
|
||||
<el-switch v-model="form.onboard_auto_submit" active-value="1" inactive-value="0"></el-switch>
|
||||
<span class="text-muted" style="font-size:12px;margin-left:8px;">需配置 crontab: shopro_huifu:onboard-auto-submit</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="失败重提间隔(小时)">
|
||||
<el-input v-model="form.onboard_auto_retry_hours" placeholder="24" style="width:120px"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">只读</el-divider>
|
||||
<el-form-item label="支付通知URL"><el-input v-model="form.notify_url" readonly></el-input></el-form-item>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
<div class="panel-heading">
|
||||
汇付个人进件
|
||||
<span class="text-muted" style="font-size:12px;margin-left:12px;">先审核进件资料,业务审核通过后再提交汇付</span>
|
||||
<span class="text-muted" style="font-size:12px;margin-left:12px;">业务审核通过后由定时任务自动提交汇付;失败满 24 小时自动重提(可手动「提交汇付/重提」)</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="margin-bottom:12px;">
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro_huifu\command;
|
||||
|
||||
use addons\shopro_huifu\library\onboard\HuifuOnboardAutoSubmit;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class OnboardAutoSubmit extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -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', '进件失败后自动重提间隔(小时)');
|
||||
|
||||
|
||||
@@ -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'] ?? '';
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro_huifu\library\onboard;
|
||||
|
||||
use addons\shopro_huifu\library\HuifuConfig;
|
||||
use addons\shopro_huifu\model\Onboard;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* 汇付进件后台自动提交:业务已通过且从未提交汇付的立即提交;失败/驳回满冷却期后重提。
|
||||
*/
|
||||
class HuifuOnboardAutoSubmit
|
||||
{
|
||||
public function run($limit = 30, $dryRun = false)
|
||||
{
|
||||
$stats = [
|
||||
'scanned' => 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);
|
||||
}
|
||||
}
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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', '进件失败后自动重提间隔(小时)');
|
||||
@@ -52,6 +52,13 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="进件通知URL"><el-input v-model="form.onboard_notify_url" readonly></el-input></el-form-item>
|
||||
<el-form-item label="自动提交进件">
|
||||
<el-switch v-model="form.onboard_auto_submit" active-value="1" inactive-value="0"></el-switch>
|
||||
<span class="text-muted" style="font-size:12px;margin-left:8px;">需配置 crontab: shopro_huifu:onboard-auto-submit</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="失败重提间隔(小时)">
|
||||
<el-input v-model="form.onboard_auto_retry_hours" placeholder="24" style="width:120px"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">只读</el-divider>
|
||||
<el-form-item label="支付通知URL"><el-input v-model="form.notify_url" readonly></el-input></el-form-item>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
<div class="panel-heading">
|
||||
汇付个人进件
|
||||
<span class="text-muted" style="font-size:12px;margin-left:12px;">先审核进件资料,业务审核通过后再提交汇付</span>
|
||||
<span class="text-muted" style="font-size:12px;margin-left:12px;">业务审核通过后由定时任务自动提交汇付;失败满 24 小时自动重提(可手动「提交汇付/重提」)</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="margin-bottom:12px;">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
在新工单中引用
屏蔽一个用户