You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决类PayPal支付平台快速点击购买导致重复支付与下单问题

解决重复支付记录问题的方案

针对用户快速多次点击购买按钮导致重复生成Income和OrderNew记录的问题,可从前端拦截、后端幂等校验、数据库约束三个核心层面解决:

一、前端拦截:从源头阻止重复点击

最直接的方式是在用户点击后立即禁用按钮或添加加载状态,避免重复触发请求:

const buyBtn = document.getElementById('purchase-btn');
buyBtn.addEventListener('click', async () => {
    // 点击后禁用按钮并显示加载状态
    buyBtn.disabled = true;
    buyBtn.textContent = '处理中,请稍候...';

    try {
        // 发起支付请求
        const formData = new FormData(document.getElementById('payment-form'));
        const res = await fetch('/payment/process', {
            method: 'POST',
            body: formData,
            headers: {
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            }
        });

        // 根据响应跳转
        if (res.ok) {
            window.location.href = '/payment/success';
        } else {
            throw new Error('支付处理失败');
        }
    } catch (err) {
        alert(err.message);
        // 失败后恢复按钮状态(可选)
        buyBtn.disabled = false;
        buyBtn.textContent = '立即购买';
    }
});

二、后端幂等校验:确保同一支付只处理一次

这是核心解决方案,利用MercadoPago返回的唯一order->id做幂等校验,在插入记录前先检查是否已处理过该支付:

修改后端处理逻辑

public function invoke(Request $request) {
    $payment_id = $request->get('payment_id');
    $credenciales = config('services.mercadopago.token');
    $user_id = $request->get('user_id'); // 直接从请求获取,避免依赖Session

    $response = Http::get("https://api.mercadopago.com/v1/payments/$payment_id?access_token=$credenciales");
    $response = json_decode($response);
    $status = $response->status;

    if($status == 'approved') {
        // 关键:检查该支付订单是否已处理(用mp_id即MercadoPago的order_id做唯一标识)
        $hasProcessed = Income::where('mp_id', $response->order->id)->exists();
        if ($hasProcessed) {
            // 已处理过,直接跳转成功页
            return redirect()->route('payment.success');
        }

        // 用事务包裹插入操作,确保数据一致性
        DB::beginTransaction();
        try {
            // 插入Income记录
            Income::insert([
                'user_id'       => $user_id,
                'evento_id'     => $request->get('variableName'),
                'mp_id'         => $response->order->id,
                'metodo'        => $response->payment_type_id,
                'monto'         => $response->transaction_details->total_paid_amount,
                'rrpp_id'       => $request->get('rrpp'),
                'ingreso'       => $response->transaction_details->net_received_amount,
            ]);

            // 插入OrderNew记录
            OrderNew::insert([
                'user_id'       => $user_id,
                'dia_id'        => $request->get('variableName'),
                'whatsapp'      => $request->get('telefono'),
                'cantidad'      => $request->get('cantidad'),
                'anticipada'    => $request->get('anticipada'),
                'horario'       => $request->get('horario'),
                'rrpp'          => $request->get('rrpp'),
                'pagado'        => '1',
                'tipo'          => 'vip',
                'codigo'        => rand(1580, 4005),
            ]);

            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            return redirect()->route('payment.error')->with('error', '支付处理失败,请重试');
        }
    }

    return redirect()->route('payment.success');
}

三、数据库约束:最后一道防线

给Income表的mp_id字段添加唯一索引,即使前端和后端校验失效,数据库也会拒绝重复插入:

-- 给incomes表添加mp_id唯一索引
ALTER TABLE incomes ADD UNIQUE INDEX unique_mp_id (mp_id);

也可以根据业务需求,给OrderNew表添加复合唯一索引(比如user_id + dia_id + whatsapp),避免同一用户重复生成相同订单。

额外优化建议

  • 避免依赖Session传递支付数据:直接从请求或MercadoPago响应中获取参数,减少Session共享导致的重复提交风险;
  • 启用Laravel的请求频率限制:给支付路由添加throttle中间件,限制短时间内的请求次数;
// 在路由中添加
Route::post('/payment/invoke', [PaymentController::class, 'invoke'])->middleware('throttle:3,1');

内容的提问来源于stack exchange,提问作者Jeremias Rosas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 10:11:07