如何用PHP项目实现提现系统?

wen java案例 1

本文目录导读:

如何用PHP项目实现提现系统?

  1. 数据库设计
  2. 提现配置文件
  3. 核心提现类
  4. 提现页面控制器
  5. 提现表单视图
  6. 管理员提现审核页面
  7. 路由配置
  8. 安全注意事项
  9. 提现记录日志表

我来详细介绍如何用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`)
);

这个提现系统包含了完整的业务流程、安全措施和防作弊机制,根据实际需求,你还可以:

  • 添加多级审核流程
  • 对接实际支付接口
  • 实现自动打款功能
  • 添加更详细的财务统计报表
  • 实现提现风控系统

抱歉,评论功能暂时关闭!