feat(huifu): 支持汇付进件通过后更换结算银行卡

- 用户端及后台管理端新增更换结算卡接口与页面交互
- 服务层实现换卡核心逻辑、参数校验及汇付接口调用
- 增加换卡异步通知处理与定时轮询补偿以同步审核状态
- 完善进件状态网关,补充换卡审核状态及驳回原因等字段
这个提交包含在:
可乐加冰
2026-11-15 00:52:59 +08:00
父节点 c264a7d30c
当前提交 abc40ef2a9
共有 19 个文件被更改,包括 1058 次插入 和 59 次删除

查看文件

@@ -142,6 +142,97 @@ class UserWalletApply extends Base
$this->success($msg, $this->resolveWalletOnboard($userId)); $this->success($msg, $this->resolveWalletOnboard($userId));
} }
/**
* 进件已通过后更换结算银行卡
*/
public function onboardModifyCard()
{
if (!class_exists('\\addons\\shopro_huifu\\library\\onboard\\HuifuOnboardGate')
|| !\addons\shopro_huifu\library\onboard\HuifuOnboardGate::isOnboardRequired()) {
$this->error('当前无需绑定进件资料');
}
$form = $this->request->post('huifu_onboard/a', []);
if (empty($form) || !is_array($form)) {
$this->error('请填写结算银行卡信息');
}
$userId = $this->auth->id;
$bindings = $this->collectOnboardBindings($userId);
if (!$bindings) {
$this->error('您暂无可更换结算卡的角色');
}
$roleType = trim((string) $this->request->post('role_type', ''));
$roleRefId = intval($this->request->post('role_ref_id', 0));
$modifiable = [];
$pending = false;
foreach ($bindings as $binding) {
$huifu = $binding['huifu_onboard'];
if (!empty($huifu['card_modify_pending'])) {
$pending = true;
continue;
}
if (($huifu['status'] ?? '') === 'approved' && !empty($huifu['can_modify_card'])) {
$modifiable[] = $binding;
}
}
if (!$modifiable) {
$this->error($pending ? '结算卡更换审核中,请耐心等待' : '当前状态不可更换结算卡');
}
$target = null;
if ($roleType !== '') {
foreach ($modifiable as $binding) {
if ($binding['role_type'] !== $roleType) {
continue;
}
if ($roleType === 'promoter' || $roleRefId <= 0 || intval($binding['role_ref_id']) === $roleRefId) {
$target = $binding;
break;
}
}
if (!$target) {
$this->error('未找到可更换结算卡的指定角色');
}
} elseif (count($modifiable) === 1) {
$target = $modifiable[0];
} else {
$this->error('存在多个已进件角色,请指定要更换结算卡的角色');
}
try {
$service = new \addons\shopro_huifu\library\onboard\HuifuOnboardService();
$service->modifySettleCard(
$userId,
$target['role_type'],
$target['role_ref_id'],
$form
);
} catch (\InvalidArgumentException $e) {
$this->error($e->getMessage());
} catch (\RuntimeException $e) {
$msg = class_exists('\\addons\\shopro_huifu\\library\\onboard\\HuifuOnboardError')
? \addons\shopro_huifu\library\onboard\HuifuOnboardError::translate($e->getMessage())
: $e->getMessage();
$this->error($msg);
} catch (\Throwable $e) {
$msg = class_exists('\\addons\\shopro_huifu\\library\\onboard\\HuifuOnboardError')
? \addons\shopro_huifu\library\onboard\HuifuOnboardError::translate($e->getMessage())
: $e->getMessage();
$this->error($msg ?: '更换结算卡失败');
}
$result = $this->resolveWalletOnboard($userId);
$pending = !empty($result['huifu_onboard']['card_modify_pending']);
$this->success(
$pending ? '结算卡更换已提交,请耐心等待审核' : '结算卡更换成功',
$result
);
}
protected function resolveWalletOnboard($userId) protected function resolveWalletOnboard($userId)
{ {
if (!class_exists('\\addons\\shopro_huifu\\library\\onboard\\HuifuOnboardGate')) { if (!class_exists('\\addons\\shopro_huifu\\library\\onboard\\HuifuOnboardGate')) {
@@ -169,6 +260,15 @@ class UserWalletApply extends Base
$primary = $binding; $primary = $binding;
} }
} }
if (!$primary) {
foreach ($bindings as $binding) {
if (!empty($binding['huifu_onboard']['can_modify_card'])
|| !empty($binding['huifu_onboard']['card_modify_pending'])) {
$primary = $binding;
break;
}
}
}
if (!$primary) { if (!$primary) {
$primary = $bindings[0]; $primary = $bindings[0];
} }
@@ -176,11 +276,15 @@ class UserWalletApply extends Base
$huifu = $primary['huifu_onboard']; $huifu = $primary['huifu_onboard'];
$show = count($bindings) > 0; $show = count($bindings) > 0;
$canSubmitBind = false; $canSubmitBind = false;
$canModifyCardBind = false;
foreach ($bindings as $binding) { foreach ($bindings as $binding) {
$item = $binding['huifu_onboard']; $item = $binding['huifu_onboard'];
if (!empty($item['can_submit']) || !empty($item['can_save_draft'])) { if (!empty($item['can_submit']) || !empty($item['can_save_draft'])) {
$canSubmitBind = true; $canSubmitBind = true;
} }
if (!empty($item['can_modify_card'])) {
$canModifyCardBind = true;
}
} }
$config = [ $config = [
@@ -199,9 +303,21 @@ class UserWalletApply extends Base
'required' => true, 'required' => true,
'role_labels' => array_column($bindings, 'role_label'), 'role_labels' => array_column($bindings, 'role_label'),
'role_type' => $primary['role_type'], 'role_type' => $primary['role_type'],
'role_ref_id' => intval($primary['role_ref_id']),
'huifu_onboard' => $huifu, 'huifu_onboard' => $huifu,
'can_submit_bind' => $canSubmitBind, 'can_submit_bind' => $canSubmitBind,
'can_modify_card_bind' => $canModifyCardBind,
'onboard_config' => $config, 'onboard_config' => $config,
'bindings' => array_map(function ($b) {
return [
'role_type' => $b['role_type'],
'role_ref_id' => intval($b['role_ref_id']),
'role_label' => $b['role_label'],
'status' => $b['huifu_onboard']['status'] ?? null,
'can_modify_card' => !empty($b['huifu_onboard']['can_modify_card']),
'card_modify_pending' => !empty($b['huifu_onboard']['card_modify_pending']),
];
}, $bindings),
]; ];
} }

查看文件

@@ -187,4 +187,40 @@ class Onboard extends Base
} }
$this->success('已重新提交进件'); $this->success('已重新提交进件');
} }
/**
* 已通过进件更换结算卡(后台代操作)
*/
public function modifyCard($ids = null)
{
$row = OnboardModel::get($ids);
if (!$row) {
$this->error('记录不存在');
}
if ($row['onboard_status'] !== OnboardModel::STATUS_APPROVED) {
$this->error('仅进件已通过时可更换结算卡');
}
$form = $this->request->post('row/a', []);
if (!$form) {
$form = $this->request->post('huifu_onboard/a', []);
}
if (!$form || !is_array($form)) {
$this->error('请填写结算银行卡信息');
}
try {
$service = new HuifuOnboardService();
$service->modifySettleCard(
$row['user_id'],
$row['role_type'],
$row['role_ref_id'],
$form,
intval($row['id'])
);
} catch (\think\exception\HttpResponseException $e) {
throw $e;
} catch (\Throwable $e) {
$this->error(HuifuOnboardError::translate($e->getMessage()) ?: '更换结算卡失败');
}
$this->success('结算卡更换已提交');
}
} }

查看文件

@@ -139,6 +139,8 @@
<el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button> <el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button>
<el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button> <el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button>
<el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button> <el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button>
<el-button type="primary" plain v-if="detailData && detailData.can_modify_card" @click="modifyCardFromDetail">更换结算卡</el-button>
<el-tag size="mini" type="warning" v-if="detailData && detailData.card_modify_pending" style="margin-left:8px;">换卡审核中</el-tag>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>

查看文件

@@ -19,6 +19,8 @@ class OnboardPoll extends Command
{ {
$service = new HuifuOnboardService(); $service = new HuifuOnboardService();
$count = $service->pollPending(100); $count = $service->pollPending(100);
$cardCount = $service->pollCardModifyPending(100);
$output->writeln('Polled onboard records: ' . $count); $output->writeln('Polled onboard records: ' . $count);
$output->writeln('Polled card-modify records: ' . $cardCount);
} }
} }

查看文件

@@ -33,6 +33,7 @@ return [
['type' => 'file', 'name' => 'shopro_huifu/onboard/poll', 'title' => '刷新状态', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/poll', 'title' => '刷新状态', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/submit', 'title' => '提交汇付', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/submit', 'title' => '提交汇付', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/retry', 'title' => '重新提交', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/retry', 'title' => '重新提交', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/modifyCard', 'title' => '更换结算卡', 'ismenu' => 0],
], ],
], ],
]; ];

查看文件

@@ -119,7 +119,42 @@ class Onboard extends Controller
try { try {
$service = new HuifuOnboardService(); $service = new HuifuOnboardService();
$row = $service->resubmit($userId, $roleType, $roleRefId, $form); $row = $service->resubmit($userId, $roleType, $roleRefId, $form);
$this->success('进件已重新提交', HuifuOnboardGate::publicStatus($row)); $this->success('进件已重新提交', HuifuOnboardGate::publicStatus($row, $userId, $roleType, $roleRefId));
} catch (\Throwable $e) {
$this->error($e->getMessage());
}
}
/**
* 进件已通过后更换结算银行卡
*/
public function modifyCard()
{
$this->ensureEnabled();
if (!HuifuOnboardGate::isOnboardRequired()) {
$this->error('当前未开启汇付进件');
}
$userId = $this->auth->id;
$roleType = $this->request->post('role_type', 'promoter');
$roleRefId = intval($this->request->post('role_ref_id', $userId));
if ($roleType === 'promoter') {
$roleRefId = $userId;
}
$form = $this->request->post('huifu_onboard/a', []);
if (!$form) {
$form = $this->request->post('form/a', []);
}
if (!$form || !is_array($form)) {
$this->error('请填写结算银行卡信息');
}
try {
$service = new HuifuOnboardService();
$row = $service->modifySettleCard($userId, $roleType, $roleRefId, $form);
$status = HuifuOnboardGate::publicStatus($row, $userId, $roleType, $roleRefId);
$msg = !empty($status['card_modify_pending'])
? '结算卡更换已提交,请等待审核'
: '结算卡更换成功';
$this->success($msg, $status);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->error($e->getMessage()); $this->error($e->getMessage());
} }

