feat: 完善协议管理、师傅资质认证、分账配置并完成测试环境全量部署与同步

这个提交包含在:
2026-11-12 17:19:49 +08:00
父节点 fafddceaf2
当前提交 72cea8445f
共有 179 个文件被更改,包括 29242 次插入 和 3340 次删除

查看文件

@@ -3,6 +3,8 @@
namespace app\admin\controller;
use app\common\controller\Backend;
use app\common\library\FinanceConfigService;
use app\common\library\PaymentService;
use think\Db;
use think\exception\PDOException;
@@ -24,7 +26,7 @@ class Withdraw extends Backend
*/
protected $model = null;
protected $relationSearch = true;
protected $searchFields = 'id,account,audit_msg,master.realname,master.mobile';
protected $searchFields = 'id,account,audit_msg,fail_code,fail_reason,out_bill_no,transfer_bill_no,master.realname,master.mobile';
/**
* 审核状态机:当前状态 => 允许流转到的目标状态
@@ -105,10 +107,12 @@ class Withdraw extends Backend
}
/**
* 财务审核:申请信息只读,仅允许合法状态流转并做资金联动
* - 驳回:退回师傅余额并解冻,写入钱包流水,通知师傅
* - 通过:仅标记审核通过,等待财务打款
* - 打款完成:划出冻结资金,写入钱包流水,通知师傅
* 财务审核与微信商家转账打款处理:
* - 微信渠道(channel=wechat):
* 1) 审核通过(pass):调用微信支付 V3 新版「商家转账(场景 1005 佣金报酬)」接口生成转账单,等待师傅在小程序确认收款
* 2) 同步状态(sync):主动查询微信商家转账单状态,若 SUCCESS 则划出冻结资金标记 done;若 FAIL/CANCELLED 则解冻退回师傅余额
* 3) 驳回/撤销(reject):若尚未发微信转账则直接退回余额;若已发微信转账则先调微信查单并撤销确认后再退回余额(防双重资损)
* - 银行卡渠道(channel=bank):保留审核通过 → 线下打款完成(done) / 驳回(reject)
*/
public function edit($ids = null)
{
@@ -116,40 +120,101 @@ class Withdraw extends Backend
if (!$row) {
$this->error(__('No Results were found'));
}
$wdCfg = FinanceConfigService::getWithdrawConfig();
$isWechat = ($row['channel'] === 'wechat');
$useWxApi = $isWechat && ($wdCfg['transfer_mode'] === 'wx_mch_transfer');
$hasWxBill = !empty($row['out_bill_no']);
$txState = strtoupper(trim((string)($row['transfer_state'] ?? '')));
$flowOptions = [];
if ($row['status'] === 'wait') {
if ($hasWxBill) {
if (in_array($txState, ['FAIL', 'NOT_FOUND'], true) && $useWxApi) {
$flowOptions = ['pass', 'sync', 'reject'];
} else {
$flowOptions = ['sync', 'reject'];
}
} else {
$flowOptions = ['pass', 'reject'];
}
} elseif ($row['status'] === 'pass') {
if ($isWechat && $hasWxBill) {
if (in_array($txState, ['FAIL', 'NOT_FOUND'], true) && $useWxApi) {
$flowOptions = ['pass', 'sync', 'reject'];
} else {
$flowOptions = ['sync', 'reject'];
}
} elseif ($useWxApi && !$hasWxBill) {
$flowOptions = ['pass', 'done', 'reject'];
} else {
$flowOptions = ['done', 'reject'];
}
}
if (false === $this->request->isPost()) {
// 展示师傅当前钱包情况,辅助财务判断
$master = Db::name('master')->where('id', (int)$row['master_id'])->field('id,realname,mobile,user_id,money,frozen_money')->find();
$this->view->assign('row', $row);
$this->view->assign('master', $master ?: []);
$this->view->assign('flowOptions', $this->statusFlow[$row['status']] ?? []);
$this->view->assign('flowOptions', $flowOptions);
$this->view->assign('useWxApi', $useWxApi);
$this->view->assign('transferStateLabel', PaymentService::transferStateLabel($row['transfer_state'] ?? ''));
return $this->view->fetch();
}
$status = (string)$this->request->post('row.status');
$auditMsg = trim((string)$this->request->post('row.audit_msg'));
// 状态机校验:终态禁止操作,流转目标必须合法
if (!isset($this->statusFlow[$row['status']])) {
$this->error('该提现单已处于终态(已驳回/已打款),不允许再次审核');
if (empty($flowOptions)) {
$this->error('该提现单已处于终态(已驳回/已打款),不允许再次操作');
}
if (!in_array($status, $this->statusFlow[$row['status']], true)) {
if (!in_array($status, $flowOptions, true)) {
$this->error('状态流转不合法,请刷新后重试');
}
if ($status === 'reject' && $auditMsg === '') {
$this->error('驳回时必须填写驳回原因');
$this->error('驳回或撤销转账时必须填写原因');
}
// 1. 驳回或撤销转账(统一走防双重资损方法)
if ($status === 'reject') {
try {
$res = PaymentService::cancelOrRejectWithdraw((int)$row['id'], $auditMsg);
} catch (\Throwable $e) {
$this->error($e->getMessage());
}
$this->success($res['message'] ?? '已驳回处理');
}
// 2. 微信钱包渠道(启用微信商家转账模式时):发起微信商家转账(1005) 或 同步微信转账状态
if ($useWxApi && $status === 'pass') {
try {
PaymentService::initiateWithdrawTransfer((int)$row['id'], $auditMsg);
} catch (\Throwable $e) {
$this->error($e->getMessage());
}
$this->success('已审核通过并发起微信商家转账,等待师傅在小程序「我的钱包」确认收款');
}
if ($isWechat && $status === 'sync') {
try {
$updated = PaymentService::syncWithdrawTransfer((int)$row['id']);
} catch (\Throwable $e) {
$this->error('同步微信状态失败:' . $e->getMessage());
}
$stateText = PaymentService::transferStateLabel($updated['transfer_state'] ?? '');
$this->success('已同步最新微信转账状态:' . $stateText);
}
// 3. 线下银行卡打款(或历史未发微信单的线下打款完成)
$masterId = (int)$row['master_id'];
$amount = round((float)$row['amount'], 2);
Db::startTrans();
try {
// 乐观锁更新状态,防止并发重复审核
$affected = Db::name('withdraw')
->where('id', $row['id'])
->where('status', $row['status'])
->update([
'status' => $status,
'audit_msg' => $auditMsg,
'audit_msg' => $auditMsg !== '' ? $auditMsg : $row['audit_msg'],
'updatetime' => time(),
]);
if (!$affected) {
@@ -161,32 +226,11 @@ class Withdraw extends Backend
throw new \Exception('提现师傅不存在');
}
if ($status === 'reject') {
// 驳回:解冻并退回余额,写入钱包流水
$newMoney = round((float)$master['money'] + $amount, 2);
Db::name('master')->where('id', $masterId)->update([
'money' => $newMoney,
'frozen_money' => round((float)$master['frozen_money'] - $amount, 2),
'updatetime' => time(),
]);
Db::name('wallet_log')->insert([
'user_type' => 'master',
'user_id' => $masterId,
'type' => 'refund',
'amount' => $amount,
'balance' => $newMoney,
'order_id' => 0,
'remark' => '提现驳回,申请金额退回钱包余额' . ($auditMsg !== '' ? '(原因:' . $auditMsg . ')' : ''),
'createtime' => time(),
]);
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现申请已驳回', '您的提现申请 ¥' . $amount . ' 未通过财务审核:' . ($auditMsg !== '' ? $auditMsg : '无') . ',申请金额已退回钱包余额');
} elseif ($status === 'pass') {
// 审核通过:仅标记,资金保持冻结,等待财务打款
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现审核通过', '您的提现申请 ¥' . $amount . ' 已通过审核,财务将尽快安排打款到 ' . ($row['channel'] === 'bank' ? '银行卡' : '微信钱包'));
if ($status === 'pass') {
$this->sendMasterMessage($masterId, (int)$master['user_id'], '提现审核通过', '您的提现申请 ¥' . $amount . ' 已通过审核,财务将尽快安排打款到银行卡');
} elseif ($status === 'done') {
// 打款完成:冻结资金划出
Db::name('master')->where('id', $masterId)->update([
'frozen_money' => round((float)$master['frozen_money'] - $amount, 2),
'frozen_money' => max(0, round((float)$master['frozen_money'] - $amount, 2)),
'updatetime' => time(),
]);
Db::name('wallet_log')->insert([
@@ -209,6 +253,27 @@ class Withdraw extends Backend
$this->success();
}
/**
* 后台列表一键同步微信商家转账单最新状态
*/
public function sync($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
if (empty($row['out_bill_no'])) {
$this->error('该提现单尚未发起微信商家转账');
}
try {
$updated = PaymentService::syncWithdrawTransfer((int)$row['id']);
} catch (\Throwable $e) {
$this->error('同步失败:' . $e->getMessage());
}
$stateText = PaymentService::transferStateLabel($updated['transfer_state'] ?? '');
$this->success('同步成功,当前微信状态:' . $stateText);
}
/**
* 提现单仅由师傅端发起,禁止后台手工添加
*/