在 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 表单。
备注:内容仅供参考。
