PAYATHON 2026

在 CodeIgniter 中加载 payment gateway 页面

支付小周

结论

curl 只能让服务器访问支付网关,无法把用户的浏览器带到网关页面。CodeIgniter 在 AJAX 请求中调用 redirect() 时,被重定向的也只是 AJAX 请求,当前页面不会跳转。

具体实现取决于网关的要求:

  • 网关接受 GET 时,控制器返回完整的跳转地址,前端在 AJAX 成功后执行 window.location.href。
  • 网关要求 POST 时,不要用 window.location.href 拼接参数。可以生成一个自动提交的 HTML 表单,或者让原表单直接提交到 CodeIgniter,再由控制器输出自动提交页面。

方案一:网关接受 GET 参数

控制器生成完整 URL,并通过 JSON 返回:

public function save_new_connection()
{
    $name   = $this->input->post('name', true);
    $amount = $this->input->post('amount', true);

    require_once APPPATH . 'class/NetworkonlieBitmapPaymentIntegration.php';

    $networkOnlineArray = [
        'Field_Existence_Indicator_Transaction' => [
            'amount' => $amount,
        ],
        'Field_Existence_Indicator_Billing' => [
            'billToFirstName' => $name,
        ],
    ];

    $networkOnlineObject = new NetworkonlieBitmapPaymentIntegration(
        $networkOnlineArray
    );

    // 按该 SDK 的实际 API 获取参数。
    $requestParameter = $networkOnlineObject->getRequestParameter();

    $gatewayUrl = 'https://abcd.com/servlet';

    // 如果 $requestParameter 已经是查询字符串,例如 foo=1&bar=2
    $redirectUrl = $gatewayUrl . '?' . ltrim($requestParameter, '?');

    return $this->output
        ->set_content_type('application/json')
        ->set_output(json_encode([
            'success' => true,
            'redirect_url' => $redirectUrl,
        ]));
}

这里的 getRequestParameter() 只是示例名称,需要换成支付 SDK 实际提供的方法。原代码没有说明 $requestParameter 从何而来。如果引入的文件也没有定义它,PHP 就会将其视为未定义变量。

前端收到地址后再主动跳转:

$(document).ready(function () {
    $(".loader").hide();

    $("#connectionForm").on("submit", function (e) {
        e.preventDefault();

        $.ajax({
            url: "/controller/save_new_connection",
            type: "POST",
            dataType: "json",
            data: new FormData(this),
            contentType: false,
            processData: false,

            success: function (data) {
                if (data.success && data.redirect_url) {
                    window.location.href = data.redirect_url;
                    return;
                }

                alert(data.message || "无法创建支付请求");
            },

            error: function (xhr) {
                console.error(xhr.responseText);
                alert("支付请求失败");
            }
        });
    });
});

如果 $requestParameter 是关联数组,而不是已经编码好的查询字符串,应改用:

$redirectUrl = $gatewayUrl . '?' . http_build_query(
    $requestParameter,
    '',
    '&',
    PHP_QUERY_RFC3986
);

姓名、订单号等内容必须先编码,不能直接拼到 URL 中。

方案二:网关要求 POST 参数

不少支付网关要求浏览器以 POST 方式提交字段。这种情况下不能使用:

redirect($gatewayUrl);

也不能简单地使用:

window.location.href = gatewayUrl;

这两种写法通常只会发起 GET 请求。可以让 CodeIgniter 输出一个自动提交的表单:

