本文目录导读:

我来详细介绍如何用PHP实现一个完整的提现系统,包括核心功能和安全措施。
数据库设计
提现申请表
CREATE TABLE `withdrawals` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`fee` decimal(10,2) DEFAULT '0.00',
`actual_amount` decimal(10,2) NOT NULL,
`payment_method` varchar(50) NOT NULL,
`account_info` text NOT NULL,
`status` enum('pending','processing','success','failed','cancelled') DEFAULT 'pending',
`created_at` datetime NOT NULL,
`processed_at` datetime DEFAULT NULL,
`admin_note` text,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `status` (`status`)
);
-- 用户余额表
CREATE TABLE `user_balances` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`balance` decimal(10,2) DEFAULT '0.00',
`frozen_amount` decimal(10,2) DEFAULT '0.00',
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
);
提现配置文件
// config/withdrawal.php
return [
'min_amount' => 10.00,
'max_amount' => 10000.00,
'daily_limit' => 3,
'daily_amount_limit' => 50000.00,
'fee_rate' => 0.01, // 1%手续费
'payment_methods' => [
'alipay' => '支付宝',
'wechat' => '微信',
'bank_card' => '银行卡'
],
'audit_required' => true, // 是否需要审核
'auto_process' => false, // 是否自动处理
'supported_currencies' => ['CNY']
];
核心提现类
<?php
// classes/WithdrawalSystem.php
class WithdrawalSystem {
private $db;
private $config;
public function __construct($db) {
$this->db = $db;
$this->config = require 'config/withdrawal.php';
}
/**
* 创建提现申请
*/
public function createWithdrawal($userId, $amount, $paymentMethod, $accountInfo) {
// 1. 验证用户身份
if (!$this->verifyUser($userId)) {
throw new Exception("用户验证失败");
}
// 2. 检查提现限制
$this->validateWithdrawal($userId, $amount);
// 3. 计算手续费
$fee = $this->calculateFee($amount);
$actualAmount = $amount - $fee;
// 4. 开启事务
$this->db->beginTransaction();
try {
// 5. 冻结余额
$this->freezeBalance($userId, $amount);
// 6. 创建提现记录
$stmt = $this->db->prepare(
"INSERT INTO withdrawals
(user_id, amount, fee, actual_amount, payment_method, account_info, created_at)
VALUES (?, ?, ?, ?, ?, ?, NOW())"
);
$stmt->execute([
$userId, $amount, $fee, $actualAmount,
json_encode($paymentMethod), json_encode($accountInfo)
]);
$withdrawalId = $this->db->lastInsertId();
// 7. 记录日志
$this->logWithdrawal($userId, $withdrawalId, 'create');
$this->db->commit();
return $withdrawalId;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
/**
* 验证提现申请
*/
private function validateWithdrawal($userId, $amount) {
// 检查最低提现金额
if ($amount < $this->config['min_amount']) {
throw new Exception("提现金额不能低于 {$this->config['min_amount']} 元");
}
// 检查最高提现金额
if ($amount > $this->config['max_amount']) {
throw new Exception("单次提现金额不能超过 {$this->config['max_amount']} 元");
}
// 检查用户余额
$balance = $this->getUserBalance($userId);
if ($balance < $amount) {
throw new Exception("余额不足,当前余额:{$balance} 元");
}
// 检查每日提现次数
$todayCount = $this->getTodayWithdrawalCount($userId);
if ($todayCount >= $this->config['daily_limit']) {
throw new Exception("每日提现次数已达上限");
}
// 检查每日提现金额限制
$todayAmount = $this->getTodayWithdrawalAmount($userId);
if ($todayAmount + $amount > $this->config['daily_amount_limit']) {
throw new Exception("每日提现金额已达上限");
}
}
/**
* 处理提现(管理员操作)
*/
public function processWithdrawal($withdrawalId, $action, $adminId, $note = '') {
$withdrawal = $this->getWithdrawalById($withdrawalId);
if (!$withdrawal) {
throw new Exception("提现记录不存在");
}
if ($withdrawal['status'] != 'pending') {
throw new Exception("只能处理待审核的提现");
}
$this->db->beginTransaction();
try {
switch ($action) {
case 'approve':
$this->approveWithdrawal($withdrawal, $adminId);
break;
case 'reject':
$this->rejectWithdrawal($withdrawal, $adminId, $note);
break;
case 'complete':
$this->completeWithdrawal($withdrawal, $adminId);
break;
default:
throw new Exception("无效操作");
}
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
/**
* 批准提现
*/
private function approveWithdrawal($withdrawal, $adminId) {
// 更新状态为处理中
$stmt = $this->db->prepare(
"UPDATE withdrawals SET status='processing', processed_at=NOW()
WHERE id=?"
);
$stmt->execute([$withdrawal['id']]);
// 记录操作日志
$this->logWithdrawalAdmin($withdrawal['user_id'], $withdrawal['id'], 'approve', $adminId);
}
/**
* 拒绝提现
*/
private function rejectWithdrawal($withdrawal, $adminId, $note) {
// 解冻余额
$this->unfreezeBalance($withdrawal['user_id'], $withdrawal['amount']);
// 更新状态
$stmt = $this->db->prepare(
"UPDATE withdrawals SET status='failed', admin_note=?, processed_at=NOW()
WHERE id=?"
);
$stmt->execute([$note, $withdrawal['id']]);
// 记录操作日志
$this->logWithdrawalAdmin($withdrawal['user_id'], $withdrawal['id'], 'reject', $adminId);
}
/**
* 完成提现(打款成功)
*/
private function completeWithdrawal($withdrawal, $adminId) {
// 扣除冻结余额
$this->deductFrozenBalance($withdrawal['user_id'], $withdrawal['amount']);
// 更新状态
$stmt = $this->db->prepare(
"UPDATE withdrawals SET status='success', processed_at=NOW()
WHERE id=?"
);
$stmt->execute([$withdrawal['id']]);
// 记录操作日志
$this->logWithdrawalAdmin($withdrawal['user_id'], $withdrawal['id'], 'complete', $adminId);
// 发送通知
$this->sendWithdrawalNotification($withdrawal['user_id'], $withdrawal, 'success');
}
/**
* 冻结余额
*/
private function freezeBalance($userId, $amount) {
$stmt = $this->db->prepare(
"UPDATE user_balances
SET balance = balance - ?,
frozen_amount = frozen_amount + ?,
updated_at = NOW()
WHERE user_id = ? AND balance >= ?"
);
$stmt->execute([$amount, $amount, $userId, $amount]);
if ($stmt->rowCount() === 0) {
throw new Exception("余额不足或更新失败");
}
}
/**
* 解冻余额
*/
private function unfreezeBalance($userId, $amount) {
$stmt = $this->db->prepare(
"UPDATE user_balances
SET balance = balance + ?,
frozen_amount = frozen_amount - ?,
updated_at = NOW()
WHERE user_id = ? AND frozen_amount >= ?"
);
$stmt->execute([$amount, $amount, $userId, $amount]);
}
/**
* 扣除冻结余额
*/
private function deductFrozenBalance($userId, $amount) {
$stmt = $this->db->prepare(
"UPDATE user_balances
SET frozen_amount = frozen_amount - ?,
updated_at = NOW()
WHERE user_id = ? AND frozen_amount >= ?"
);
$stmt->execute([$amount, $userId, $amount]);
}
/**
* 获取用户余额
*/
public function getUserBalance($userId) {
$stmt = $this->db->prepare("SELECT balance FROM user_balances WHERE user_id = ?");
$stmt->execute([$userId]);
$result = $stmt->fetch();
return $result ? $result['balance'] : 0;
}
/**
* 获取用户冻结金额
*/
public function getUserFrozenAmount($userId) {
$stmt = $this->db->prepare("SELECT frozen_amount FROM user_balances WHERE user_id = ?");
$stmt->execute([$userId]);
$result = $stmt->fetch();
return $result ? $result['frozen_amount'] : 0;
}
/**
* 计算手续费
*/
private function calculateFee($amount) {
return round($amount * $this->config['fee_rate'], 2);
}
/**
* 获取今日提现次数
*/
private function getTodayWithdrawalCount($userId) {
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM withdrawals
WHERE user_id = ?
AND DATE(created_at) = CURDATE()"
);
$stmt->execute([$userId]);
return $stmt->fetchColumn();
}
/**
* 获取今日提现金额
*/
private function getTodayWithdrawalAmount($userId) {
$stmt = $this->db->prepare(
"SELECT COALESCE(SUM(amount), 0) FROM withdrawals
WHERE user_id = ?
AND DATE(created_at) = CURDATE()"
);
$stmt->execute([$userId]);
return $stmt->fetchColumn();
}
/**
* 获取提现记录
*/
public function getWithdrawalById($id) {
$stmt = $this->db->prepare("SELECT * FROM withdrawals WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 获取用户提现历史
*/
public function getUserWithdrawals($userId, $page = 1, $limit = 20) {
$offset = ($page - 1) * $limit;
$stmt = $this->db->prepare(
"SELECT * FROM withdrawals
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?"
);
$stmt->execute([$userId, $limit, $offset]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 获取所有提现记录(管理员)
*/
public function getAllWithdrawals($status = null, $page = 1, $limit = 20) {
$where = '';
$params = [];
if ($status) {
$where = "WHERE status = ?";
$params[] = $status;
}
$offset = ($page - 1) * $limit;
$stmt = $this->db->prepare(
"SELECT w.*, u.username
FROM withdrawals w
JOIN users u ON w.user_id = u.id
$where
ORDER BY w.created_at DESC
LIMIT ? OFFSET ?"
);
$params[] = $limit;
$params[] = $offset;
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
提现页面控制器
<?php
// controllers/WithdrawalController.php
class WithdrawalController {
private $withdrawalSystem;
private $user;
public function __construct($db, $userId) {
$this->withdrawalSystem = new WithdrawalSystem($db);
$this->user = $this->getUser($db, $userId);
}
/**
* 显示提现页面
*/
public function showWithdrawalForm() {
$balance = $this->withdrawalSystem->getUserBalance($this->user['id']);
$frozenAmount = $this->withdrawalSystem->getUserFrozenAmount($this->user['id']);
$availableBalance = $balance;
$config = require 'config/withdrawal.php';
$paymentMethods = $config['payment_methods'];
include 'views/withdrawal/form.php';
}
/**
* 处理提现申请
*/
public function submitWithdrawal() {
try {
$amount = floatval($_POST['amount']);
$paymentMethod = $_POST['payment_method'];
$accountInfo = [
'account' => $_POST['account'],
'name' => $_POST['account_name']
];
$withdrawalId = $this->withdrawalSystem->createWithdrawal(
$this->user['id'],
$amount,
$paymentMethod,
$accountInfo
);
$_SESSION['success'] = '提现申请提交成功,请等待审核';
} catch (Exception $e) {
$_SESSION['error'] = $e->getMessage();
}
header('Location: /withdrawal');
exit;
}
/**
* 提现历史
*/
public function showWithdrawalHistory() {
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$withdrawals = $this->withdrawalSystem->getUserWithdrawals($this->user['id'], $page);
include 'views/withdrawal/history.php';
}
}
提现表单视图
<!-- views/withdrawal/form.php -->
<div class="container">
<h2>提现申请</h2>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger"><?= $_SESSION['error'] ?></div>
<?php unset($_SESSION['error']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success"><?= $_SESSION['success'] ?></div>
<?php unset($_SESSION['success']); ?>
<?php endif; ?>
<div class="balance-info">
<p>当前余额:<?= number_format($balance, 2) ?> 元</p>
<p>冻结金额:<?= number_format($frozenAmount, 2) ?> 元</p>
<p>可用余额:<?= number_format($availableBalance, 2) ?> 元</p>
</div>
<form action="/withdrawal/submit" method="POST">
<div class="form-group">
<label>提现金额</label>
<input type="number" name="amount" step="0.01"
min="<?= $config['min_amount'] ?>"
max="<?= $config['max_amount'] ?>"
required>
<small>最低提现金额:<?= $config['min_amount'] ?> 元</small>
</div>
<div class="form-group">
<label>支付方式</label>
<select name="payment_method" required>
<?php foreach ($paymentMethods as $key => $value): ?>
<option value="<?= $key ?>"><?= $value ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>收款账号</label>
<input type="text" name="account" required>
</div>
<div class="form-group">
<label>收款人姓名</label>
<input type="text" name="account_name" required>
</div>
<button type="submit" class="btn btn-primary">提交提现申请</button>
</form>
</div>
管理员提现审核页面
<!-- views/admin/withdrawal/review.php -->
<div class="container">
<h2>提现审核管理</h2>
<div class="filter">
<form action="/admin/withdrawals" method="GET">
<select name="status">
<option value="pending">待审核</option>
<option value="processing">处理中</option>
<option value="success">已完成</option>
<option value="failed">已拒绝</option>
</select>
<button type="submit">筛选</button>
</form>
</div>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>用户</th>
<th>金额</th>
<th>手续费</th>
<th>实际到账</th>
<th>支付方式</th>
<th>状态</th>
<th>申请时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($withdrawals as $wd): ?>
<tr>
<td><?= $wd['id'] ?></td>
<td><?= htmlspecialchars($wd['username']) ?></td>
<td><?= number_format($wd['amount'], 2) ?></td>
<td><?= number_format($wd['fee'], 2) ?></td>
<td><?= number_format($wd['actual_amount'], 2) ?></td>
<td><?= $wd['payment_method'] ?></td>
<td>
<span class="badge badge-<?= $wd['status'] ?>">
<?= $wd['status'] ?>
</span>
</td>
<td><?= $wd['created_at'] ?></td>
<td>
<?php if ($wd['status'] == 'pending'): ?>
<button onclick="processWithdrawal(<?= $wd['id'] ?>, 'approve')">
审核通过
</button>
<button onclick="processWithdrawal(<?= $wd['id'] ?>, 'reject')">
拒绝
</button>
<?php elseif ($wd['status'] == 'processing'): ?>
<button onclick="processWithdrawal(<?= $wd['id'] ?>, 'complete')">
确认到账
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<script>
function processWithdrawal(id, action) {
if (!confirm('确认执行此操作?')) return;
fetch('/admin/withdrawals/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
withdrawal_id: id,
action: action
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message);
}
});
}
</script>
路由配置
// routes/web.php
// 用户提现路由
Router::get('/withdrawal', 'WithdrawalController@showWithdrawalForm');
Router::post('/withdrawal/submit', 'WithdrawalController@submitWithdrawal');
Router::get('/withdrawal/history', 'WithdrawalController@showWithdrawalHistory');
// 管理员提现管理路由
Router::get('/admin/withdrawals', 'AdminWithdrawalController@index');
Router::post('/admin/withdrawals/process', 'AdminWithdrawalController@process');
Router::get('/admin/withdrawals/{id}', 'AdminWithdrawalController@detail');
安全注意事项
防止重复提交
// 添加CSRF令牌
session_start();
$_SESSION['withdrawal_token'] = bin2hex(random_bytes(32));
// 表单中添加
<input type="hidden" name="csrf_token" value="<?= $_SESSION['withdrawal_token'] ?>">
// 验证token
if ($_POST['csrf_token'] !== $_SESSION['withdrawal_token']) {
throw new Exception("安全验证失败");
}
金额验证
// 严格验证金额格式
if (!preg_match('/^\d+(\.\d{1,2})?$/', $amount)) {
throw new Exception("金额格式错误");
}
防刷机制
// 添加验证码 // 使用限流中间件
提现记录日志表
CREATE TABLE `withdrawal_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `withdrawal_id` int(11) NOT NULL, `action` varchar(50) NOT NULL, `admin_id` int(11) DEFAULT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `withdrawal_id` (`withdrawal_id`) );
这个提现系统包含了完整的业务流程、安全措施和防作弊机制,根据实际需求,你还可以:
- 添加多级审核流程
- 对接实际支付接口
- 实现自动打款功能
- 添加更详细的财务统计报表
- 实现提现风控系统