397 行
16 KiB
PHP
397 行
16 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro_huifu\command;
|
||
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\Db;
|
||
|
||
class VerifyInstall extends Command
|
||
{
|
||
protected function configure()
|
||
{
|
||
$this->setName('shopro_huifu:verify')
|
||
->setDescription('Check shopro_huifu plugin installation (tables, menus, files)');
|
||
}
|
||
|
||
protected function execute(Input $input, Output $output)
|
||
{
|
||
$prefix = config('database.prefix');
|
||
$root = ROOT_PATH;
|
||
$pass = 0;
|
||
$fail = 0;
|
||
|
||
$checks = [
|
||
'入口文件 Shopro_huifu.php' => is_file($root . 'addons/shopro_huifu/Shopro_huifu.php'),
|
||
'info.ini' => is_file($root . 'addons/shopro_huifu/info.ini'),
|
||
'config/menu.php' => is_file($root . 'addons/shopro_huifu/config/menu.php'),
|
||
'后台控制器(已部署)' => is_file($root . 'application/admin/controller/shopro_huifu/Config.php'),
|
||
'后台控制器(插件包内)' => is_file($root . 'addons/shopro_huifu/application/admin/controller/shopro_huifu/Config.php'),
|
||
'后台视图(已部署)' => is_file($root . 'application/admin/view/shopro_huifu/config/index.html'),
|
||
'后台 JS(已部署)' => is_file($root . 'public/assets/js/backend/shopro_huifu/config.js'),
|
||
'汇付 SDK init.php' => $this->hasSdk($root),
|
||
];
|
||
|
||
$output->writeln('=== 文件检查 ===');
|
||
foreach ($checks as $label => $ok) {
|
||
$this->line($output, $label, $ok, $pass, $fail);
|
||
}
|
||
|
||
$output->writeln('');
|
||
$output->writeln('=== 数据库检查 ===');
|
||
|
||
$tables = ['shopro_huifu_config', 'shopro_huifu_pay_ext', 'shopro_huifu_notify_log'];
|
||
foreach ($tables as $table) {
|
||
$ok = $this->tableExists($prefix . $table);
|
||
$this->line($output, '表 ' . $prefix . $table, $ok, $pass, $fail);
|
||
}
|
||
|
||
try {
|
||
$cfgCount = Db::name('shopro_huifu_config')->count();
|
||
$this->line($output, '汇付配置项数量 >= 10', $cfgCount >= 10, $pass, $fail, (string) $cfgCount);
|
||
} catch (\Exception $e) {
|
||
$this->line($output, '汇付配置项', false, $pass, $fail, $e->getMessage());
|
||
}
|
||
|
||
try {
|
||
$orderTable = $prefix . 'shopro_order';
|
||
$rows = Db::query("SHOW COLUMNS FROM `{$orderTable}` LIKE 'pay_type'");
|
||
$type = $rows[0]['Type'] ?? '';
|
||
$supportsHuifu = stripos($type, 'huifu_wechat') !== false;
|
||
$this->line(
|
||
$output,
|
||
'shopro_order.pay_type 支持 huifu_wechat',
|
||
$supportsHuifu,
|
||
$pass,
|
||
$fail,
|
||
$type ?: '未找到 pay_type 字段'
|
||
);
|
||
} catch (\Exception $e) {
|
||
$this->line($output, 'shopro_order.pay_type 支持 huifu_wechat', false, $pass, $fail, $e->getMessage());
|
||
}
|
||
|
||
$output->writeln('');
|
||
$output->writeln('=== 菜单与权限检查 ===');
|
||
|
||
$menuNames = [
|
||
'shopro_huifu/config',
|
||
'shopro_huifu/config/index',
|
||
'shopro_huifu/config/save',
|
||
'shopro/split',
|
||
'shopro/split/config',
|
||
'shopro/split/config/index',
|
||
'shopro/split/receiver',
|
||
'shopro/split/rule',
|
||
'shopro/split/order',
|
||
];
|
||
foreach ($menuNames as $name) {
|
||
$row = Db::name('auth_rule')->where('name', $name)->find();
|
||
$ok = (bool) $row;
|
||
$extra = $row ? ('pid=' . $row['pid'] . ' status=' . $row['status'] . ' ismenu=' . $row['ismenu']) : '未找到';
|
||
$this->line($output, '菜单 ' . $name, $ok, $pass, $fail, $extra);
|
||
}
|
||
|
||
$legacyMenu = Db::name('auth_rule')->where('name', 'shopro_huifu')->find();
|
||
$this->line($output, '旧版菜单 shopro_huifu 已移除', !$legacyMenu, $pass, $fail, $legacyMenu ? '仍存在,需 repair' : '无');
|
||
|
||
foreach ([
|
||
'shopro_huifu/config' => '汇付菜单父节点',
|
||
'shopro/split' => '分账菜单父节点',
|
||
] as $menuName => $label) {
|
||
$row = Db::name('auth_rule')->where('name', $menuName)->find();
|
||
if ($row) {
|
||
$parent = Db::name('auth_rule')->where('id', $row['pid'])->find();
|
||
$parentName = $parent['name'] ?? 'unknown';
|
||
$ok = in_array($parentName, ['shopro/config', 'shopro'], true);
|
||
$this->line($output, $label . '正确', $ok, $pass, $fail, 'parent=' . $parentName);
|
||
} else {
|
||
$this->line($output, $label . '正确', false, $pass, $fail, '缺少 ' . $menuName);
|
||
}
|
||
}
|
||
|
||
$splitConfig = Db::name('auth_rule')->where('name', 'shopro/split/config')->find();
|
||
if ($splitConfig && intval($splitConfig['ismenu']) === 1 && $splitConfig['status'] === 'normal') {
|
||
$path = $this->getMenuPathTitles(intval($splitConfig['id']));
|
||
$this->line($output, '分账设置侧栏路径', !empty($path), $pass, $fail, implode(' -> ', $path));
|
||
} else {
|
||
$this->line($output, '分账设置侧栏路径', false, $pass, $fail, '缺少 shopro/split/config');
|
||
}
|
||
|
||
$huifuConfig = Db::name('auth_rule')->where('name', 'shopro_huifu/config')->find();
|
||
if ($huifuConfig && intval($huifuConfig['ismenu']) === 1 && $huifuConfig['status'] === 'normal') {
|
||
$path = $this->getMenuPathTitles(intval($huifuConfig['id']));
|
||
$this->line($output, '汇付配置侧栏路径', !empty($path), $pass, $fail, implode(' -> ', $path));
|
||
}
|
||
|
||
$indexRule = Db::name('auth_rule')->where('name', 'shopro_huifu/config/index')->find();
|
||
$superGroup = Db::name('auth_group')->where('rules', '*')->find();
|
||
if ($superGroup) {
|
||
$this->line($output, '存在超级管理员权限组(*)', true, $pass, $fail, 'id=' . $superGroup['id']);
|
||
} else {
|
||
$this->line($output, '存在超级管理员权限组(*)', false, $pass, $fail);
|
||
}
|
||
|
||
if ($indexRule) {
|
||
$groupsWithHuifu = 0;
|
||
foreach (Db::name('auth_group')->where('status', 'normal')->select() as $group) {
|
||
if ($group['rules'] === '*') {
|
||
$groupsWithHuifu++;
|
||
continue;
|
||
}
|
||
$owned = array_filter(array_map('trim', explode(',', (string) $group['rules'])));
|
||
if (in_array((string) $indexRule['id'], $owned, true)) {
|
||
$groupsWithHuifu++;
|
||
}
|
||
}
|
||
$this->line($output, '至少1个管理组拥有汇付权限', $groupsWithHuifu > 0, $pass, $fail, (string) $groupsWithHuifu);
|
||
}
|
||
|
||
$iniFile = ADDON_PATH . 'shopro_huifu/info.ini';
|
||
$iniState = 0;
|
||
if (is_file($iniFile)) {
|
||
$ini = parse_ini_file($iniFile);
|
||
$iniState = intval($ini['state'] ?? 0);
|
||
}
|
||
$cachedInfo = get_addon_info('shopro_huifu');
|
||
$cachedState = intval($cachedInfo['state'] ?? 0);
|
||
$this->line(
|
||
$output,
|
||
'插件 info.ini state=1',
|
||
$iniState === 1,
|
||
$pass,
|
||
$fail,
|
||
'info.ini state=' . $iniState
|
||
);
|
||
if ($iniState === 1 && $cachedState !== 1) {
|
||
$this->line(
|
||
$output,
|
||
'get_addon_info 缓存与 info.ini 一致',
|
||
false,
|
||
$pass,
|
||
$fail,
|
||
'缓存 state=' . $cachedState . ',需刷新插件缓存'
|
||
);
|
||
} else {
|
||
$this->line(
|
||
$output,
|
||
'get_addon_info 缓存与 info.ini 一致',
|
||
true,
|
||
$pass,
|
||
$fail,
|
||
'缓存 state=' . $cachedState
|
||
);
|
||
}
|
||
|
||
$addonsFile = ROOT_PATH . 'application/extra/addons.php';
|
||
$appInitHooks = [];
|
||
if (is_file($addonsFile)) {
|
||
$addonsConfig = include $addonsFile;
|
||
$appInitHooks = (array) ($addonsConfig['hooks']['app_init'] ?? []);
|
||
}
|
||
$inAppInit = in_array('shopro_huifu', $appInitHooks, true);
|
||
$this->line(
|
||
$output,
|
||
'addons.php app_init 含 shopro_huifu',
|
||
$iniState !== 1 || $inAppInit,
|
||
$pass,
|
||
$fail,
|
||
$inAppInit ? '已注册' : '未注册(线上 Hook 可能不加载,需刷新插件缓存)'
|
||
);
|
||
|
||
$output->writeln('');
|
||
$output->writeln('=== 支付通道路由检查(小程序 prepay 是否走汇付) ===');
|
||
$this->line(
|
||
$output,
|
||
'说明:收银台仍显示「微信支付」属正常,由后端路由决定实际通道',
|
||
true,
|
||
$pass,
|
||
$fail
|
||
);
|
||
|
||
try {
|
||
$cfg = Db::name('shopro_huifu_config')->column('value', 'name');
|
||
$channel = $cfg['miniprogram_channel'] ?? 'yansongda';
|
||
$hfStatus = $cfg['status'] ?? '0';
|
||
$this->line(
|
||
$output,
|
||
'miniprogram_channel=huifu',
|
||
$channel === 'huifu',
|
||
$pass,
|
||
$fail,
|
||
'当前=' . $channel
|
||
);
|
||
$this->line(
|
||
$output,
|
||
'汇付通道开关 status=1',
|
||
$hfStatus === '1',
|
||
$pass,
|
||
$fail,
|
||
'当前=' . $hfStatus
|
||
);
|
||
|
||
if ($channel === 'huifu') {
|
||
$credOk = !empty($cfg['sys_id']) && !empty($cfg['product_id']) && !empty($cfg['huifu_id']);
|
||
$this->line(
|
||
$output,
|
||
'汇付凭证 sys_id/product_id/huifu_id 已填',
|
||
$credOk,
|
||
$pass,
|
||
$fail,
|
||
$credOk ? '已填' : '缺商户号/系统号/产品号'
|
||
);
|
||
$keyOk = !empty($cfg['merchant_private_key']) && !empty($cfg['huifu_public_key']);
|
||
$this->line(
|
||
$output,
|
||
'RSA 密钥对已填',
|
||
$keyOk,
|
||
$pass,
|
||
$fail,
|
||
$keyOk ? '已填' : '缺私钥或汇付公钥'
|
||
);
|
||
if ($keyOk && class_exists('\\addons\\shopro_huifu\\library\\HuifuConfig')) {
|
||
try {
|
||
\addons\shopro_huifu\library\HuifuConfig::reload()->validateKeys();
|
||
$this->line($output, 'RSA 密钥格式可签名', true, $pass, $fail);
|
||
} catch (\Exception $e) {
|
||
$this->line($output, 'RSA 密钥格式可签名', false, $pass, $fail, $e->getMessage());
|
||
}
|
||
}
|
||
$payScene = trim((string) ($cfg['pay_scene'] ?? ''));
|
||
$channelNo = trim((string) ($cfg['channel_no'] ?? ''));
|
||
$scenePairOk = ($channelNo === '') || ($payScene !== '' && $channelNo !== '');
|
||
$this->line(
|
||
$output,
|
||
'pay_scene 与 channel_no 成对配置',
|
||
$scenePairOk,
|
||
$pass,
|
||
$fail,
|
||
$scenePairOk ? 'OK' : '填了 channel_no 时必须同时填 pay_scene'
|
||
);
|
||
}
|
||
|
||
$mini = Db::name('shopro_config')->where('name', 'wxMiniProgram')->value('value');
|
||
$mini = json_decode($mini, true);
|
||
$appId = $mini['app_id'] ?? '';
|
||
$this->line(
|
||
$output,
|
||
'商城小程序 AppID 已配置',
|
||
$appId !== '',
|
||
$pass,
|
||
$fail,
|
||
$appId !== '' ? $appId : 'shopro_config.wxMiniProgram 为空'
|
||
);
|
||
} catch (\Exception $e) {
|
||
$this->line($output, '支付通道配置可读', false, $pass, $fail, $e->getMessage());
|
||
}
|
||
|
||
if (class_exists('\\addons\\shopro_huifu\\library\\Bridge')) {
|
||
$bridgeEnabled = \addons\shopro_huifu\library\Bridge::isEnabled();
|
||
$this->line(
|
||
$output,
|
||
'Bridge::isEnabled()',
|
||
$bridgeEnabled,
|
||
$pass,
|
||
$fail,
|
||
$bridgeEnabled ? 'true' : 'false(插件未启用或 status=0)'
|
||
);
|
||
$huifuChannel = \addons\shopro_huifu\library\Bridge::isHuifuMiniProgramChannel('wxMiniProgram');
|
||
$this->line(
|
||
$output,
|
||
'Bridge::isHuifuMiniProgramChannel(wxMiniProgram)',
|
||
$huifuChannel,
|
||
$pass,
|
||
$fail,
|
||
$huifuChannel ? 'true,prepay 应走汇付' : 'false,prepay 仍走 Yansongda'
|
||
);
|
||
$shouldPay = \addons\shopro_huifu\library\Bridge::shouldHandlePay([
|
||
'platform' => 'wxMiniProgram',
|
||
'payment' => 'wechat',
|
||
'prepay_type' => 'order',
|
||
]);
|
||
$this->line(
|
||
$output,
|
||
'Bridge::shouldHandlePay(小程序订单)',
|
||
$shouldPay,
|
||
$pass,
|
||
$fail,
|
||
$shouldPay ? 'true' : 'false'
|
||
);
|
||
}
|
||
|
||
$hookBehaviors = \think\Hook::get('shopro_pay_create');
|
||
$hookOk = !empty($hookBehaviors);
|
||
$this->line(
|
||
$output,
|
||
'Hook shopro_pay_create 已注册',
|
||
$hookOk,
|
||
$pass,
|
||
$fail,
|
||
$hookOk ? json_encode($hookBehaviors, JSON_UNESCAPED_UNICODE) : '未注册(插件 state=0 或未加载 appInit)'
|
||
);
|
||
|
||
$output->writeln('');
|
||
$output->writeln("结果: 通过 {$pass} / 失败 {$fail}");
|
||
$output->writeln('汇付配置直达: /admin/shopro_huifu/config');
|
||
$output->writeln('分账设置直达: /admin/shopro/split/config');
|
||
if ($fail > 0) {
|
||
$output->writeln('修复菜单/权限/缓存: php think shopro_huifu:repair-menu');
|
||
$output->writeln('修复后请退出后台重新登录');
|
||
}
|
||
}
|
||
|
||
protected function getMenuPathTitles($menuId)
|
||
{
|
||
$path = [];
|
||
$currentId = $menuId;
|
||
$guard = 0;
|
||
while ($currentId && $guard < 20) {
|
||
$row = Db::name('auth_rule')->where('id', $currentId)->find();
|
||
if (!$row) {
|
||
break;
|
||
}
|
||
if (intval($row['ismenu']) === 1 && $row['status'] === 'normal') {
|
||
array_unshift($path, $row['title']);
|
||
}
|
||
$currentId = intval($row['pid']);
|
||
$guard++;
|
||
}
|
||
return $path;
|
||
}
|
||
|
||
protected function line(Output $output, $label, $ok, &$pass, &$fail, $extra = '')
|
||
{
|
||
if ($ok) {
|
||
$pass++;
|
||
$output->writeln('[OK] ' . $label . ($extra ? ' (' . $extra . ')' : ''));
|
||
} else {
|
||
$fail++;
|
||
$output->writeln('[FAIL] ' . $label . ($extra ? ' (' . $extra . ')' : ''));
|
||
}
|
||
}
|
||
|
||
protected function tableExists($table)
|
||
{
|
||
try {
|
||
$rows = Db::query("SHOW TABLES LIKE '{$table}'");
|
||
return !empty($rows);
|
||
} catch (\Exception $e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
protected function hasSdk($root)
|
||
{
|
||
$paths = [
|
||
$root . 'addons/shopro_huifu/library/sdk/extracted/huifurepo-bspay-php-sdk-45b895b/BsPaySdk/init.php',
|
||
$root . 'vendor/huifurepo/dg-php-sdk/BsPaySdk/init.php',
|
||
];
|
||
foreach ($paths as $p) {
|
||
if (is_file($p)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|