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

PHP支付后偶发登出跳转首页问题求助(生产环境)

支付成功后偶尔自动登出跳转到首页的问题排查与解决方案

核心问题分析

生产环境中出现的偶发登出,本质是支付网关回调/跳转时,用户会话已失效或无法被正确识别:

  • 本地环境会话生命周期长,支付流程耗时短,会话不会过期;生产环境会话垃圾回收时间短、用户支付操作耗时久(比如跳转银行页面等待时间长),导致会话过期。
  • 支付网关的同步跳转请求可能无法携带完整的会话Cookie(比如跨域场景下SameSite配置问题),导致pay_response.php判定用户未登录。

具体解决方案

1. 核心逻辑脱离会话依赖,改用订单标识验证

不要依赖$_SESSION判断支付回调的合法性,而是通过支付请求时生成的唯一订单ID关联用户:

  • 修改payment.php:生成customer_id后,将订单信息存入数据库,关联当前用户:

    <?php
    // 优化会话配置
    ini_set('session.gc_maxlifetime', 7200);
    session_set_cookie_params([
        'lifetime' => 7200,
        'path' => '/',
        'domain' => 'demosite.tripura.gov.in',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax'
    ]);
    session_start();
    
    // 前端访问校验
    if (!isset($_SESSION["user_mobile"])) {
        header("Location: index.php");
        exit();
    }
    
    // 响应头设置
    header("X-Frame-Options: SAMEORIGIN");
    header('X-Content-Type-Options: nosniff');
    header("X-XSS-Protection: 1; mode=block");
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"); 
    header("Referrer-Policy: same-origin");
    ?>
    <html>
    <head></head>
    <body>
    <?php 
    $merchant_id = "xxxx";
    $security_id = "xxxx";
    $checksum_key = "xxxx";
    $customer_id = round(microtime(true) * 10000);
    $amount = 10.00;
    $additionaltxt1 = "xxxx";       
    $user_uid = $_SESSION['user_uid']; // 假设会话中存储用户UID
    $return_url = 'https://demosite.tripura.gov.in/pay_response.php';  
    
    // 插入待支付订单到数据库(需提前创建payment_orders表)
    $pdo = new PDO("mysql:host=localhost;dbname=your_db", "db_user", "db_pass");
    $stmt = $pdo->prepare("INSERT INTO payment_orders (customer_id, user_uid, amount, status, created_at) VALUES (?, ?, ?, 'pending', NOW())");
    $stmt->execute([$customer_id, $user_uid, $amount]);
    
    // 生成支付字符串
    $str = $merchant_id . '|' . $customer_id . '|NA|'.$amount.'|NA|NA|NA|INR|NA|R|'.$security_id . 
    '|NA|NA|F|' . $additionaltxt1 . '|'.$user_uid.'|NA|NA|NA|NA|NA|'.$return_url;
    
    $checksum = strtoupper(hash_hmac("sha256", $str, $checksum_key, false));
    $payment_string = $str . '|' . $checksum;
    ?>
    
    <form method="POST" action="https://pgi.billdesk.com/pgidsk/PGIMerchantPayment">
    <input type="hidden" name="msg" value="<?php echo $payment_string; ?>">
    <input type="Submit" class="btn btn-primary btn-md" value='PAY'>
    </form>
    </body>
    </html>
    
  • 修改pay_response.php:优先通过订单ID验证请求合法性,会话仅用于前端展示:

    <?php
    $checksum_key = "xxxx"; // 和payment.php一致
    if (!isset($_POST['msg'])) {
        header("Location: index.php");
        exit();
    }
    
    // 1. 校验回调签名,防止伪造请求
    $resp_str = $_POST['msg'];
    $resp_arr = preg_split("/\|/", $resp_str);
    $returned_checksum = array_pop($resp_arr);
    $str_to_verify = implode('|', $resp_arr);
    $calculated_checksum = strtoupper(hash_hmac("sha256", $str_to_verify, $checksum_key, false));
    
    if ($calculated_checksum !== $returned_checksum) {
        error_log("非法支付回调: " . $resp_str);
        header("Location: index.php");
        exit();
    }
    
    // 2. 通过customer_id查询订单
    $customer_id = $resp_arr[1]; // 对应请求时传入的customer_id字段索引
    $pdo = new PDO("mysql:host=localhost;dbname=your_db", "db_user", "db_pass");
    $stmt = $pdo->prepare("SELECT * FROM payment_orders WHERE customer_id = ?");
    $stmt->execute([$customer_id]);
    $order = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$order) {
        error_log("未找到对应订单: " . $customer_id);
        header("Location: index.php");
        exit();
    }
    
    // 3. 更新订单状态
    $response_code = $resp_arr[14];
    $status = ($response_code === '000') ? 'success' : 'failed';
    $stmt = $pdo->prepare("UPDATE payment_orders SET status = ?, response_msg = ? WHERE customer_id = ?");
    $stmt->execute([$status, $resp_str, $customer_id]);
    
    // 4. 前端展示:会话存在则正常显示,不存在则提示登录查看
    ini_set('session.gc_maxlifetime', 7200);
    session_set_cookie_params([
        'lifetime' => 7200,
        'path' => '/',
        'domain' => 'demosite.tripura.gov.in',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax'
    ]);
    session_start();
    ?>
    <!doctype html>
    <html lang="en">
    <head></head>
    <body>
        <?php if (isset($_SESSION["user_mobile"])): ?>
            <h3>支付<?= $status === 'success' ? '成功' : '失败' ?></h3>
            <p>订单号: <?= $customer_id ?></p>
            <p>金额: <?= $order['amount'] ?> INR</p>
        <?php else: ?>
            <h3>支付<?= $status === 'success' ? '成功' : '失败' ?></h3>
            <p>订单号: <?= $customer_id ?></p>
            <p>请<a href="index.php">登录</a>查看订单详情</p>
        <?php endif; ?>
    </body>
    </html>
    

2. 优化生产环境会话配置,防止会话过早过期

在所有需要会话的页面(payment.php、pay_response.php等)的session_start()前添加以下配置,延长会话有效期并增强Cookie安全性:

ini_set('session.gc_maxlifetime', 7200); // 2小时有效期
session_set_cookie_params([
    'lifetime' => 7200,
    'path' => '/',
    'domain' => 'demosite.tripura.gov.in',
    'secure' => true, // HTTPS环境下启用,防止Cookie明文传输
    'httponly' => true, // 禁止JS读取Cookie,防范XSS
    'samesite' => 'Lax' // 允许跨域跳转时携带Cookie,适配支付网关场景
]);

3. 启用支付网关异步通知(Webhook)

同步跳转受限于浏览器会话,异步通知是更可靠的支付结果通知方式:

  • 联系BillDesk配置异步通知URL(比如https://demosite.tripura.gov.in/pay_webhook.php),网关会主动向该URL发送支付结果。
  • 编写pay_webhook.php,逻辑和pay_response.php的校验、订单更新部分一致,无需会话验证,仅处理订单状态更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 00:49:51