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, 'failReasonText' => WithdrawText::fail($row['fail_reason']), 'reasonText' => WithdrawText::reason($row['reason']), ]); 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('微信转账已失败/已撤销:' . (WithdrawText::fail($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('转账已成功撤销,款项额度已自动退回用户可提现余额'); } }