diff --git a/application/admin/controller/fc/Userext.php b/application/admin/controller/fc/Userext.php
index 46cbed0..b802321 100644
--- a/application/admin/controller/fc/Userext.php
+++ b/application/admin/controller/fc/Userext.php
@@ -3,6 +3,7 @@
namespace app\admin\controller\fc;
use app\common\controller\Backend;
+use think\Db;
/**
* 同城-用户扩展
@@ -25,13 +26,120 @@ class Userext extends Backend
}
+ /**
+ * 会员列表(关联 fa_user 展示昵称 / 手机号 / 账户余额)
+ *
+ * 说明:余额money 存放在核心表 fa_user,本表只有业务扩展字段,
+ * 因此列表需要 JOIN fa_user 才能看出「谁有多少余额」。
+ * 注意 TP5.0 的 count() 会清空查询条件,必须分别构建查询。
+ */
+ public function index()
+ {
+ if ($this->request->isAjax()) {
+ list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+ $total = $this->model->alias('e')
+ ->join('user u', 'u.id = e.user_id', 'LEFT')
+ ->where($where)
+ ->count();
+
+ $list = $this->model->alias('e')
+ ->join('user u', 'u.id = e.user_id', 'LEFT')
+ ->where($where)
+ ->field('e.*, u.nickname, u.mobile, u.money')
+ ->order($sort, $order)
+ ->limit($offset, $limit)
+ ->select();
+
+ return json(['total' => $total, 'rows' => $list]);
+ }
+ return $this->view->fetch();
+ }
+
+
+
+ /**
+ * 余额充值(给选中用户增加账户余额)
+ *
+ * POST /s.php/fc/userext/recharge
+ * 入参:{ ids: '1001,1002' , amount: 100, remark?: '备注' }
+ *
+ * 说明:
+ * 余额存放在核心表 fa_user.money,本表(fc_user_ext)仅作业务扩展,
+ * 因此这里按 user_id 关联 fa_user 进行加减。
+ * 为可追溯,每次充值同时写入 fa_fc_finance_flow(资金流水)与
+ * fa_fc_message(站内通知),后台操作留痕由 Backend 自动记录。
+ */
+ public function recharge()
+ {
+ $ids = (string)$this->request->post('ids', '');
+ $userIds = array_filter(array_map('intval', explode(',', $ids)));
+ $userIds = array_values(array_unique($userIds));
+ if (!$userIds) {
+ $this->error('请选择要充值的用户');
+ }
+
+ $amount = round((float)$this->request->post('amount', 0), 2);
+ if ($amount <= 0) {
+ $this->error('充值金额需大于 0');
+ }
+ if ($amount > 100000) {
+ $this->error('单次充值金额过大');
+ }
+ $remark = trim((string)$this->request->post('remark', ''));
+ $now = time();
+ $done = 0;
+
+ foreach ($userIds as $uid) {
+ $user = Db::name('user')->where('id', $uid)->find();
+ if (!$user) {
+ continue;
+ }
+
+ $balance = round((float)$user['money'] + $amount, 2);
+ Db::name('user')->where('id', $uid)->update([
+ 'money' => $balance,
+ 'updatetime' => $now,
+ ]);
+
+ // 资金流水(平台口径:余额充值属于收入)
+ Db::name('fc_finance_flow')->insert([
+ 'flow_no' => 'R' . date('YmdHis') . mt_rand(100, 999) . $uid,
+ 'type' => 'income',
+ 'scene' => '余额充值',
+ 'amount' => $amount,
+ 'balance' => $balance,
+ 'related_id' => $uid,
+ 'remark' => $remark !== '' ? $remark : ('后台充值给用户 #' . $uid),
+ 'createtime' => $now,
+ ]);
+
+ // 站内通知
+ Db::name('fc_message')->insert([
+ 'user_id' => $uid,
+ 'type' => 'system',
+ 'title' => '余额充值到账',
+ 'content' => '您的账户已充值 ¥' . number_format($amount, 2, '.', '')
+ . ',当前余额 ¥' . number_format($balance, 2, '.', '')
+ . '。' . ($remark !== '' ? '备注:' . $remark : ''),
+ 'isread' => 0,
+ 'readtime' => null,
+ 'createtime' => $now,
+ ]);
+
+ $done++;
+ }
+
+ if (!$done) {
+ $this->error('未找到对应用户');
+ }
+
+ $this->success('充值成功:' . $done . ' 个用户,每人 ¥' . number_format($amount, 2, '.', ''));
+ }
/**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
*/
-
-
}
diff --git a/application/admin/view/fc/userext/index.html b/application/admin/view/fc/userext/index.html
index 5963d6e..d0c894b 100644
--- a/application/admin/view/fc/userext/index.html
+++ b/application/admin/view/fc/userext/index.html
@@ -10,6 +10,7 @@
{:__('Add')}
{:__('Edit')}
{:__('Delete')}
+ 余额充值
diff --git a/application/api/controller/Member.php b/application/api/controller/Member.php
index fbdf32a..b2f5c0a 100644
--- a/application/api/controller/Member.php
+++ b/application/api/controller/Member.php
@@ -99,6 +99,12 @@ class Member extends V1
$amount = round((float)$plan['price'], 2);
$now = time();
+ // 余额校验必须在落订单之前:否则余额不足时已插入一条 status=paid 的假订单
+ $balance = (float)$user['money'];
+ if ($payType === 'balance' && $balance < $amount) {
+ $this->fail('余额不足');
+ }
+
$orderNo = $this->orderNo('MO');
$orderId = Db::name('fc_order')->insertGetId([
'order_no' => $orderNo,
@@ -115,10 +121,6 @@ class Member extends V1
]);
if ($payType === 'balance') {
- $balance = (float)$user['money'];
- if ($balance < $amount) {
- $this->fail('余额不足');
- }
$balance = round($balance - $amount, 2);
Db::name('user')->where('id', $user['id'])->update([
'money' => $balance,
diff --git a/application/api/controller/Post.php b/application/api/controller/Post.php
index 7f87d68..c614cc1 100644
--- a/application/api/controller/Post.php
+++ b/application/api/controller/Post.php
@@ -57,11 +57,18 @@ class Post extends V1
$payType = (string)$this->input('payType', '');
$amount = 0;
if ($paid) {
+ if (!in_array($payType, ['wxpay', 'balance'], true)) {
+ $this->fail('支付方式不支持');
+ }
$amount = $this->input('amount', null);
if ($amount === null || $amount === '') {
$amount = $this->cfg('fee.singlePost', 5);
}
$amount = round((float)$amount, 2);
+ // 余额校验须在落库之前:否则余额不足时会残留一条 is_paid=1 的无效发布
+ if ($payType === 'balance' && (float)$user['money'] < $amount) {
+ $this->fail('余额不足');
+ }
}
$now = time();
@@ -94,6 +101,9 @@ class Post extends V1
$order = null;
if ($paid) {
+ // 余额支付为「即时支付」:下单即扣款、订单直接置为已支付;
+ // 微信支付需要前端调起收银台,保持 pending 由支付回调置为 paid。
+ $paidNow = $payType === 'balance';
$orderNo = $this->orderNo('PO');
Db::name('fc_order')->insert([
'order_no' => $orderNo,
@@ -102,12 +112,28 @@ class Post extends V1
'title' => $title,
'amount' => $amount,
'pay_type' => $payType,
- 'status' => 'pending',
+ 'status' => $paidNow ? 'paid' : 'pending',
+ 'paytime' => $paidNow ? $now : null,
'related_id' => $postId,
'createtime' => $now,
'updatetime' => $now,
]);
- $order = ['orderNo' => $orderNo, 'amount' => $amount, 'payType' => $payType];
+
+ if ($paidNow) {
+ $balance = round((float)$user['money'] - $amount, 2);
+ Db::name('user')->where('id', $user['id'])->update([
+ 'money' => $balance,
+ 'updatetime' => $now,
+ ]);
+ $this->flow('income', '发布付费', $amount, $balance, $postId);
+ }
+
+ $order = [
+ 'orderNo' => $orderNo,
+ 'amount' => $amount,
+ 'payType' => $payType,
+ 'status' => $paidNow ? 'paid' : 'pending',
+ ];
}
$this->ok([
diff --git a/public/assets/js/backend/fc/userext.js b/public/assets/js/backend/fc/userext.js
index 4c0141f..6ecc5fc 100644
--- a/public/assets/js/backend/fc/userext.js
+++ b/public/assets/js/backend/fc/userext.js
@@ -28,19 +28,38 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
[
{checkbox: true},
{field: 'id', title: __('Id')},
- {field: 'user_id', title: __('User_id')},
- {field: 'openid', title: __('Openid'), operate: 'LIKE'},
- {field: 'user_type', title: __('User_type')},
- {field: 'realname_status', title: __('Realname_status')},
- {field: 'is_member', title: __('Is_member')},
- {field: 'member_level', title: __('Member_level'), operate: 'LIKE'},
- {field: 'member_expiretime', title: __('Member_expiretime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
- {field: 'invite_code', title: __('Invite_code'), operate: 'LIKE'},
- {field: 'inviter_id', title: __('Inviter_id')},
- {field: 'free_view_date', title: __('Free_view_date'), operate: 'LIKE'},
- {field: 'free_view_used', title: __('Free_view_used')},
- {field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
- {field: 'updatetime', title: __('Updatetime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
+ {field: 'user_id', title: '用户ID'},
+ {field: 'nickname', title: '昵称', operate: 'LIKE'},
+ {field: 'mobile', title: '手机号', operate: 'LIKE'},
+ {
+ field: 'money', title: '账户余额', operate: false,
+ formatter: function (value) {
+ var n = parseFloat(value || 0);
+ return n > 0 ? '¥' + n.toFixed(2) : '¥0.00';
+ }
+ },
+ {
+ field: 'user_type', title: '身份', searchList: {'1': '个人', '2': '商家'},
+ formatter: function (value) {
+ return value == 2 ? '商家' : '个人';
+ }
+ },
+ {
+ field: 'is_member', title: '会员', searchList: {'1': '是', '0': '否'},
+ formatter: function (value) {
+ return value == 1 ? '是' : '否';
+ }
+ },
+ {field: 'member_level', title: '会员等级', operate: 'LIKE'},
+ {field: 'member_expiretime', title: '会员到期', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'invite_code', title: '邀请码', operate: 'LIKE'},
+ {field: 'openid', title: __('Openid'), operate: 'LIKE', visible: false},
+ {field: 'realname_status', title: __('Realname_status'), visible: false},
+ {field: 'inviter_id', title: __('Inviter_id'), visible: false},
+ {field: 'free_view_date', title: __('Free_view_date'), visible: false},
+ {field: 'free_view_used', title: __('Free_view_used'), visible: false},
+ {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'updatetime', title: __('Updatetime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, visible: false},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
@@ -48,6 +67,30 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 为表格绑定事件
Table.api.bindevent(table);
+
+ // 余额充值:需先勾选用户,再输入金额
+ $(document).on('click', '.btn-recharge', function () {
+ var ids = Table.api.selectedids(table);
+ if (!ids.length) {
+ Layer.msg('请先勾选要充值的用户');
+ return;
+ }
+ Layer.prompt({title: '请输入充值金额(元)', formType: 0}, function (value, index) {
+ var amount = parseFloat(value);
+ if (!(amount > 0)) {
+ Layer.msg('充值金额需大于 0');
+ return;
+ }
+ Fast.api.ajax({
+ url: 'fc/userext/recharge',
+ data: {ids: ids.join(','), amount: amount}
+ }, function (data) {
+ table.bootstrapTable('refresh');
+ return false;
+ });
+ Layer.close(index);
+ });
+ });
},
add: function () {
Controller.api.bindevent();