查看文件

@@ -91,6 +91,12 @@ class HuifuOnboardAdminView
'can_submit_huifu' => ($data['onboard_status'] ?? '') === 'draft', 'can_submit_huifu' => ($data['onboard_status'] ?? '') === 'draft',
'can_retry_huifu' => in_array($data['onboard_status'] ?? '', ['rejected', 'failed'], true), 'can_retry_huifu' => in_array($data['onboard_status'] ?? '', ['rejected', 'failed'], true),
'can_poll' => !in_array($data['onboard_status'] ?? '', ['draft', 'approved'], true), 'can_poll' => !in_array($data['onboard_status'] ?? '', ['draft', 'approved'], true),
'can_modify_card' => ($data['onboard_status'] ?? '') === 'approved' && !empty($data['huifu_id'])
&& (($payload['_card_modify']['status'] ?? '') !== 'pending'),
'card_modify_pending' => ($payload['_card_modify']['status'] ?? '') === 'pending',
'card_modify_reject_reason' => (string) (($payload['_card_modify']['status'] ?? '') === 'rejected'
? ($payload['_card_modify']['reject_reason'] ?? '')
: ''),
]; ];
} }
@@ -550,6 +556,7 @@ class HuifuOnboardAdminView
return null; return null;
} }
$form = array_merge(self::defaultFormData(), $payload); $form = array_merge(self::defaultFormData(), $payload);
unset($form['_card_modify']);
foreach ([ foreach ([
'id_card_front_url', 'id_card_back_url', 'bank_card_url', 'license_url', 'id_card_front_url', 'id_card_back_url', 'bank_card_url', 'license_url',
'legal_id_front_url', 'legal_id_back_url', 'account_permit_url', 'legal_id_front_url', 'legal_id_back_url', 'account_permit_url',

查看文件

@@ -0,0 +1,114 @@
<?php
namespace addons\shopro_huifu\library\onboard;
use addons\shopro_huifu\library\SdkLoader;
/**
* 用户业务入驻修改:仅更换结算卡(不改结算/取现费率)
*/
class HuifuOnboardBusiModify
{
protected $sdk;
public function __construct(HuifuOnboardSdk $sdk = null)
{
$this->sdk = $sdk ?: new HuifuOnboardSdk();
}
public function modifyCard($huifuId, array $form, array $fileMap)
{
SdkLoader::requireRequest('V2UserBusiModifyRequest.php');
$meta = $this->sdk->newReqMeta();
$request = new \BsPaySdk\request\V2UserBusiModifyRequest();
$request->setHuifuId($huifuId);
$request->setReqSeqId($meta['req_seq_id']);
$request->setReqDate($meta['req_date']);
$cardInfo = self::buildCardInfo($form);
$fileList = self::buildFileList($form, $fileMap);
$extend = [
'card_info' => json_encode($cardInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'async_return_url' => $this->sdk->getOnboardNotifyUrl(),
];
if ($fileList) {
$extend['file_list'] = json_encode($fileList, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
$request->setExtendInfo($extend);
$result = $this->sdk->getClient()->postRequest($request);
$data = $this->sdk->getClient()->parseResponse($result);
$applyNo = (string) ($data['apply_no'] ?? ($data['apply_id'] ?? ''));
return [
'req_date' => $meta['req_date'],
'req_seq_id' => $meta['req_seq_id'],
'apply_no' => $applyNo,
'resp' => $data,
];
}
public static function buildCardInfo(array $form)
{
// 系统 bank_account_type:0=对私,1=对公
// 汇付 card_type:0=对公,1=对私法人,2=对私非法人
$bankAccountType = (string) ($form['bank_account_type'] ?? '0');
$cardType = $bankAccountType === '1' ? '0' : '1';
$accountType = $form['account_type'] ?? 'indv';
$cardName = $form['card_name'] ?? '';
if ($accountType === 'indv') {
$cardName = $form['name'] ?? $cardName;
} elseif ($bankAccountType === '0') {
$cardName = $form['legal_name'] ?? $cardName;
}
$cardInfo = [
'card_type' => $cardType,
'card_name' => $cardName,
'card_no' => $form['card_no'],
'prov_id' => $form['prov_id'],
'area_id' => HuifuOnboardAreaHelper::toHuifuCityCode($form['area_id'] ?? ''),
'mp' => $form['mp'] ?? '',
];
if (!empty($form['branch_code'])) {
$cardInfo['branch_code'] = $form['branch_code'];
}
if (!empty($form['branch_name'])) {
$cardInfo['branch_name'] = $form['branch_name'];
}
if ($cardType === '1') {
$cardInfo['cert_type'] = '00';
$cardInfo['cert_no'] = $form['cert_no'];
$cardInfo['cert_validity_type'] = $form['cert_validity_type'];
$cardInfo['cert_begin_date'] = $form['cert_begin_date'];
if (($form['cert_validity_type'] ?? '') === '0' && !empty($form['cert_end_date'])) {
$cardInfo['cert_end_date'] = $form['cert_end_date'];
}
}
return $cardInfo;
}
public static function buildFileList(array $form, array $fileMap)
{
$bankAccountType = (string) ($form['bank_account_type'] ?? '0');
$cardType = $bankAccountType === '1' ? '0' : '1';
$fileList = [];
if (!empty($fileMap['F13'])) {
$fileList[] = [
'file_type' => 'F13',
'file_id' => $fileMap['F13'],
'file_name' => 'bank_card.jpg',
];
}
if ($cardType === '0' && !empty($fileMap['F08'])) {
$fileList[] = [
'file_type' => 'F08',
'file_id' => $fileMap['F08'],
'file_name' => 'account_permit.jpg',
];
}
return $fileList;
}
}

查看文件

@@ -118,11 +118,26 @@ class HuifuOnboardGate
// 仅历史 draft 残留时提示等待(正常已改为用户提交直连汇付) // 仅历史 draft 残留时提示等待(正常已改为用户提交直连汇付)
$awaitAdmin = $required && $row && $row['onboard_status'] === Onboard::STATUS_DRAFT $awaitAdmin = $required && $row && $row['onboard_status'] === Onboard::STATUS_DRAFT
&& in_array($agentStatus, ['pending', 'needinfo', 'reject'], true); && in_array($agentStatus, ['pending', 'needinfo', 'reject'], true);
$cardModifyPending = false;
$cardModifyReject = '';
$canModifyCard = false;
if ($row && $row['onboard_status'] === Onboard::STATUS_APPROVED && $required) {
$payload = json_decode($row['payload'] ?? '', true);
$meta = is_array($payload) ? HuifuOnboardService::getCardModifyMeta($payload) : [];
$cardModifyPending = ($meta['status'] ?? '') === 'pending';
if (($meta['status'] ?? '') === 'rejected') {
$cardModifyReject = (string) ($meta['reject_reason'] ?? '');
}
$canModifyCard = !$cardModifyPending && !empty($row['huifu_id']);
}
if (!$row) { if (!$row) {
return [ return [
'required' => $required, 'required' => $required,
'can_submit' => $canSubmit, 'can_submit' => $canSubmit,
'can_save_draft' => $canSaveDraft, 'can_save_draft' => $canSaveDraft,
'can_modify_card' => false,
'card_modify_pending' => false,
'card_modify_reject_reason' => '',
'await_admin' => false, 'await_admin' => false,
'audit_first' => false, 'audit_first' => false,
'status' => null, 'status' => null,
@@ -135,12 +150,17 @@ class HuifuOnboardGate
'required' => $required, 'required' => $required,
'can_submit' => $canSubmit, 'can_submit' => $canSubmit,
'can_save_draft' => $canSaveDraft, 'can_save_draft' => $canSaveDraft,
'can_modify_card' => $canModifyCard,
'card_modify_pending' => $cardModifyPending,
'card_modify_reject_reason' => $cardModifyReject,
'await_admin' => $awaitAdmin, 'await_admin' => $awaitAdmin,
'audit_first' => false, 'audit_first' => false,
'status' => $row['onboard_status'], 'status' => $row['onboard_status'],
'huifu_id' => $row['huifu_id'] ? substr($row['huifu_id'], 0, 4) . '****' . substr($row['huifu_id'], -4) : '', 'huifu_id' => $row['huifu_id'] ? substr($row['huifu_id'], 0, 4) . '****' . substr($row['huifu_id'], -4) : '',
'reject_reason' => $row['reject_reason'] ?? '', 'reject_reason' => $row['reject_reason'] ?? '',
'apply_no' => $row['apply_no'] ?? '', 'apply_no' => $row['apply_no'] ?? '',
'role_type' => $row['role_type'] ?? $roleType,
'role_ref_id' => intval($row['role_ref_id'] ?? $roleRefId),
'draft' => HuifuOnboardAdminView::clientFormFromRow($row), 'draft' => HuifuOnboardAdminView::clientFormFromRow($row),
]; ];
} }

查看文件

@@ -13,6 +13,7 @@ class HuifuOnboardService
protected $basic; protected $basic;
protected $basicEnt; protected $basicEnt;
protected $busi; protected $busi;
protected $busiModify;
protected $query; protected $query;
protected function sdk() protected function sdk()
@@ -23,6 +24,7 @@ class HuifuOnboardService
$this->basic = new HuifuOnboardBasicIndv($this->sdk); $this->basic = new HuifuOnboardBasicIndv($this->sdk);
$this->basicEnt = new HuifuOnboardBasicEnt($this->sdk); $this->basicEnt = new HuifuOnboardBasicEnt($this->sdk);
$this->busi = new HuifuOnboardBusiOpen($this->sdk); $this->busi = new HuifuOnboardBusiOpen($this->sdk);
$this->busiModify = new HuifuOnboardBusiModify($this->sdk);
$this->query = new HuifuOnboardQuery($this->sdk); $this->query = new HuifuOnboardQuery($this->sdk);
} }
return $this->sdk; return $this->sdk;
@@ -230,23 +232,382 @@ class HuifuOnboardService
return $fileMap; return $fileMap;
} }
/**
* 已通过进件后更换结算银行卡(V2UserBusiModify,仅 card_info)
*/
public function modifySettleCard($userId, $roleType, $roleRefId, array $formData, $onboardId = 0)
{
if (!HuifuOnboardGate::isOnboardRequired()) {
throw new \RuntimeException('当前未开启汇付进件');
}
if (!in_array($roleType, ['promoter', 'regional_agent'], true)) {
throw new \InvalidArgumentException('无效角色类型');
}
$onboardId = intval($onboardId);
if ($onboardId > 0) {
$row = Onboard::get($onboardId);
if (!$row || intval($row['user_id']) !== intval($userId)) {
throw new \InvalidArgumentException('进件记录不存在');
}
} else {
$row = Onboard::findByRole($userId, $roleType, $roleRefId);
}
if (!$row || $row['onboard_status'] !== Onboard::STATUS_APPROVED) {
throw new \RuntimeException('仅进件已通过时可更换结算卡');
}
if (empty($row['huifu_id'])) {
throw new \RuntimeException('缺少汇付商户号,无法更换结算卡');
}
$payload = json_decode($row['payload'] ?? '', true);
if (!is_array($payload)) {
$payload = [];
}
$cardModify = self::getCardModifyMeta($payload);
if (($cardModify['status'] ?? '') === 'pending') {
throw new \RuntimeException('结算卡更换审核中,请稍后再试');
}
$form = HuifuOnboardValidator::validateCardModify($formData, $payload);
$oldCard = preg_replace('/\s+/', '', (string) ($payload['card_no'] ?? ''));
$newCard = preg_replace('/\s+/', '', (string) ($form['card_no'] ?? ''));
try {
$this->sdk();
$fileMap = $this->uploadCardModifyPictures($form, $payload, $oldCard !== $newCard);
$result = $this->busiModify->modifyCard($row['huifu_id'], $form, $fileMap);
$resp = is_array($result['resp'] ?? null) ? $result['resp'] : [];
$applyNo = (string) ($result['apply_no'] ?? '');
$pending = $this->isCardModifyPending($resp, $applyNo);
if ($pending) {
$payload['_card_modify'] = [
'status' => 'pending',
'apply_no' => $applyNo,
'req_seq_id' => (string) ($result['req_seq_id'] ?? ''),
'req_date' => (string) ($result['req_date'] ?? ''),
'pending_form' => $this->cardFieldsOnly($form),
'reject_reason' => '',
'updated_at' => time(),
];
$row->payload = json_encode($payload, JSON_UNESCAPED_UNICODE);
$row->resp_snapshot = json_encode($resp, JSON_UNESCAPED_UNICODE);
$row->save();
return $row;
}
if (!$this->isCardModifySyncSuccess($resp)) {
$msg = (string) ($resp['resp_desc'] ?? '更换结算卡失败');
throw new \RuntimeException(HuifuOnboardError::translate($msg));
}
return $this->commitCardModifySuccess($row, $payload, $form, $resp);
} catch (\RuntimeException $e) {
throw $e;
} catch (\Throwable $e) {
$friendly = HuifuOnboardError::translate($e->getMessage());
Log::error('[shopro_huifu] card modify fail uid=' . $userId . ' id=' . $row['id'] . ' ' . $e->getMessage());
throw new \RuntimeException($friendly, 0, $e);
}
}
public function handleNotify(array $data) public function handleNotify(array $data)
{ {
$huifuId = trim((string) ($data['huifu_id'] ?? '')); $huifuId = trim((string) ($data['huifu_id'] ?? ''));
$applyNo = trim((string) ($data['apply_no'] ?? ''));
if ($huifuId === '') { if ($huifuId === '') {
return false; return false;
} }
$query = Onboard::where('huifu_id', $huifuId);
if ($applyNo !== '') { $auditInfo = $data['audit_info'] ?? null;
$query->where('apply_no', $applyNo); if (is_string($auditInfo)) {
$auditInfo = json_decode($auditInfo, true) ?: [];
} }
$row = $query->order('id desc')->find(); if (!is_array($auditInfo)) {
$auditInfo = [];
}
$applyNo = trim((string) ($data['apply_no'] ?? ($auditInfo['apply_no'] ?? '')));
$reqSeqId = trim((string) ($data['req_seq_id'] ?? ''));
$row = Onboard::where('huifu_id', $huifuId)->order('id desc')->find();
if (!$row) { if (!$row) {
return false; return false;
} }
if ($this->tryApplyCardModifyNotify($row, $data, $auditInfo, $applyNo, $reqSeqId)) {
return true;
}
// 原进件审核:优先按 apply_no 精确匹配,避免误更新其他记录
if ($applyNo !== '') {
$byApply = Onboard::where('huifu_id', $huifuId)->where('apply_no', $applyNo)->order('id desc')->find();
if ($byApply) {
$row = $byApply;
}
}
$this->sdk(); $this->sdk();
$this->query->pollAndUpdate($row); $this->query->pollAndUpdate($row);
return true; return true;
} }
/**
* 补偿:扫描换卡 pending,查单后更新(不改 onboard_status)
*/
public function pollCardModifyPending($limit = 50)
{
$list = Onboard::where('onboard_status', Onboard::STATUS_APPROVED)
->where('payload', 'like', '%"_card_modify"%')
->limit($limit)
->select();
if (!$list || count($list) === 0) {
return 0;
}
$this->sdk();
$count = 0;
foreach ($list as $row) {
try {
$payload = json_decode($row['payload'] ?? '', true);
if (!is_array($payload)) {
continue;
}
$meta = self::getCardModifyMeta($payload);
if (($meta['status'] ?? '') !== 'pending') {
continue;
}
$applyNo = trim((string) ($meta['apply_no'] ?? ''));
if ($applyNo === '' || empty($row['huifu_id'])) {
continue;
}
$status = $this->query->queryApply($row['huifu_id'], $applyNo);
$this->applyCardModifyRemoteStatus($row, $payload, $meta, $status);
$count++;
} catch (\Throwable $e) {
Log::error('[shopro_huifu] card modify poll fail id=' . $row['id'] . ' ' . $e->getMessage());
}
}
return $count;
}
protected function uploadCardModifyPictures(array $form, array $stored, $cardChanged)
{
$fileMap = [];
$needBankPic = $cardChanged || empty($stored['bank_card_url']) || (!empty($form['bank_card_url']) && ($form['bank_card_url'] !== ($stored['bank_card_url'] ?? '')));
if ($needBankPic && !empty($form['bank_card_url'])) {
$fileMap['F13'] = $this->picture->uploadByUrl('F13', $form['bank_card_url']);
}
if ((string) ($form['bank_account_type'] ?? '0') === '1' && !empty($form['account_permit_url'])) {
$oldPermit = (string) ($stored['account_permit_url'] ?? '');
if ($form['account_permit_url'] !== $oldPermit || empty($oldPermit)) {
$fileMap['F08'] = $this->picture->uploadByUrl('F08', $form['account_permit_url']);
}
}
return $fileMap;
}
protected function isCardModifyPending(array $resp, $applyNo)
{
$applyNo = trim((string) $applyNo);
if ($applyNo === '') {
return false;
}
// 有申请单号且绑卡结果未明确成功 → 审核中
if ($this->cardBusinessSucceeded($resp)) {
return false;
}
return true;
}
protected function isCardModifySyncSuccess(array $resp)
{
$code = (string) ($resp['resp_code'] ?? '');
if (!in_array($code, ['00000000', '00', '0000'], true) && $code !== '') {
// parseResponse 成功时通常已是业务成功码;若无码但有 token_no 也视为成功
if (empty($resp['token_no']) && !$this->cardBusinessSucceeded($resp)) {
return false;
}
}
if ($this->cardBusinessFailed($resp)) {
return false;
}
return true;
}
protected function cardBusinessSucceeded(array $resp)
{
$list = $resp['resp_business'] ?? [];
if (is_string($list)) {
$list = json_decode($list, true) ?: [];
}
if (!is_array($list) || !$list) {
return false;
}
foreach ($list as $item) {
if (!is_array($item)) {
continue;
}
if ((string) ($item['type'] ?? '') === '1' && strtoupper((string) ($item['code'] ?? '')) === 'S') {
return true;
}
}
return false;
}
protected function cardBusinessFailed(array $resp)
{
$list = $resp['resp_business'] ?? [];
if (is_string($list)) {
$list = json_decode($list, true) ?: [];
}
if (!is_array($list)) {
return false;
}
foreach ($list as $item) {
if (!is_array($item)) {
continue;
}
if ((string) ($item['type'] ?? '') === '1' && strtoupper((string) ($item['code'] ?? '')) === 'F') {
return true;
}
}
return false;
}
protected function commitCardModifySuccess(Onboard $row, array $payload, array $form, array $resp = [])
{
foreach ($this->cardFieldsOnly($form) as $key => $val) {
$payload[$key] = $val;
}
unset($payload['_card_modify']);
$row->payload = json_encode($payload, JSON_UNESCAPED_UNICODE);
if ($resp) {
$row->resp_snapshot = json_encode($resp, JSON_UNESCAPED_UNICODE);
}
// 保持 approved,不改 apply_no
$row->save();
return $row;
}
protected function cardFieldsOnly(array $form)
{
$keys = [
'bank_account_type', 'card_no', 'card_name', 'branch_code', 'branch_name',
'prov_id', 'area_id', 'district_id', 'shopro_province_id', 'shopro_city_id', 'shopro_district_id',
'mp', 'mobile_no', 'legal_mobile', 'bank_card_url', 'account_permit_url',
// normalize 可能同步的证件字段(对私)
'cert_no', 'cert_validity_type', 'cert_begin_date', 'cert_end_date', 'name',
];
$out = [];
foreach ($keys as $key) {
if (array_key_exists($key, $form)) {
$out[$key] = $form[$key];
}
}
return $out;
}
protected function tryApplyCardModifyNotify(Onboard $row, array $data, array $auditInfo, $applyNo, $reqSeqId)
{
$payload = json_decode($row['payload'] ?? '', true);
if (!is_array($payload)) {
return false;
}
$meta = self::getCardModifyMeta($payload);
if (($meta['status'] ?? '') !== 'pending') {
return false;
}
$metaApply = trim((string) ($meta['apply_no'] ?? ''));
$metaReq = trim((string) ($meta['req_seq_id'] ?? ''));
$matched = false;
if ($applyNo !== '' && $metaApply !== '' && $applyNo === $metaApply) {
$matched = true;
} elseif ($reqSeqId !== '' && $metaReq !== '' && $reqSeqId === $metaReq) {
$matched = true;
}
if (!$matched) {
return false;
}
$auditStatus = strtoupper(trim((string) (
$auditInfo['audit_status']
?? $data['audit_status']
?? $data['apply_status']
?? ''
)));
$auditDesc = (string) (
$auditInfo['audit_desc']
?? $data['audit_desc']
?? $data['sub_resp_desc']
?? $data['resp_desc']
?? ''
);
if (in_array($auditStatus, ['Y', 'S', 'SUCCESS', 'APPROVED', '1', '02', '2'], true)) {
$pendingForm = is_array($meta['pending_form'] ?? null) ? $meta['pending_form'] : [];
$this->commitCardModifySuccess($row, $payload, $pendingForm, $data);
return true;
}
if (in_array($auditStatus, ['N', 'F', 'FAIL', 'FAILED', 'REJECTED', '3', '03', '-1'], true)) {
$payload['_card_modify'] = [
'status' => 'rejected',
'apply_no' => $metaApply,
'req_seq_id' => $metaReq,
'pending_form' => $meta['pending_form'] ?? [],
'reject_reason' => $auditDesc ?: '结算卡更换被驳回',
'updated_at' => time(),
];
$row->payload = json_encode($payload, JSON_UNESCAPED_UNICODE);
$row->resp_snapshot = json_encode($data, JSON_UNESCAPED_UNICODE);
$row->save();
return true;
}
// 审核中:保持 pending
$payload['_card_modify']['updated_at'] = time();
$row->payload = json_encode($payload, JSON_UNESCAPED_UNICODE);
$row->resp_snapshot = json_encode($data, JSON_UNESCAPED_UNICODE);
$row->save();
return true;
}
protected function applyCardModifyRemoteStatus(Onboard $row, array $payload, array $meta, array $status)
{
if (!$status) {
return;
}
$stat = strtoupper(trim((string) (
$status['audit_status']
?? $status['apply_status']
?? $status['status']
?? ''
)));
$desc = (string) ($status['audit_desc'] ?? $status['apply_desc'] ?? $status['resp_desc'] ?? '');
if (in_array($stat, ['Y', 'S', 'SUCCESS', 'APPROVED', '1', '02', '2'], true)) {
$pendingForm = is_array($meta['pending_form'] ?? null) ? $meta['pending_form'] : [];
$this->commitCardModifySuccess($row, $payload, $pendingForm, $status);
return;
}
if (in_array($stat, ['N', 'F', 'FAIL', 'FAILED', 'REJECTED', '3', '03', '-1'], true)) {
$payload['_card_modify'] = [
'status' => 'rejected',
'apply_no' => (string) ($meta['apply_no'] ?? ''),
'req_seq_id' => (string) ($meta['req_seq_id'] ?? ''),
'pending_form' => $meta['pending_form'] ?? [],
'reject_reason' => $desc ?: '结算卡更换被驳回',
'updated_at' => time(),
];
$row->payload = json_encode($payload, JSON_UNESCAPED_UNICODE);
$row->resp_snapshot = json_encode($status, JSON_UNESCAPED_UNICODE);
$row->save();
}
}
public static function getCardModifyMeta(array $payload)
{
$meta = $payload['_card_modify'] ?? null;
return is_array($meta) ? $meta : [];
}
} }

查看文件

@@ -43,6 +43,54 @@ class HuifuOnboardValidator
return self::normalize($data); return self::normalize($data);
} }
/**
* 已通过进件后仅更换结算卡:银行卡字段来自 patch,身份/执照沿用库内 stored
*/
public static function validateCardModify(array $patch, array $stored)
{
if (!$stored || !is_array($stored)) {
throw new \InvalidArgumentException('缺少原进件资料,无法更换结算卡');
}
$merged = $stored;
$bankKeys = [
'bank_account_type', 'card_no', 'card_name', 'branch_code', 'branch_name',
'prov_id', 'area_id', 'district_id', 'shopro_province_id', 'shopro_city_id', 'shopro_district_id',
'mp', 'mobile_no', 'legal_mobile', 'bank_card_url', 'account_permit_url',
];
foreach ($bankKeys as $key) {
if (!array_key_exists($key, $patch)) {
continue;
}
$val = $patch[$key];
if ($val === null || $val === '') {
continue;
}
$merged[$key] = $val;
}
$oldCard = preg_replace('/\s+/', '', (string) ($stored['card_no'] ?? ''));
$newCardRaw = preg_replace('/\s+/', '', (string) ($merged['card_no'] ?? ''));
// 前端若回显脱敏卡号且用户未改,沿用库内原卡号
if ($newCardRaw !== '' && strpos($newCardRaw, '*') !== false) {
$merged['card_no'] = $oldCard;
$newCardRaw = $oldCard;
}
HuifuOnboardAreaHelper::applyRegionCodes($merged);
self::resolveBranch($merged);
self::validateBank($merged);
if ($newCardRaw !== $oldCard && empty($merged['bank_card_url'])) {
throw new \InvalidArgumentException('更换银行卡号时请重新上传银行卡照片');
}
if ((string) ($merged['bank_account_type'] ?? '0') === '1' && empty($merged['account_permit_url'])) {
throw new \InvalidArgumentException('对公账户请上传开户许可证');
}
return self::normalize($merged);
}
protected static function validateIndividual(array $data) protected static function validateIndividual(array $data)
{ {
foreach (['name', 'cert_no', 'mobile_no', 'reg_detail', 'id_card_front_url', 'id_card_back_url'] as $field) { foreach (['name', 'cert_no', 'mobile_no', 'reg_detail', 'id_card_front_url', 'id_card_back_url'] as $field) {

查看文件

@@ -508,6 +508,47 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.retryRow(this.detailId); this.retryRow(this.detailId);
} }
}, },
modifyCardFromDetail: function () {
var vm = this;
if (!vm.detailId || !vm.detailData || !vm.detailData.form) {
return;
}
var form = vm.detailData.form;
Layer.prompt({
title: '输入新结算银行卡号(支行等信息沿用当前资料,更换卡号请同步在资料中更新支行/卡图)',
value: form.card_no || '',
formType: 0,
maxlength: 32
}, function (cardNo, index) {
Layer.close(index);
cardNo = (cardNo || '').replace(/\s+/g, '');
if (!/^\d{12,19}$/.test(cardNo)) {
Toastr.error('银行卡号格式不正确');
return;
}
Fast.api.ajax({
url: 'shopro_huifu/onboard/modifyCard/ids/' + vm.detailId,
type: 'POST',
data: {
row: {
card_no: cardNo,
bank_account_type: form.bank_account_type || '0',
branch_code: form.branch_code || '',
branch_name: form.branch_name || '',
prov_id: form.prov_id || '',
area_id: form.area_id || '',
mp: form.mobile_no || '',
mobile_no: form.mobile_no || '',
bank_card_url: form.bank_card_url || '',
account_permit_url: form.account_permit_url || ''
}
}
}, function () {
vm.openDetail(vm.detailId);
vm.getList();
});
});
},
editFromDetail: function () { editFromDetail: function () {
if (this.detailId) { if (this.detailId) {
this.openEdit(this.detailId); this.openEdit(this.detailId);

查看文件

@@ -189,4 +189,40 @@ class Onboard extends Base
} }
$this->success('已重新提交进件'); $this->success('已重新提交进件');
} }
/**
* 已通过进件更换结算卡(后台代操作)
*/
public function modifyCard($ids = null)
{
$row = OnboardModel::get($ids);
if (!$row) {
$this->error('记录不存在');
}
if ($row['onboard_status'] !== OnboardModel::STATUS_APPROVED) {
$this->error('仅进件已通过时可更换结算卡');
}
$form = $this->request->post('row/a', []);
if (!$form) {
$form = $this->request->post('huifu_onboard/a', []);
}
if (!$form || !is_array($form)) {
$this->error('请填写结算银行卡信息');
}
try {
$service = new HuifuOnboardService();
$service->modifySettleCard(
$row['user_id'],
$row['role_type'],
$row['role_ref_id'],
$form,
intval($row['id'])
);
} catch (\think\exception\HttpResponseException $e) {
throw $e;
} catch (\Throwable $e) {
$this->error(HuifuOnboardError::translate($e->getMessage()) ?: '更换结算卡失败');
}
$this->success('结算卡更换已提交');
}
} }

