feat: 完善会员资金明细与退款转账逻辑,新增支付密码组件与资金流水界面

这个提交包含在:
2026-10-24 17:55:36 +08:00
父节点 6b86177b5f
当前提交 b6c53abb84
共有 30 个文件被更改,包括 1222 次插入 和 537 次删除

查看文件

@@ -80,6 +80,9 @@ class FcInstall extends Command
$output->writeln('==> 发布模式与发布套餐升级');
$this->upgradePublish($output);
$output->writeln('==> 系统配置分组整理');
$this->upgradeConfigGroups($output);
$output->writeln('==> LBS / 店铺装修 / 支付 / 优惠券 / 举报升级(v1.7.0)');
$this->upgradeV170($output);
@@ -151,16 +154,16 @@ class FcInstall extends Command
/**
* 发布模式与发布套餐升级(幂等,可重复执行)
*
* 背景:v1.0.7 新增「免费发布 / 付费发布」开关与付费发布的三档定价
* 背景:v1.0.7 新增「免费发布 / 付费发布」开关与付费发布定价
* · 单条价格 post.singlePrice
* · 免费发布条数 post.freeQuota / post.freeQuotaCycle(v1.0.10:达到条数后按单条价格计费)
* · 按月(价格 + 含条数)post.monthPrice / post.monthQuota
* · 按年(价格 + 含条数)post.yearPrice / post.yearQuota
* 套餐价格与含发布条数唯一真源 = fc_plan(后台「运营推广 → 会员套餐」维护),
* 不再使用 post.monthPrice / post.yearPrice 等配置键(历史键在本步骤清理)。
* 老库缺列/缺配置时补齐,**不覆盖运营已改过的值**:
* ① fc_user_ext 补 post_quota_left / post_quota_expire(剩余发布条数 + 有效期)
* ② fc_plan 补 post_quota(套餐含发布条数,月/年卡赠送额度)
* ③ fc_config 补 post.* 八项(已存在则跳过)
* ④ 月卡/年卡的价格与条数以配置为准做一次同步(配置缺失时反向用套餐值填配置)
* ③ fc_config 补 post.* 四项(已存在则跳过)
* ④ 清理历史遗留的套餐定价配置键(post.month* / post.year* / fee.*)
*/
private function upgradePublish(Output $output)
{
@@ -197,10 +200,6 @@ class FcInstall extends Command
['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'],
['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'],
['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'],
['post.monthPrice', '19.9', 'post', '按月发布价格(元)'],
['post.monthQuota', '30', 'post', '按月含发布条数'],
['post.yearPrice', '168', 'post', '按年发布价格(元)'],
['post.yearQuota', '500', 'post', '按年含发布条数'],
];
$now = time();
$added = [];
@@ -219,27 +218,14 @@ class FcInstall extends Command
$changed++;
}
// ④ 月/年卡「价格 + 含发布条数」与配置对齐
$map = ['M1' => ['post.monthPrice', 'post.monthQuota'], 'M12' => ['post.yearPrice', 'post.yearQuota']];
$synced = 0;
foreach ($map as $code => $keys) {
$plan = Db::name('fc_plan')->where('plan_code', $code)->find();
if (!$plan) {
continue;
}
$price = round((float)$this->cfgValue($keys[0], $plan['price']), 2);
$quota = (int)$this->cfgValue($keys[1], $plan['post_quota']);
if (round((float)$plan['price'], 2) !== $price || (int)$plan['post_quota'] !== $quota) {
Db::name('fc_plan')->where('id', $plan['id'])->update([
'price' => $price,
'post_quota' => $quota,
'updatetime' => time(),
]);
$synced++;
}
}
if ($synced) {
$output->writeln(' - 会员套餐已与发布配置对齐 ' . $synced . ' 条(月/年卡价格与含条数)');
// ④ 清理历史遗留配置键:套餐定价已统一到 fc_plan,发布免费条数只认 post.freeQuota
$deprecated = [
'post.monthPrice', 'post.monthQuota', 'post.yearPrice', 'post.yearQuota',
'fee.singlePost', 'fee.freePostPerDay', 'fee.memberMonth', 'fee.memberQuarter', 'fee.memberYear',
];
$removedKeys = Db::name('fc_config')->where('key', 'in', $deprecated)->delete();
if ($removedKeys) {
$output->writeln(' - 清理废弃配置 ' . $removedKeys . ' 项(套餐定价真源已统一为 fc_plan)');
$changed++;
}
@@ -248,6 +234,43 @@ class FcInstall extends Command
: ' - 发布模式与套餐配置已是最新,无需处理');
}
/**
* 系统配置分组整理(幂等)
*
* 背景:fa_config 的 configgroup 字典保留了 FastAdmin 演示分组 user(会员配置)与
* example(示例分组),两者没有任何配置项,却会在「系统配置」页渲染出空白 tab,
* 且 user 与业务分组「会员配置」重名易混淆;这里从字典中移除。
*/
private function upgradeConfigGroups(Output $output)
{
$row = Db::name('config')->where('name', 'configgroup')->find();
if (!$row) {
$output->writeln(' - 未找到 configgroup 字典,跳过');
return;
}
$decoded = json_decode((string)$row['value'], true);
if (!is_array($decoded)) {
$output->writeln(' - configgroup 字典格式异常,跳过');
return;
}
$target = ['basic', 'email', 'dictionary'];
$kept = [];
foreach ($decoded as $k => $v) {
if (in_array($k, $target, true) || strpos((string)$k, 'fc_') === 0) {
$kept[$k] = $v;
}
}
if ($kept === $decoded) {
$output->writeln(' - 系统配置分组已是最新,无需处理');
return;
}
Db::name('config')->where('name', 'configgroup')->update([
'value' => json_encode($kept, JSON_UNESCAPED_UNICODE),
]);
$removed = array_diff(array_keys($decoded), array_keys($kept));
$output->writeln(' - 已移除空分组:' . implode(' / ', $removed));
}
/**
* v1.7.0 升级:LBS 经纬度 / 店铺装修字段 / 收藏关注 / 支付与举报配置(幂等,可重复执行)
*
@@ -642,6 +665,21 @@ class FcInstall extends Command
protected function tables()
{
return [
'wallet_flow' => "CREATE TABLE IF NOT EXISTS `#P#wallet_flow` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
`flow_no` varchar(32) NOT NULL DEFAULT '' COMMENT '流水号',
`type` varchar(10) NOT NULL DEFAULT '' COMMENT 'income=入账 expense=支出(用户钱包口径)',
`scene` varchar(32) NOT NULL DEFAULT '' COMMENT '场景:余额充值/后台充值/会员开通/发布付费/付费查看/订单退款(余额)/佣金提现',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动金额',
`balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动后钱包余额(fa_user.money)',
`related_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '关联单据ID(订单/信息/提现单)',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `flow_no` (`flow_no`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户钱包流水';",
'config' => "CREATE TABLE IF NOT EXISTS `#P#config` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置键',
@@ -860,6 +898,7 @@ class FcInstall extends Command
`free_view_used` int(10) NOT NULL DEFAULT '0',
`post_quota_left` int(10) NOT NULL DEFAULT '0' COMMENT '剩余发布条数(月/年套餐赠送)',
`post_quota_expire` int(10) DEFAULT NULL COMMENT '发布额度有效期(=会员到期时间)',
`pay_password` varchar(255) NOT NULL DEFAULT '' COMMENT '支付密码(password_hash,空=未设置)',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
@@ -921,6 +960,7 @@ class FcInstall extends Command
'finance_flow' => "CREATE TABLE IF NOT EXISTS `#P#finance_flow` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '归属用户,0=平台/历史数据',
`flow_no` varchar(32) NOT NULL DEFAULT '',
`type` varchar(16) NOT NULL DEFAULT 'income' COMMENT 'income/expense',
`scene` varchar(32) NOT NULL DEFAULT '' COMMENT '业务场景',
@@ -930,7 +970,8 @@ class FcInstall extends Command
`remark` varchar(255) NOT NULL DEFAULT '',
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `flow_no` (`flow_no`)
UNIQUE KEY `flow_no` (`flow_no`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-资金流水';",
'message' => "CREATE TABLE IF NOT EXISTS `#P#message` (
@@ -1035,20 +1076,12 @@ class FcInstall extends Command
['wx.secret', 'b023726713eebe3dad3876f5cea8ee6a', 'wx', '小程序AppSecret(仅服务端持有,不下发前端)'],
['pay.mchId', '', 'pay', '微信支付商户号'],
['pay.apiKey', '', 'pay', '微信支付密钥'],
['fee.singlePost', '5', 'fee', '单条发布价格(元)'],
['fee.freePostPerDay', '2', 'fee', '每日免费发布条数'],
['fee.memberMonth', '19.9', 'fee', '月卡价格'],
['fee.memberQuarter', '49.9', 'fee', '季卡价格'],
['fee.memberYear', '168', 'fee', '年卡价格'],
// 发布模式与发布套餐(后台「发布与费用配置」页维护;真源=配置,保存时同步 fc_plan)
// 发布模式与发布价格(后台「系统配置 → 发布配置」维护)
// 套餐价格与含发布条数在 fc_plan(后台「运营推广 → 会员套餐」维护),不走配置键
['post.mode', 'free', 'post', '发布模式:free 免费发布 / paid 付费发布(默认免费,避免上线即撞付费墙)'],
['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'],
['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'],
['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'],
['post.monthPrice', '19.9', 'post', '按月发布价格(元)'],
['post.monthQuota', '30', 'post', '按月含发布条数'],
['post.yearPrice', '168', 'post', '按年发布价格(元)'],
['post.yearQuota', '500', 'post', '按年含发布条数'],
['dist.enabled', '1', 'dist', '是否开启分销'],
['dist.level', '2', 'dist', '分佣层级'],
['dist.level1Rate', '20', 'dist', '一级分佣比例%'],