新增分销提现单详情页面及接口,支持查看提现单据完整信息;新增后台撤销转账中提现单功能,前端添加对应操作按钮与交互逻辑;完善提现记录跳转逻辑,支持点击进入详情页,同时新增部署脚本同步线上代码。
348 行
12 KiB
PHP
348 行
12 KiB
PHP
<?php
|
||
|
||
namespace app\admin\controller\fc;
|
||
|
||
use app\common\controller\Backend;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 同城-提现管理与审核控制器
|
||
*
|
||
* @icon fa fa-circle-o
|
||
*/
|
||
class Withdraw extends Backend
|
||
{
|
||
|
||
/**
|
||
* FcWithdraw模型对象
|
||
* @var \app\admin\model\FcWithdraw
|
||
*/
|
||
protected $model = null;
|
||
|
||
/**
|
||
* 初始化控制器
|
||
*
|
||
* @return void
|
||
*/
|
||
public function _initialize()
|
||
{
|
||
parent::_initialize();
|
||
$this->model = new \app\admin\model\FcWithdraw;
|
||
$this->view->assign("statusList", $this->model->getStatusList());
|
||
}
|
||
|
||
/**
|
||
* 提现审核详情与核验
|
||
*
|
||
* @param mixed $ids 提现记录ID
|
||
* @return string
|
||
*/
|
||
public function edit($ids = null)
|
||
{
|
||
$row = $this->model->get($ids);
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
$adminIds = $this->getDataLimitAdminIds();
|
||
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
|
||
$this->error(__('You have no permission'));
|
||
}
|
||
|
||
if ($this->request->isPost()) {
|
||
$params = $this->request->post('row/a');
|
||
if ($params) {
|
||
$params = $this->preExcludeFields($params);
|
||
// 资金安全:严禁在编辑中篡改提现单号、金额、用户ID、渠道与微信单号
|
||
unset($params['withdraw_no'], $params['amount'], $params['user_id'], $params['channel'], $params['out_bill_no'], $params['transfer_id']);
|
||
|
||
$result = false;
|
||
Db::startTrans();
|
||
try {
|
||
// 只允许修改备注、处理时间等辅助信息
|
||
$result = $row->allowField(['reason', 'paytime', 'updatetime'])->save($params);
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
}
|
||
if ($result !== false) {
|
||
$this->success();
|
||
} else {
|
||
$this->error(__('No rows were updated'));
|
||
}
|
||
}
|
||
$this->error(__('Parameter %s can not be empty', ''));
|
||
}
|
||
|
||
$userId = (int)$row['user_id'];
|
||
|
||
// 1. 获取申请用户信息
|
||
$user = Db::name('user')
|
||
->where('id', $userId)
|
||
->field('id,username,nickname,mobile,avatar,money,score,createtime')
|
||
->find();
|
||
|
||
// 2. 获取用户同城扩展信息(微信OpenID、实名认证等)
|
||
$userExt = Db::name('fc_user_ext')
|
||
->where('user_id', $userId)
|
||
->field('openid,realname,realname_status,is_member,member_level')
|
||
->find();
|
||
|
||
// 3. 计算用户分销佣金与提现对账数据
|
||
$totalCommission = (float)Db::name('fc_commission')
|
||
->where('user_id', $userId)
|
||
->where('status', '<>', 2)
|
||
->sum('amount');
|
||
|
||
$settledCommission = (float)Db::name('fc_commission')
|
||
->where('user_id', $userId)
|
||
->where('status', 1)
|
||
->sum('amount');
|
||
|
||
$totalWithdrawn = (float)Db::name('fc_withdraw')
|
||
->where('user_id', $userId)
|
||
->where('status', 'paid')
|
||
->sum('amount');
|
||
|
||
$appliedTotal = (float)Db::name('fc_withdraw')
|
||
->where('user_id', $userId)
|
||
->where('status', 'in', ['auditing', 'paying', 'paid'])
|
||
->sum('amount');
|
||
|
||
// 当前剩余可提现金额
|
||
$withdrawable = max(0, $settledCommission - $appliedTotal);
|
||
|
||
// 4. 预设快捷驳回原因模板
|
||
$rejectReasons = [
|
||
'微信未实名或实名与提现账户不一致',
|
||
'提现用户未绑定微信或OpenID失效',
|
||
'关联分销订单存在售后退款,佣金核验中',
|
||
'账号涉嫌虚假刷单/违规套现,暂缓打款',
|
||
'用户联系客服主动申请撤回提现',
|
||
'微信商家转账账户余额不足,请稍后重试',
|
||
];
|
||
|
||
$this->view->assign([
|
||
'row' => $row,
|
||
'userInfo' => $user ?: [],
|
||
'userExt' => $userExt ?: [],
|
||
'totalCommission' => round($totalCommission, 2),
|
||
'settledCommission' => round($settledCommission, 2),
|
||
'totalWithdrawn' => round($totalWithdrawn, 2),
|
||
'withdrawable' => round($withdrawable, 2),
|
||
'rejectReasons' => $rejectReasons,
|
||
]);
|
||
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 提现打款/驳回操作处理
|
||
*
|
||
* @return void
|
||
*/
|
||
public function pay()
|
||
{
|
||
$ids = $this->request->post('ids');
|
||
$op = $this->request->post('op');
|
||
$reason = $this->request->post('reason', '');
|
||
if (!in_array($op, ['pay', 'reject'])) {
|
||
$this->error('操作类型错误');
|
||
}
|
||
if ($op === 'reject' && trim((string)$reason) === '') {
|
||
$this->error('驳回需填写原因');
|
||
}
|
||
$ids = is_array($ids) ? $ids : explode(',', (string)$ids);
|
||
$ids = array_filter($ids);
|
||
if (!$ids) {
|
||
$this->error('请选择要操作的提现记录');
|
||
}
|
||
$withdraws = Db::name('fc_withdraw')->where('id', 'in', $ids)->select();
|
||
if (!$withdraws) {
|
||
$this->error('提现记录不存在');
|
||
}
|
||
|
||
$done = 0;
|
||
$pending = 0;
|
||
$failed = 0;
|
||
$errors = [];
|
||
|
||
if ($op === 'reject') {
|
||
try {
|
||
Db::transaction(function () use ($withdraws, $reason, &$done) {
|
||
foreach ($withdraws as $withdraw) {
|
||
// 行锁 + 状态校验:已处理的记录不会被二次驳回
|
||
$locked = Db::name('fc_withdraw')->where('id', $withdraw['id'])->lock(true)->find();
|
||
if (!$locked || (string)$locked['status'] !== 'auditing') {
|
||
continue;
|
||
}
|
||
$done++;
|
||
|
||
Db::name('fc_withdraw')->where('id', $locked['id'])->update([
|
||
'status' => 'rejected',
|
||
'reason' => $reason,
|
||
'updatetime' => time(),
|
||
]);
|
||
Db::name('fc_message')->insert([
|
||
'user_id' => $locked['user_id'],
|
||
'type' => 'system',
|
||
'title' => '提现驳回',
|
||
'content' => '您的提现申请「' . $locked['withdraw_no'] . '」被驳回' . ($reason ? ',原因:' . $reason : ''),
|
||
'isread' => 0,
|
||
'createtime' => time(),
|
||
]);
|
||
}
|
||
});
|
||
} catch (\Throwable $e) {
|
||
$this->error('操作失败:' . $e->getMessage());
|
||
}
|
||
|
||
if ($done === 0) {
|
||
$this->error('选中的提现记录均已处理,请刷新后重试');
|
||
}
|
||
$this->success('驳回成功(' . $done . ' 条)');
|
||
}
|
||
|
||
// 通过 → 逐笔发起微信商家转账(支持 auditing 与 paying 重试,外部调用不能放在事务里)
|
||
foreach ($withdraws as $withdraw) {
|
||
if (!in_array((string)$withdraw['status'], ['auditing', 'paying'], true)) {
|
||
continue;
|
||
}
|
||
try {
|
||
$res = \app\common\library\WxTransfer::approve((int)$withdraw['id']);
|
||
$done++;
|
||
if ($res['withdrawState'] === 'paying') {
|
||
$pending++;
|
||
} elseif ($res['withdrawState'] === 'failed') {
|
||
$failed++;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$errors[] = $withdraw['withdraw_no'] . ':' . $e->getMessage();
|
||
}
|
||
}
|
||
|
||
if ($done === 0) {
|
||
$this->error('转账失败:' . implode(';', $errors));
|
||
}
|
||
|
||
$msg = '已发起转账 ' . $done . ' 笔';
|
||
if ($pending > 0) {
|
||
$msg .= '(其中 ' . $pending . ' 笔等待用户在微信确认收款)';
|
||
}
|
||
if ($failed > 0) {
|
||
$msg .= ',' . $failed . ' 笔转账失败';
|
||
}
|
||
if ($errors) {
|
||
$msg .= ';失败 ' . count($errors) . ' 笔:' . implode(';', $errors);
|
||
}
|
||
$this->success($msg);
|
||
}
|
||
|
||
/**
|
||
* 同步微信商家转账状态(主动查询)
|
||
*
|
||
* @return void
|
||
*/
|
||
public function sync()
|
||
{
|
||
$ids = $this->request->post('ids');
|
||
$ids = is_array($ids) ? $ids : explode(',', (string)$ids);
|
||
$ids = array_filter($ids);
|
||
if (!$ids) {
|
||
$this->error('请选择要同步的提现记录');
|
||
}
|
||
|
||
$withdraws = Db::name('fc_withdraw')
|
||
->where('id', 'in', $ids)
|
||
->where('status', 'paying')
|
||
->select();
|
||
|
||
if (!$withdraws) {
|
||
$this->error('选中的记录中没有处于「转账中」的提现单');
|
||
}
|
||
|
||
$successCount = 0;
|
||
$failedCount = 0;
|
||
$payingCount = 0;
|
||
$errors = [];
|
||
|
||
foreach ($withdraws as $withdraw) {
|
||
try {
|
||
$state = \app\common\library\WxTransfer::sync((int)$withdraw['id']);
|
||
if ($state === 'paid') {
|
||
$successCount++;
|
||
} elseif ($state === 'failed') {
|
||
$failedCount++;
|
||
} else {
|
||
$payingCount++;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$errors[] = $withdraw['withdraw_no'] . ':' . $e->getMessage();
|
||
}
|
||
}
|
||
|
||
// 单笔精准反馈文案
|
||
if (count($withdraws) === 1) {
|
||
$row = $withdraws[0];
|
||
if ($errors) {
|
||
$this->error('同步失败:' . $errors[0]);
|
||
}
|
||
$latest = Db::name('fc_withdraw')->where('id', $row['id'])->find();
|
||
$transferStatus = (string)$latest['transfer_status'];
|
||
$stText = $transferStatus === 'WAIT_USER_CONFIRM' ? '待收款用户确认' : ($transferStatus === 'SUCCESS' ? '已转账成功' : ($transferStatus ?: '处理中'));
|
||
if ($latest['status'] === 'paid') {
|
||
$this->success('微信转账已完成,单据已更新为「已成功到账」');
|
||
} elseif ($latest['status'] === 'failed') {
|
||
$this->error('微信转账已失败/已撤销:' . ($latest['fail_reason'] ?: '未知原因'));
|
||
} else {
|
||
$this->success('已同步微信最新状态:' . $stText . '(微信已受理,需收款人在微信完成确认方可到账)');
|
||
}
|
||
}
|
||
|
||
$msg = '状态同步完成:';
|
||
if ($successCount > 0) {
|
||
$msg .= $successCount . ' 笔已确认到账;';
|
||
}
|
||
if ($payingCount > 0) {
|
||
$msg .= $payingCount . ' 笔仍待用户确认或处理中;';
|
||
}
|
||
if ($failedCount > 0) {
|
||
$msg .= $failedCount . ' 笔转账失败;';
|
||
}
|
||
if ($errors) {
|
||
$msg .= ' 异常:' . implode(';', $errors);
|
||
}
|
||
|
||
$this->success($msg);
|
||
}
|
||
|
||
/**
|
||
* 撤销「转账中」的微信转账提现单
|
||
*
|
||
* @return void
|
||
*/
|
||
public function cancel()
|
||
{
|
||
$id = (int)$this->request->post('id', 0);
|
||
if ($id <= 0) {
|
||
$this->error('参数错误');
|
||
}
|
||
|
||
$withdraw = Db::name('fc_withdraw')->where('id', $id)->find();
|
||
if (!$withdraw) {
|
||
$this->error('提现记录不存在');
|
||
}
|
||
if ((string)$withdraw['status'] !== 'paying') {
|
||
$this->error('只有处于「转账中」的记录方可撤销(当前:' . $withdraw['status'] . ')');
|
||
}
|
||
|
||
try {
|
||
$state = \app\common\library\WxTransfer::cancel($id);
|
||
} catch (\Throwable $e) {
|
||
$this->error('撤销失败:' . $e->getMessage());
|
||
}
|
||
|
||
$this->success('转账已成功撤销,款项额度已自动退回用户可提现余额');
|
||
}
|
||
}
|