查看文件

@@ -139,6 +139,8 @@
<el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button> <el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button>
<el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button> <el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button>
<el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button> <el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button>
<el-button type="primary" plain v-if="detailData && detailData.can_modify_card" @click="modifyCardFromDetail">更换结算卡</el-button>
<el-tag size="mini" type="warning" v-if="detailData && detailData.card_modify_pending" style="margin-left:8px;">换卡审核中</el-tag>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>

查看文件

@@ -508,6 +508,47 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.retryRow(this.detailId); this.retryRow(this.detailId);
} }
}, },
modifyCardFromDetail: function () {
var vm = this;
if (!vm.detailId || !vm.detailData || !vm.detailData.form) {
return;
}
var form = vm.detailData.form;
Layer.prompt({
title: '输入新结算银行卡号(支行等信息沿用当前资料,更换卡号请同步在资料中更新支行/卡图)',
value: form.card_no || '',
formType: 0,
maxlength: 32
}, function (cardNo, index) {
Layer.close(index);
cardNo = (cardNo || '').replace(/\s+/g, '');
if (!/^\d{12,19}$/.test(cardNo)) {
Toastr.error('银行卡号格式不正确');
return;
}
Fast.api.ajax({
url: 'shopro_huifu/onboard/modifyCard/ids/' + vm.detailId,
type: 'POST',
data: {
row: {
card_no: cardNo,
bank_account_type: form.bank_account_type || '0',
branch_code: form.branch_code || '',
branch_name: form.branch_name || '',
prov_id: form.prov_id || '',
area_id: form.area_id || '',
mp: form.mobile_no || '',
mobile_no: form.mobile_no || '',
bank_card_url: form.bank_card_url || '',
account_permit_url: form.account_permit_url || ''
}
}
}, function () {
vm.openDetail(vm.detailId);
vm.getList();
});
});
},
editFromDetail: function () { editFromDetail: function () {
if (this.detailId) { if (this.detailId) {
this.openEdit(this.detailId); this.openEdit(this.detailId);

查看文件

@@ -6,7 +6,7 @@
<view class="huifu-badge">汇付</view> <view class="huifu-badge">汇付</view>
<view class="huifu-head-text"> <view class="huifu-head-text">
<view class="section-title">绑定银行卡</view> <view class="section-title">绑定银行卡</view>
<view class="tip">填写开户资料,先由平台审核,通过后再提交汇付</view> <view class="tip">{{ formTipText }}</view>
</view> </view>
</view> </view>
<view class="status-pill" v-if="statusInfo && statusInfo.status" :class="'is-' + statusInfo.status"> <view class="status-pill" v-if="statusInfo && statusInfo.status" :class="'is-' + statusInfo.status">
@@ -21,7 +21,7 @@
class="type-item" class="type-item"
v-for="item in accountTypeOptions" v-for="item in accountTypeOptions"
:key="item.value" :key="item.value"
:class="{ active: form.account_type === item.value, disabled: readonly }" :class="{ active: form.account_type === item.value, disabled: identityReadonly }"
@tap="setAccountType(item.value)" @tap="setAccountType(item.value)"
> >
<text class="type-check">{{ form.account_type === item.value ? '✓' : '' }}</text> <text class="type-check">{{ form.account_type === item.value ? '✓' : '' }}</text>
@@ -36,19 +36,19 @@
<view class="field" id="field-name" :class="{ 'is-error': fieldErrors.name }"> <view class="field" id="field-name" :class="{ 'is-error': fieldErrors.name }">
<text class="field-label required">真实姓名</text> <text class="field-label required">真实姓名</text>
<text class="field-hint">银行卡绑定的用户姓名</text> <text class="field-hint">银行卡绑定的用户姓名</text>
<input class="field-input" v-model="form.name" placeholder="请输入真实姓名" placeholder-class="ph" :disabled="readonly" @input="clearError('name')" /> <input class="field-input" v-model="form.name" placeholder="请输入真实姓名" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('name')" />
<text class="field-error" v-if="fieldErrors.name">{{ fieldErrors.name }}</text> <text class="field-error" v-if="fieldErrors.name">{{ fieldErrors.name }}</text>
</view> </view>
<view class="field" id="field-cert_no" :class="{ 'is-error': fieldErrors.cert_no }"> <view class="field" id="field-cert_no" :class="{ 'is-error': fieldErrors.cert_no }">
<text class="field-label required">身份证号</text> <text class="field-label required">身份证号</text>
<text class="field-hint">18 位二代身份证号码</text> <text class="field-hint">18 位二代身份证号码</text>
<input class="field-input" v-model="form.cert_no" maxlength="18" placeholder="请输入身份证号" placeholder-class="ph" :disabled="readonly" @input="clearError('cert_no')" /> <input class="field-input" v-model="form.cert_no" maxlength="18" placeholder="请输入身份证号" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('cert_no')" />
<text class="field-error" v-if="fieldErrors.cert_no">{{ fieldErrors.cert_no }}</text> <text class="field-error" v-if="fieldErrors.cert_no">{{ fieldErrors.cert_no }}</text>
</view> </view>
<view class="field" id="field-cert_end_date" :class="{ 'is-error': fieldErrors.cert_end_date }"> <view class="field" id="field-cert_end_date" :class="{ 'is-error': fieldErrors.cert_end_date }">
<text class="field-label required">身份证有效期至</text> <text class="field-label required">身份证有效期至</text>
<text class="field-hint">长期有效请选择 2099-12-31</text> <text class="field-hint">长期有效请选择 2099-12-31</text>
<picker mode="date" :value="certEndPickerValue" @change="onCertEndChange" :disabled="readonly"> <picker mode="date" :value="certEndPickerValue" @change="onCertEndChange" :disabled="identityReadonly">
<view class="field-input is-picker"> <view class="field-input is-picker">
<text :class="{ ph: !form.cert_end_date }">{{ certEndDisplay || '请选择日期' }}</text> <text :class="{ ph: !form.cert_end_date }">{{ certEndDisplay || '请选择日期' }}</text>
<text class="arrow">›</text> <text class="arrow">›</text>
@@ -66,18 +66,18 @@
</view> </view>
<view class="field" id="field-reg_detail" :class="{ 'is-error': fieldErrors.reg_detail }"> <view class="field" id="field-reg_detail" :class="{ 'is-error': fieldErrors.reg_detail }">
<text class="field-label required">详细地址</text> <text class="field-label required">详细地址</text>
<input class="field-input" v-model="form.reg_detail" placeholder="请输入详细居住地址" placeholder-class="ph" :disabled="readonly" @input="clearError('reg_detail')" /> <input class="field-input" v-model="form.reg_detail" placeholder="请输入详细居住地址" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('reg_detail')" />
<text class="field-error" v-if="fieldErrors.reg_detail">{{ fieldErrors.reg_detail }}</text> <text class="field-error" v-if="fieldErrors.reg_detail">{{ fieldErrors.reg_detail }}</text>
</view> </view>
<view class="upload-grid"> <view class="upload-grid">
<view class="upload-cell" id="field-id_card_front_url" :class="{ 'is-error': fieldErrors.id_card_front_url }"> <view class="upload-cell" id="field-id_card_front_url" :class="{ 'is-error': fieldErrors.id_card_front_url }">
<text class="upload-label required">身份证人像面</text> <text class="upload-label required">身份证人像面</text>
<u-upload :file-list="frontList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onFrontSuccess" @on-uploaded="onFrontUploaded" @on-error="onUploadError('id_card_front_url', '身份证人像面')" @on-remove="onFrontRemoved"></u-upload> <u-upload :file-list="frontList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!identityReadonly" :disabled="identityReadonly" @on-success="onFrontSuccess" @on-uploaded="onFrontUploaded" @on-error="onUploadError('id_card_front_url', '身份证人像面')" @on-remove="onFrontRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.id_card_front_url">{{ fieldErrors.id_card_front_url }}</text> <text class="field-error" v-if="fieldErrors.id_card_front_url">{{ fieldErrors.id_card_front_url }}</text>
</view> </view>
<view class="upload-cell" id="field-id_card_back_url" :class="{ 'is-error': fieldErrors.id_card_back_url }"> <view class="upload-cell" id="field-id_card_back_url" :class="{ 'is-error': fieldErrors.id_card_back_url }">
<text class="upload-label required">身份证国徽面</text> <text class="upload-label required">身份证国徽面</text>
<u-upload :file-list="backList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onBackSuccess" @on-uploaded="onBackUploaded" @on-error="onUploadError('id_card_back_url', '身份证国徽面')" @on-remove="onBackRemoved"></u-upload> <u-upload :file-list="backList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!identityReadonly" :disabled="identityReadonly" @on-success="onBackSuccess" @on-uploaded="onBackUploaded" @on-error="onUploadError('id_card_back_url', '身份证国徽面')" @on-remove="onBackRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.id_card_back_url">{{ fieldErrors.id_card_back_url }}</text> <text class="field-error" v-if="fieldErrors.id_card_back_url">{{ fieldErrors.id_card_back_url }}</text>
</view> </view>
</view> </view>
@@ -89,18 +89,18 @@
<view class="field" id="field-reg_name" :class="{ 'is-error': fieldErrors.reg_name }"> <view class="field" id="field-reg_name" :class="{ 'is-error': fieldErrors.reg_name }">
<text class="field-label required">商户名称</text> <text class="field-label required">商户名称</text>
<text class="field-hint">请输入营业执照上的名称</text> <text class="field-hint">请输入营业执照上的名称</text>
<input class="field-input" v-model="form.reg_name" placeholder="请输入商户名称" placeholder-class="ph" :disabled="readonly" @input="clearError('reg_name')" /> <input class="field-input" v-model="form.reg_name" placeholder="请输入商户名称" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('reg_name')" />
<text class="field-error" v-if="fieldErrors.reg_name">{{ fieldErrors.reg_name }}</text> <text class="field-error" v-if="fieldErrors.reg_name">{{ fieldErrors.reg_name }}</text>
</view> </view>
<view class="field" id="field-license_code" :class="{ 'is-error': fieldErrors.license_code }"> <view class="field" id="field-license_code" :class="{ 'is-error': fieldErrors.license_code }">
<text class="field-label required">统一社会信用代码</text> <text class="field-label required">统一社会信用代码</text>
<input class="field-input" v-model="form.license_code" maxlength="18" placeholder="请输入统一社会信用代码" placeholder-class="ph" :disabled="readonly" @input="clearError('license_code')" /> <input class="field-input" v-model="form.license_code" maxlength="18" placeholder="请输入统一社会信用代码" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('license_code')" />
<text class="field-error" v-if="fieldErrors.license_code">{{ fieldErrors.license_code }}</text> <text class="field-error" v-if="fieldErrors.license_code">{{ fieldErrors.license_code }}</text>
</view> </view>
<view class="field" id="field-license_end_date" :class="{ 'is-error': fieldErrors.license_end_date }"> <view class="field" id="field-license_end_date" :class="{ 'is-error': fieldErrors.license_end_date }">
<text class="field-label required">统一社会信用代码有效期</text> <text class="field-label required">统一社会信用代码有效期</text>
<text class="field-hint">长期有效请选择 2099-12-31</text> <text class="field-hint">长期有效请选择 2099-12-31</text>
<picker mode="date" :value="licenseEndPickerValue" @change="onLicenseEndChange" :disabled="readonly"> <picker mode="date" :value="licenseEndPickerValue" @change="onLicenseEndChange" :disabled="identityReadonly">
<view class="field-input is-picker"> <view class="field-input is-picker">
<text :class="{ ph: !form.license_end_date }">{{ licenseEndDisplay || '请选择日期' }}</text> <text :class="{ ph: !form.license_end_date }">{{ licenseEndDisplay || '请选择日期' }}</text>
<text class="arrow">›</text> <text class="arrow">›</text>
@@ -118,27 +118,27 @@
</view> </view>
<view class="field" id="field-ent-reg_detail" :class="{ 'is-error': fieldErrors.reg_detail }"> <view class="field" id="field-ent-reg_detail" :class="{ 'is-error': fieldErrors.reg_detail }">
<text class="field-label required">企业地址</text> <text class="field-label required">企业地址</text>
<input class="field-input" v-model="form.reg_detail" placeholder="请输入企业经营地址" placeholder-class="ph" :disabled="readonly" @input="clearError('reg_detail')" /> <input class="field-input" v-model="form.reg_detail" placeholder="请输入企业经营地址" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('reg_detail')" />
<text class="field-error" v-if="fieldErrors.reg_detail">{{ fieldErrors.reg_detail }}</text> <text class="field-error" v-if="fieldErrors.reg_detail">{{ fieldErrors.reg_detail }}</text>
</view> </view>
<view class="field" id="field-legal_name" :class="{ 'is-error': fieldErrors.legal_name }"> <view class="field" id="field-legal_name" :class="{ 'is-error': fieldErrors.legal_name }">
<text class="field-label required">法人姓名</text> <text class="field-label required">法人姓名</text>
<input class="field-input" v-model="form.legal_name" placeholder="请输入法人姓名" placeholder-class="ph" :disabled="readonly" @input="clearError('legal_name')" /> <input class="field-input" v-model="form.legal_name" placeholder="请输入法人姓名" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('legal_name')" />
<text class="field-error" v-if="fieldErrors.legal_name">{{ fieldErrors.legal_name }}</text> <text class="field-error" v-if="fieldErrors.legal_name">{{ fieldErrors.legal_name }}</text>
</view> </view>
<view class="field" id="field-legal_mobile" :class="{ 'is-error': fieldErrors.legal_mobile }"> <view class="field" id="field-legal_mobile" :class="{ 'is-error': fieldErrors.legal_mobile }">
<text class="field-label required">法人手机号</text> <text class="field-label required">法人手机号</text>
<input class="field-input" v-model="form.legal_mobile" type="number" maxlength="11" placeholder="请输入法人手机号" placeholder-class="ph" :disabled="readonly" @input="clearError('legal_mobile')" /> <input class="field-input" v-model="form.legal_mobile" type="number" maxlength="11" placeholder="请输入法人手机号" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('legal_mobile')" />
<text class="field-error" v-if="fieldErrors.legal_mobile">{{ fieldErrors.legal_mobile }}</text> <text class="field-error" v-if="fieldErrors.legal_mobile">{{ fieldErrors.legal_mobile }}</text>
</view> </view>
<view class="field" id="field-legal_cert_no" :class="{ 'is-error': fieldErrors.legal_cert_no }"> <view class="field" id="field-legal_cert_no" :class="{ 'is-error': fieldErrors.legal_cert_no }">
<text class="field-label required">法人身份证号</text> <text class="field-label required">法人身份证号</text>
<input class="field-input" v-model="form.legal_cert_no" maxlength="18" placeholder="请输入法人身份证号" placeholder-class="ph" :disabled="readonly" @input="clearError('legal_cert_no')" /> <input class="field-input" v-model="form.legal_cert_no" maxlength="18" placeholder="请输入法人身份证号" placeholder-class="ph" :disabled="identityReadonly" @input="clearError('legal_cert_no')" />
<text class="field-error" v-if="fieldErrors.legal_cert_no">{{ fieldErrors.legal_cert_no }}</text> <text class="field-error" v-if="fieldErrors.legal_cert_no">{{ fieldErrors.legal_cert_no }}</text>
</view> </view>
<view class="field" id="field-legal_cert_end_date" :class="{ 'is-error': fieldErrors.legal_cert_end_date }"> <view class="field" id="field-legal_cert_end_date" :class="{ 'is-error': fieldErrors.legal_cert_end_date }">
<text class="field-label required">法人身份证有效期</text> <text class="field-label required">法人身份证有效期</text>
<picker mode="date" :value="legalCertEndPickerValue" @change="onLegalCertEndChange" :disabled="readonly"> <picker mode="date" :value="legalCertEndPickerValue" @change="onLegalCertEndChange" :disabled="identityReadonly">
<view class="field-input is-picker"> <view class="field-input is-picker">
<text :class="{ ph: !form.legal_cert_end_date }">{{ legalCertEndDisplay || '请选择日期' }}</text> <text :class="{ ph: !form.legal_cert_end_date }">{{ legalCertEndDisplay || '请选择日期' }}</text>
<text class="arrow">›</text> <text class="arrow">›</text>
@@ -149,17 +149,17 @@
<view class="upload-grid"> <view class="upload-grid">
<view class="upload-cell" id="field-license_url" :class="{ 'is-error': fieldErrors.license_url }"> <view class="upload-cell" id="field-license_url" :class="{ 'is-error': fieldErrors.license_url }">
<text class="upload-label required">营业执照照片</text> <text class="upload-label required">营业执照照片</text>
<u-upload :file-list="licenseList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onLicenseSuccess" @on-uploaded="onLicenseUploaded" @on-remove="onLicenseRemoved"></u-upload> <u-upload :file-list="licenseList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!identityReadonly" :disabled="identityReadonly" @on-success="onLicenseSuccess" @on-uploaded="onLicenseUploaded" @on-remove="onLicenseRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.license_url">{{ fieldErrors.license_url }}</text> <text class="field-error" v-if="fieldErrors.license_url">{{ fieldErrors.license_url }}</text>
</view> </view>
<view class="upload-cell" id="field-legal_id_front_url" :class="{ 'is-error': fieldErrors.legal_id_front_url }"> <view class="upload-cell" id="field-legal_id_front_url" :class="{ 'is-error': fieldErrors.legal_id_front_url }">
<text class="upload-label required">法人身份证人像面</text> <text class="upload-label required">法人身份证人像面</text>
<u-upload :file-list="legalFrontList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onLegalFrontSuccess" @on-uploaded="onLegalFrontUploaded" @on-remove="onLegalFrontRemoved"></u-upload> <u-upload :file-list="legalFrontList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!identityReadonly" :disabled="identityReadonly" @on-success="onLegalFrontSuccess" @on-uploaded="onLegalFrontUploaded" @on-remove="onLegalFrontRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.legal_id_front_url">{{ fieldErrors.legal_id_front_url }}</text> <text class="field-error" v-if="fieldErrors.legal_id_front_url">{{ fieldErrors.legal_id_front_url }}</text>
</view> </view>
<view class="upload-cell" id="field-legal_id_back_url" :class="{ 'is-error': fieldErrors.legal_id_back_url }"> <view class="upload-cell" id="field-legal_id_back_url" :class="{ 'is-error': fieldErrors.legal_id_back_url }">
<text class="upload-label required">法人身份证国徽面</text> <text class="upload-label required">法人身份证国徽面</text>
<u-upload :file-list="legalBackList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onLegalBackSuccess" @on-uploaded="onLegalBackUploaded" @on-remove="onLegalBackRemoved"></u-upload> <u-upload :file-list="legalBackList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!identityReadonly" :disabled="identityReadonly" @on-success="onLegalBackSuccess" @on-uploaded="onLegalBackUploaded" @on-remove="onLegalBackRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.legal_id_back_url">{{ fieldErrors.legal_id_back_url }}</text> <text class="field-error" v-if="fieldErrors.legal_id_back_url">{{ fieldErrors.legal_id_back_url }}</text>
</view> </view>
</view> </view>
@@ -173,7 +173,7 @@
class="type-item small" class="type-item small"
v-for="item in bankAccountTypeOptions" v-for="item in bankAccountTypeOptions"
:key="item.value" :key="item.value"
:class="{ active: form.bank_account_type === item.value, disabled: readonly }" :class="{ active: form.bank_account_type === item.value, disabled: bankReadonly }"
@tap="setBankAccountType(item.value)" @tap="setBankAccountType(item.value)"
> >
<text class="type-check">{{ form.bank_account_type === item.value ? '✓' : '' }}</text> <text class="type-check">{{ form.bank_account_type === item.value ? '✓' : '' }}</text>
@@ -183,14 +183,14 @@
<view class="field" id="field-card_no" :class="{ 'is-error': fieldErrors.card_no }"> <view class="field" id="field-card_no" :class="{ 'is-error': fieldErrors.card_no }">
<text class="field-label required">结算账户银行卡号</text> <text class="field-label required">结算账户银行卡号</text>
<text class="field-hint">分成资金到账银行卡号,须为一类借记卡</text> <text class="field-hint">分成资金到账银行卡号,须为一类借记卡</text>
<input class="field-input" v-model="form.card_no" type="number" placeholder="请输入结算银行卡号" placeholder-class="ph" :disabled="readonly" @input="clearError('card_no')" /> <input class="field-input" v-model="form.card_no" type="number" placeholder="请输入结算银行卡号" placeholder-class="ph" :disabled="bankReadonly" @input="onCardNoInput" />
<text class="field-error" v-if="fieldErrors.card_no">{{ fieldErrors.card_no }}</text> <text class="field-error" v-if="fieldErrors.card_no">{{ fieldErrors.card_no }}</text>
</view> </view>
<view class="field" id="field-branch_name" :class="{ 'is-error': fieldErrors.branch_name }"> <view class="field" id="field-branch_name" :class="{ 'is-error': fieldErrors.branch_name }">
<text class="field-label required">开户银行名称</text> <text class="field-label required">开户银行名称</text>
<text class="field-hint">搜索支行名称,系统将自动匹配联行号</text> <text class="field-hint">搜索支行名称,系统将自动匹配联行号</text>
<input class="field-input" v-model="branchKeyword" placeholder="例如:农业银行贵阳总府支行" placeholder-class="ph" :disabled="readonly" @input="onBranchInput" @focus="onBranchInput" /> <input class="field-input" v-model="branchKeyword" placeholder="例如:农业银行贵阳总府支行" placeholder-class="ph" :disabled="bankReadonly" @input="onBranchInput" @focus="onBranchInput" />
<view class="branch-panel" v-if="branchResults.length && !readonly"> <view class="branch-panel" v-if="branchResults.length && !bankReadonly">
<view class="branch-item" v-for="(item, idx) in branchResults" :key="idx" @tap="selectBranch(item)"> <view class="branch-item" v-for="(item, idx) in branchResults" :key="idx" @tap="selectBranch(item)">
<text class="branch-name">{{ item.branch_name }}</text> <text class="branch-name">{{ item.branch_name }}</text>
<text class="branch-code">{{ item.branch_code }}</text> <text class="branch-code">{{ item.branch_code }}</text>
@@ -207,23 +207,24 @@
<view class="field" v-if="form.account_type === 'indv' || (form.account_type === 'ent' && form.bank_account_type === '0')" id="field-mobile_no" :class="{ 'is-error': fieldErrors.mobile_no }"> <view class="field" v-if="form.account_type === 'indv' || (form.account_type === 'ent' && form.bank_account_type === '0')" id="field-mobile_no" :class="{ 'is-error': fieldErrors.mobile_no }">
<text class="field-label required">{{ form.account_type === 'ent' ? '手机号' : '手机号' }}</text> <text class="field-label required">{{ form.account_type === 'ent' ? '手机号' : '手机号' }}</text>
<text class="field-hint">银行卡开户预留的手机号</text> <text class="field-hint">银行卡开户预留的手机号</text>
<input class="field-input" v-model="bankMobile" type="number" maxlength="11" placeholder="请输入银行预留手机号" placeholder-class="ph" :disabled="readonly" @input="clearError('mobile_no')" /> <input class="field-input" v-model="bankMobile" type="number" maxlength="11" placeholder="请输入银行预留手机号" placeholder-class="ph" :disabled="bankReadonly" @input="clearError('mobile_no')" />
<text class="field-error" v-if="fieldErrors.mobile_no">{{ fieldErrors.mobile_no }}</text> <text class="field-error" v-if="fieldErrors.mobile_no">{{ fieldErrors.mobile_no }}</text>
</view> </view>
<view class="field upload-bank"> <view class="field upload-bank">
<text class="field-label">银行卡正面照</text> <text class="field-label" :class="{ required: editMode === 'bank_only' }">银行卡正面照</text>
<text class="field-hint">建议上传,可加快审核</text> <text class="field-hint">{{ editMode === 'bank_only' ? '更换卡号时请重新上传银行卡照片' : '建议上传,可加快审核' }}</text>
<u-upload :file-list="bankList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onBankSuccess" @on-uploaded="onBankUploaded" @on-remove="onBankRemoved"></u-upload> <u-upload :file-list="bankList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!bankReadonly" :disabled="bankReadonly" @on-success="onBankSuccess" @on-uploaded="onBankUploaded" @on-remove="onBankRemoved"></u-upload>
<text class="field-error" v-if="fieldErrors.bank_card_url">{{ fieldErrors.bank_card_url }}</text>
</view> </view>
<view class="field upload-bank" v-if="form.account_type === 'ent' && form.bank_account_type === '1'"> <view class="field upload-bank" v-if="form.account_type === 'ent' && form.bank_account_type === '1'">
<text class="field-label required">开户许可证/基本户证明</text> <text class="field-label required">开户许可证/基本户证明</text>
<u-upload :file-list="permitList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!readonly" :disabled="readonly" @on-success="onPermitSuccess" @on-uploaded="onPermitUploaded" @on-remove="onPermitRemoved"></u-upload> <u-upload :file-list="permitList" :action="`${$API_URL}/index/upload`" :header="uploadHeader" maxCount="1" width="200" height="140" :deletable="!bankReadonly" :disabled="bankReadonly" @on-success="onPermitSuccess" @on-uploaded="onPermitUploaded" @on-remove="onPermitRemoved"></u-upload>
</view> </view>
</view> </view>
<view class="reject-box" v-if="statusInfo && statusInfo.reject_reason"> <view class="reject-box" v-if="statusInfo && (statusInfo.reject_reason || statusInfo.card_modify_reject_reason)">
<text class="reject-label">驳回原因</text> <text class="reject-label">{{ statusInfo.card_modify_reject_reason ? '换卡驳回原因' : '驳回原因' }}</text>
<text class="reject-text">{{ statusInfo.reject_reason }}</text> <text class="reject-text">{{ statusInfo.card_modify_reject_reason || statusInfo.reject_reason }}</text>
</view> </view>
<u-select mode="mutil-column-auto" safe-area-inset-bottom :list="addressAreaList" v-model="showRegionSelect" @confirm="onRegionConfirm"></u-select> <u-select mode="mutil-column-auto" safe-area-inset-bottom :list="addressAreaList" v-model="showRegionSelect" @confirm="onRegionConfirm"></u-select>
@@ -236,7 +237,9 @@ export default {
props: { props: {
required: { type: Boolean, default: false }, required: { type: Boolean, default: false },
statusInfo: { type: Object, default: () => ({}) }, statusInfo: { type: Object, default: () => ({}) },
readonly: { type: Boolean, default: false } readonly: { type: Boolean, default: false },
/** full=完整进件;bank_only=仅更换结算卡 */
editMode: { type: String, default: 'full' }
}, },
data() { data() {
return { return {
@@ -248,6 +251,7 @@ export default {
{ label: '对公', value: '1' }, { label: '对公', value: '1' },
{ label: '对私', value: '0' } { label: '对私', value: '0' }
], ],
originalCardNo: '',
form: { form: {
account_type: 'indv', account_type: 'indv',
bank_account_type: '0', bank_account_type: '0',
@@ -308,6 +312,18 @@ export default {
uploadHeader() { uploadHeader() {
return { token: uni.getStorageSync('token') || '' }; return { token: uni.getStorageSync('token') || '' };
}, },
identityReadonly() {
return this.readonly || this.editMode === 'bank_only';
},
bankReadonly() {
return this.readonly;
},
formTipText() {
if (this.editMode === 'bank_only') {
return '进件已通过,可修改结算银行卡信息后提交更换';
}
return '填写开户资料,先由平台审核,通过后再提交汇付';
},
bankMobile: { bankMobile: {
get() { get() {
return this.form.account_type === 'ent' ? this.form.legal_mobile : this.form.mobile_no; return this.form.account_type === 'ent' ? this.form.legal_mobile : this.form.mobile_no;
@@ -340,10 +356,49 @@ export default {
return this.displayDate(this.form.legal_cert_end_date); return this.displayDate(this.form.legal_cert_end_date);
} }
}, },
watch: {
statusInfo: {
immediate: true,
deep: true,
handler(val) {
if (val && val.draft) {
this.applyDraft(val.draft);
}
}
}
},
mounted() { mounted() {
this.loadArea(); this.loadArea();
}, },
methods: { methods: {
applyDraft(draft) {
if (!draft || typeof draft !== 'object') return;
Object.keys(this.form).forEach(key => {
if (draft[key] !== undefined && draft[key] !== null && draft[key] !== '') {
this.form[key] = draft[key];
}
});
this.originalCardNo = String(this.form.card_no || '').replace(/\s+/g, '');
if (draft.branch_name) {
this.branchKeyword = draft.branch_name;
}
const setList = (url, listKey) => {
if (url) this[listKey] = [{ url }];
};
setList(this.form.id_card_front_url, 'frontList');
setList(this.form.id_card_back_url, 'backList');
setList(this.form.license_url, 'licenseList');
setList(this.form.legal_id_front_url, 'legalFrontList');
setList(this.form.legal_id_back_url, 'legalBackList');
setList(this.form.bank_card_url, 'bankList');
setList(this.form.account_permit_url, 'permitList');
if (draft.shopro_province_id || draft.prov_id) {
// 区域文案在无完整省市区树时先用编码占位,用户可重选支行补齐
if (!this.regionText) {
this.regionText = [draft.prov_id, draft.area_id, draft.district_id].filter(Boolean).join(' / ');
}
}
},
loadArea() { loadArea() {
this.$http('address.area').then(res => { this.$http('address.area').then(res => {
if (res.code !== 1) return; if (res.code !== 1) return;
@@ -360,18 +415,18 @@ export default {
}); });
}, },
setAccountType(value) { setAccountType(value) {
if (this.readonly) return; if (this.identityReadonly) return;
this.form.account_type = value; this.form.account_type = value;
if (value === 'indv') { if (value === 'indv') {
this.form.bank_account_type = '0'; this.form.bank_account_type = '0';
} }
}, },
setBankAccountType(value) { setBankAccountType(value) {
if (this.readonly) return; if (this.bankReadonly) return;
this.form.bank_account_type = value; this.form.bank_account_type = value;
}, },
openRegionPicker() { openRegionPicker() {
if (this.readonly) return; if (this.identityReadonly) return;
if (!this.addressAreaList.length) { if (!this.addressAreaList.length) {
this.$u.toast('区域数据加载中,请稍候'); this.$u.toast('区域数据加载中,请稍候');
return; return;
@@ -414,8 +469,20 @@ export default {
this.applyValidityByEnd('legal_cert', e.detail.value); this.applyValidityByEnd('legal_cert', e.detail.value);
this.clearError('legal_cert_end_date'); this.clearError('legal_cert_end_date');
}, },
onCardNoInput() {
this.clearError('card_no');
if (this.editMode !== 'bank_only') return;
const newCard = String(this.form.card_no || '').replace(/\s+/g, '');
const oldCard = String(this.originalCardNo || '').replace(/\s+/g, '');
if (newCard && oldCard && newCard !== oldCard) {
// 卡号变更后必须重新上传银行卡图,避免沿用旧图
this.form.bank_card_url = '';
this.bankList = [];
this.clearError('bank_card_url');
}
},
onBranchInput() { onBranchInput() {
if (this.readonly) return; if (this.bankReadonly) return;
clearTimeout(this.branchTimer); clearTimeout(this.branchTimer);
const keyword = (this.branchKeyword || '').trim(); const keyword = (this.branchKeyword || '').trim();
if (keyword.length < 2) { if (keyword.length < 2) {
@@ -545,13 +612,16 @@ export default {
if (!this.required) return true; if (!this.required) return true;
this.fieldErrors = {}; this.fieldErrors = {};
const f = this.form; const f = this.form;
if (!f.prov_id || !f.area_id) return this.failField('prov_id', '请选择所在区域');
if (!f.branch_code && !(this.branchKeyword || f.branch_name)) { if (!f.branch_code && !(this.branchKeyword || f.branch_name)) {
return this.failField('branch_name', '请搜索并选择开户银行支行'); return this.failField('branch_name', '请搜索并选择开户银行支行');
} }
if (!f.branch_name && this.branchKeyword) { if (!f.branch_name && this.branchKeyword) {
f.branch_name = this.branchKeyword.trim(); f.branch_name = this.branchKeyword.trim();
} }
if (this.editMode === 'bank_only') {
return this.validateBankOnly();
}
if (!f.prov_id || !f.area_id) return this.failField('prov_id', '请选择所在区域');
if (f.account_type === 'indv') { if (f.account_type === 'indv') {
if (!f.name) return this.failField('name', '请填写真实姓名'); if (!f.name) return this.failField('name', '请填写真实姓名');
if (!f.cert_no) return this.failField('cert_no', '请填写身份证号'); if (!f.cert_no) return this.failField('cert_no', '请填写身份证号');
@@ -597,6 +667,30 @@ export default {
} }
return true; return true;
}, },
validateBankOnly() {
const f = this.form;
if (!f.card_no || !/^\d{12,19}$/.test(String(f.card_no).replace(/\s+/g, ''))) {
return this.failField('card_no', '请填写正确银行卡号');
}
if (!f.branch_code) {
return this.failField('branch_name', '请搜索并选择开户银行支行');
}
const newCard = String(f.card_no).replace(/\s+/g, '');
const oldCard = String(this.originalCardNo || '').replace(/\s+/g, '');
if (newCard !== oldCard && !f.bank_card_url) {
return this.failField('bank_card_url', '更换银行卡号时请重新上传银行卡照片');
}
if (f.account_type === 'indv' || (f.account_type === 'ent' && f.bank_account_type === '0')) {
const mobile = f.account_type === 'ent' ? f.legal_mobile : f.mobile_no;
if (!mobile || !/^1\d{10}$/.test(mobile)) {
return this.failField('mobile_no', '请填写银行预留手机号');
}
}
if (f.account_type === 'ent' && f.bank_account_type === '1' && !f.account_permit_url) {
return this.failField('account_permit_url', '对公账户请上传开户许可证');
}
return true;
},
statusText(status) { statusText(status) {
const map = { const map = {
draft: '待后台审核', draft: '待后台审核',

查看文件

@@ -13,6 +13,7 @@
:required="huifuRequired" :required="huifuRequired"
:statusInfo="huifuStatus" :statusInfo="huifuStatus"
:readonly="huifuFormReadonly" :readonly="huifuFormReadonly"
:editMode="formEditMode"
></huifu-onboard-form> ></huifu-onboard-form>
<view class="foot-box safe-area-inset-bottom" v-if="showOnboardSubmitBtn"> <view class="foot-box safe-area-inset-bottom" v-if="showOnboardSubmitBtn">
<button <button
@@ -38,6 +39,9 @@ export default {
huifuStatus: {}, huifuStatus: {},
onboardRoleLabels: [], onboardRoleLabels: [],
canSubmitOnboard: false, canSubmitOnboard: false,
canModifyCard: false,
roleType: '',
roleRefId: 0,
onboardSubmitting: false onboardSubmitting: false
}; };
}, },
@@ -46,19 +50,33 @@ export default {
if (!this.onboardRoleLabels.length) return ''; if (!this.onboardRoleLabels.length) return '';
return `适用角色:${this.onboardRoleLabels.join('、')}`; return `适用角色:${this.onboardRoleLabels.join('、')}`;
}, },
formEditMode() {
if (this.canModifyCard || (this.huifuStatus && this.huifuStatus.status === 'approved')) {
return 'bank_only';
}
return 'full';
},
huifuFormReadonly() { huifuFormReadonly() {
if (!this.huifuRequired) return true; if (!this.huifuRequired) return true;
if (this.huifuStatus && this.huifuStatus.card_modify_pending) return true;
if (this.canModifyCard) return false;
return !this.canSubmitOnboard; return !this.canSubmitOnboard;
}, },
showOnboardSubmitBtn() { showOnboardSubmitBtn() {
return this.canSubmitOnboard; return this.canSubmitOnboard || this.canModifyCard;
}, },
onboardSubmitText() { onboardSubmitText() {
if (this.canModifyCard) return '提交更换结算卡';
if (this.huifuStatus && this.huifuStatus.can_submit) return '重新提交进件资料'; if (this.huifuStatus && this.huifuStatus.can_submit) return '重新提交进件资料';
return '提交开户认证'; return '提交开户认证';
}, },
onboardNoticeText() { onboardNoticeText() {
const h = this.huifuStatus || {}; const h = this.huifuStatus || {};
if (h.card_modify_pending) return '结算卡更换审核中,请耐心等待';
if (h.card_modify_reject_reason) {
return `结算卡更换被驳回:${h.card_modify_reject_reason},请修改后重新提交`;
}
if (this.canModifyCard) return '汇付进件已通过,可更换结算银行卡';
if (h.can_submit) return '汇付进件被驳回,请修改下方资料后重新提交'; if (h.can_submit) return '汇付进件被驳回,请修改下方资料后重新提交';
if (h.await_admin) return '进件资料已提交,请等待后台审核;审核通过后将自动提交汇付'; if (h.await_admin) return '进件资料已提交,请等待后台审核;审核通过后将自动提交汇付';
if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') { if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') {
@@ -75,6 +93,15 @@ export default {
this.loadOnboardBind(); this.loadOnboardBind();
}, },
methods: { methods: {
applyOnboardData(data) {
this.huifuRequired = !!data.required;
this.huifuStatus = data.huifu_onboard || {};
this.onboardRoleLabels = Array.isArray(data.role_labels) ? data.role_labels : [];
this.canSubmitOnboard = !!data.can_submit_bind;
this.canModifyCard = !!data.can_modify_card_bind || !!(this.huifuStatus && this.huifuStatus.can_modify_card);
this.roleType = data.role_type || (this.huifuStatus.role_type || '');
this.roleRefId = parseInt(data.role_ref_id || this.huifuStatus.role_ref_id || 0, 10) || 0;
},
loadOnboardBind() { loadOnboardBind() {
this.$http('money.walletOnboard', {}, '', false).then(res => { this.$http('money.walletOnboard', {}, '', false).then(res => {
if (res.code !== 1) { if (res.code !== 1) {
@@ -88,10 +115,7 @@ export default {
setTimeout(() => this.$Router.back(), 1500); setTimeout(() => this.$Router.back(), 1500);
return; return;
} }
this.huifuRequired = !!data.required; this.applyOnboardData(data);
this.huifuStatus = data.huifu_onboard || {};
this.onboardRoleLabels = Array.isArray(data.role_labels) ? data.role_labels : [];
this.canSubmitOnboard = !!data.can_submit_bind;
this.loaded = true; this.loaded = true;
}); });
}, },
@@ -99,20 +123,22 @@ export default {
if (!this.$refs.huifuForm) return; if (!this.$refs.huifuForm) return;
const valid = this.$refs.huifuForm.validate(); const valid = this.$refs.huifuForm.validate();
if (valid !== true) return; if (valid !== true) return;
const isModifyCard = this.canModifyCard && !this.canSubmitOnboard;
const api = isModifyCard ? 'money.walletOnboardModifyCard' : 'money.walletOnboardSubmit';
const payload = {
huifu_onboard: this.$refs.huifuForm.getPayload()
};
if (isModifyCard) {
if (this.roleType) payload.role_type = this.roleType;
if (this.roleRefId) payload.role_ref_id = this.roleRefId;
}
this.onboardSubmitting = true; this.onboardSubmitting = true;
this.$http( this.$http(api, payload, '提交中...', false).then(res => {
'money.walletOnboardSubmit',
{ huifu_onboard: this.$refs.huifuForm.getPayload() },
'提交中...',
false
).then(res => {
this.onboardSubmitting = false; this.onboardSubmitting = false;
if (res.code === 1) { if (res.code === 1) {
this.$u.toast(res.msg || '提交成功'); this.$u.toast(res.msg || '提交成功');
if (res.data) { if (res.data) {
this.huifuStatus = res.data.huifu_onboard || {}; this.applyOnboardData(res.data);
this.onboardRoleLabels = Array.isArray(res.data.role_labels) ? res.data.role_labels : [];
this.canSubmitOnboard = !!res.data.can_submit_bind;
if (!res.data.show) { if (!res.data.show) {
setTimeout(() => this.$Router.back(), 1500); setTimeout(() => this.$Router.back(), 1500);
} }

查看文件

@@ -177,6 +177,9 @@
onboardEntryDesc() { onboardEntryDesc() {
if (this.canSubmitOnboard) return '请完善实名与银行卡信息'; if (this.canSubmitOnboard) return '请完善实名与银行卡信息';
const h = this.huifuStatus || {}; const h = this.huifuStatus || {};
if (h.card_modify_pending) return '结算卡更换审核中';
if (h.card_modify_reject_reason) return '结算卡更换被驳回,点击重新更换';
if (h.can_modify_card || h.status === 'approved') return '进件已通过,可查看或更换结算银行卡';
if (h.await_admin) return '资料已提交,等待平台审核'; if (h.await_admin) return '资料已提交,等待平台审核';
if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') { if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') {
return '汇付进件审核中'; return '汇付进件审核中';
@@ -193,9 +196,12 @@
submitting: '提交中', submitting: '提交中',
basic_ok: '开户成功', basic_ok: '开户成功',
busi_pending: '审核中', busi_pending: '审核中',
approved: '已通过',
rejected: '已驳回', rejected: '已驳回',
failed: '失败' failed: '失败'
}; };
if (h.card_modify_pending) return '换卡审核中';
if (h.card_modify_reject_reason) return '换卡驳回';
if (h.can_submit) return '待修改'; if (h.can_submit) return '待修改';
if (h.await_admin) return '待审核'; if (h.await_admin) return '待审核';
if (h.status && map[h.status]) return map[h.status]; if (h.status && map[h.status]) return map[h.status];
@@ -204,11 +210,12 @@
}, },
onboardStatusClass() { onboardStatusClass() {
const h = this.huifuStatus || {}; const h = this.huifuStatus || {};
if (h.can_submit || h.status === 'rejected' || h.status === 'failed') return 'is-warn'; if (h.can_submit || h.status === 'rejected' || h.status === 'failed' || h.card_modify_reject_reason) return 'is-warn';
if (h.await_admin || h.status === 'draft') return 'is-pending'; if (h.await_admin || h.status === 'draft' || h.card_modify_pending) return 'is-pending';
if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') { if (h.status === 'busi_pending' || h.status === 'submitting' || h.status === 'basic_ok') {
return 'is-pending'; return 'is-pending';
} }
if (h.status === 'approved') return 'is-ok';
if (this.canSubmitOnboard) return 'is-warn'; if (this.canSubmitOnboard) return 'is-warn';
return ''; return '';
} }
@@ -430,6 +437,10 @@
&.is-pending { &.is-pending {
color: #1677FF; color: #1677FF;
} }
&.is-ok {
color: #52c41a;
}
} }
.arrow-icon { .arrow-icon {

查看文件

@@ -693,6 +693,12 @@ export default {
method: "POST", method: "POST",
// desc: '提现页提交分账进件资料', // desc: '提现页提交分账进件资料',
}, },
walletOnboardModifyCard: {
url: "user_wallet_apply/onboardModifyCard",
auth: true,
method: "POST",
// desc: '进件通过后更换结算银行卡',
},
walletLog: { walletLog: {
url: "user_wallet_log", url: "user_wallet_log",
auth: true, auth: true,