public function save_new_connection()
{
    $name   = $this->input->post('name', true);
    $amount = $this->input->post('amount', true);

    require_once APPPATH . 'class/NetworkonlieBitmapPaymentIntegration.php';

    $networkOnlineArray = [
        'Field_Existence_Indicator_Transaction' => [
            'amount' => $amount,
        ],
        'Field_Existence_Indicator_Billing' => [
            'billToFirstName' => $name,
        ],
    ];

    $networkOnlineObject = new NetworkonlieBitmapPaymentIntegration(
        $networkOnlineArray
    );

    // 应由 SDK 或服务端业务逻辑生成。
    $requestParameter = $networkOnlineObject->getRequestParameter();

    $gatewayUrl = 'https://abcd.com/servlet';

    $fields = [
        'requestParameter' => $requestParameter,
    ];

    $html = '<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>正在跳转到支付页面</title>
</head>
<body>
    <p>正在跳转到支付页面,请稍候……</p>
    <form id="payment-form" method="post" action="' .
        htmlspecialchars($gatewayUrl, ENT_QUOTES, 'UTF-8') . '">';

    foreach ($fields as $key => $value) {
        $html .= '<input type="hidden" name="' .
            htmlspecialchars($key, ENT_QUOTES, 'UTF-8') .
            '" value="' .
            htmlspecialchars($value, ENT_QUOTES, 'UTF-8') .
            '">';
    }

    $html .= '</form>
<script>
    document.getElementById("payment-form").submit();
</script>
</body>
</html>';

    return $this->output
        ->set_content_type('text/html', 'UTF-8')
        ->set_output($html);
}

这个响应必须作为浏览器页面打开,不能继续放在 AJAX 请求中。让原表单直接提交到控制器即可:

<form
    id="connectionForm"
    method="post"
    action="/controller/save_new_connection"
>
    <!-- 表单字段 -->
    <button type="submit">继续支付</button>
</form>

同时删除拦截表单提交的代码:

e.preventDefault();

浏览器会先进入 CodeIgniter 控制器,再通过自动提交的表单跳转到支付网关。

如果 SDK 返回的不是单个 requestParameter,而是多个网关字段,就要分别生成对应的隐藏字段:

$fields = [
    'amount'      => $amount,
    'firstName'   => $name,
    'merchantId'  => $merchantId,
    'signature'   => $signature,
];

实际字段名、提交方式和签名规则都要以支付网关的接口文档为准。

为什么 echo 看起来没有生效

当前 AJAX 配置中声明了:

dataType: "json"

这意味着 jQuery 期望服务器返回合法的 JSON。如果控制器输出普通字符串、HTML、PHP 警告,或者引入的文件产生了额外输出,jQuery 会因解析失败而跳过 success,转入 error。

调试时可以先补上错误处理:

error: function (xhr, status, error) {
    console.log(status);
    console.log(error);
    console.log(xhr.responseText);
}

还需要逐项检查:

  • NetworkonlieBitmapPaymentIntegration.php 是否调用了 exit、die,或者再次执行重定向。
  • 类文件是否输出了 PHP warning、notice 或其他内容。
  • $requestParameter 是否确实已经赋值。
  • 控制器方法结尾是否缺少 }。
  • AJAX 的 url 是否指向正确的 CodeIgniter 路由。
  • JSON 响应前后是否混入了调试文本或 HTML。

如果类文件只用于声明类,建议使用:

require_once APPPATH . 'class/NetworkonlieBitmapPaymentIntegration.php';

不要依赖 include 文件顺带在全局作用域中创建 $requestParameter。通过明确的方法返回参数,排查问题会更容易。

支付流程中的注意事项

金额、订单号和签名必须由服务器生成并校验,不能直接信任浏览器提交的 amount。通常需要先在数据库中创建待支付订单,再根据该订单生成网关参数。

还要注意以下事项:

  • 使用 HTTPS。
  • 保证订单号唯一,避免重复支付。
  • 签名密钥只能保存在服务端。
  • 不要在 URL 中携带密钥、银行卡信息等敏感数据。
  • 支付结果应以后端回调验签为准,不能只相信用户浏览器返回的页面。
  • 网关要求 POST 时,应严格使用 POST 表单,不能擅自改成查询字符串。
  • cache: true 对这个 POST 请求没有必要,可以删除。

如果没有必须保留 AJAX 的交互需求,更稳妥的支付跳转流程是:浏览器正常提交到 CodeIgniter,控制器创建订单并生成支付参数,然后输出自动提交到网关的 POST 表单。

备注:内容仅供参考。