如何使用 cURL 和 POST data 跳转到 payment gateway
支付阿杰
结论
cURL 发起的是服务器端请求,无法让用户浏览器携带 POST data 跳转到 payment gateway。
如果需要实现类似表单 submit 的效果,可以向浏览器返回一个 HTML 表单,把 payment gateway 设为 action,再通过 JavaScript 自动提交。这样,POST 请求会由用户浏览器直接发送到 payment gateway,地址栏也会随之进入支付页面。
为什么 cURL 无法实现跳转
当前代码的请求过程发生在服务器端:
用户浏览器 → 你的服务器 → cURL → payment gateway
当 CURLOPT_RETURNTRANSFER 设为 0 时,cURL 会把收到的响应直接输出到当前页面。用户看到的内容可能来自 payment gateway,但浏览器访问的仍是你的域名,所以地址栏不会变化。
CURLOPT_FOLLOWLOCATION 也只会让服务器端的 cURL 跟随重定向,无法控制用户浏览器。
普通的 HTTP Location 重定向通常会发起 GET 请求,也无法携带一组新构造的 POST data:
header('Location: https://paymentgateway.com/script');
exit;
只有在 gateway 支持通过 URL 参数或支付会话 ID 进入支付页面时,才能使用这种方式。
使用自动提交的 POST 表单
可以生成一个隐藏表单,将支付参数写入隐藏字段,然后自动提交:
<?php
$gatewayUrl = 'https://paymentgateway.com/script';
$postdata = [
'merchant_id' => 'your_merchant_id',
'amount' => '100.00',
'currency' => 'CNY',
'order_id' => 'ORDER-123456',
'return_url' => 'https://example.com/payment/return',
];
?>
<!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') ?>"
>
<?php foreach ($postdata as $name => $value): ?>
<input
type="hidden"
name="<?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>"
value="<?= htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8') ?>"
>
<?php endforeach; ?>
<noscript>
<button type="submit">继续支付</button>
</noscript>
</form>
<script>
document.getElementById('payment-form').submit();
</script>
</body>
</html>
浏览器收到页面后,请求过程会变成:
用户浏览器 → POST payment gateway
payment gateway 的响应会直接加载到浏览器中,不再由你的服务器代为获取。
更推荐的支付会话模式
如果 payment gateway 提供”创建订单”或”创建支付会话”API,通常可以采用以下流程:
- 服务器通过 cURL 调用 gateway API。
- gateway 返回支付地址、token 或 session ID。
- 服务器使用
Location将浏览器重定向到支付地址。
例如:
<?php
$connection = curl_init('https://paymentgateway.com/api/payment-sessions');
curl_setopt_array($connection, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'amount' => '100.00',
'currency' => 'CNY',
'order_id' => 'ORDER-123456',
'return_url' => 'https://example.com/payment/return',
]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($connection);
if ($response === false) {
throw new RuntimeException(curl_error($connection));
}
$statusCode = curl_getinfo($connection, CURLINFO_RESPONSE_CODE);
curl_close($connection);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException('Payment gateway returned HTTP ' . $statusCode);
}
$result = json_decode($response, true, flags: JSON_THROW_ON_ERROR);
if (empty($result['payment_url'])) {
throw new RuntimeException('Payment URL is missing');
}
header('Location: ' . $result['payment_url'], true, 303);
exit;
这里的 API 地址、字段和响应结构仅用于说明流程,实际代码必须按照 payment gateway 的接口文档调整。
注意事项
- 不要把 API key、签名密钥等机密信息写入隐藏表单。用户可以查看和修改表单内容。
- 金额、订单号和回调地址等关键字段应由服务器签名,并在支付回调中验证签名。
- 不要只根据用户跳转回来时携带的页面参数判断支付是否成功。最终状态应通过 gateway 的服务端通知或订单查询接口确认。
- 隐藏字段依然是用户可控数据,不能因为字段不可见就直接信任。
- 除非系统已经符合相应的安全规范,否则不要收集、记录或转发银行卡号、CVV 等敏感支付信息。
- PHP 在输出表单前不能提前发送其他响应内容,否则可能破坏页面结构,也可能影响后续的
header()调用。 - 如果 gateway 要求重复字段、嵌套字段或特定编码方式,应严格按照其文档生成表单,不能只依赖简单的关联数组循环。
- HTTP
307和308在特定情况下可以保留原请求的方法和请求体,但无法让服务器替浏览器构造一份新的 POST body,因此通常不适合这里的需求。
备注:内容仅供参考。
