Payfort 客户端集成第二步出现 CORS Error
结论
这类 CORS Error 通常与请求参数无关。问题在于浏览器不允许前端 JavaScript 直接调用 PayFort 的支付接口。
PayFort(现 Amazon Payment Services)的部分流程可以在客户端完成,比如采集银行卡信息并获取 token_name。支付执行、签名生成及部分 3-D Secure 流程通常要由商户后端发起。不要在浏览器中使用 axios 直接调用支付接口,也不要把签名密钥放到前端。
为什么 ASP.NET 可以,浏览器却报错
ASP.NET 发送的是服务器到服务器的 HTTP 请求,不受浏览器同源策略限制。浏览器中的 axios 发送跨域请求时,目标服务器必须返回类似下面的响应头:
Access-Control-Allow-Origin: https://your-domain.example
你的请求还指定了:
Content-Type: application/json
这通常会触发 CORS 预检请求:
OPTIONS /payment-endpoint
如果 PayFort 接口不处理这个 OPTIONS 请求,或者没有返回允许跨域的响应头,浏览器会在真正发送 POST 之前将请求拦截。修改 Axios 配置解决不了这个限制,也不应使用 mode: "no-cors",因为代码仍然无法正常读取支付响应。
推荐的集成结构
支付流程应由客户端和后端分别处理:
- 客户端通过 PayFort 提供的令牌化流程获取
token_name。 - 客户端只将
token_name、订单编号等必要信息提交给自己的后端。 - 后端验证订单金额和用户身份。
- 后端生成
signature,再向 PayFort 发起支付请求。 - 如果响应要求进行 3-D Secure 验证,让浏览器跳转或提交表单到对应地址。
- 支付完成后,由后端校验返回参数、签名和最终交易状态。
数据流大致如下:
Browser
├─ 获取 token_name
└─ POST /api/payments
↓
Merchant Backend
├─ 从数据库读取订单金额
├─ 生成 signature
└─ POST PayFort API
↓
PayFort / 3-D Secure
前端代码示例
前端不再直接请求 payFortUrl2,而是调用自己的后端:
_getAPIData = (paymentData) => ({
token_name: parseQueryString("token_name"),
merchant_reference: paymentData.merchantReference,
remember_me: paymentData.cmsData.rememberMe,
language: paymentData.language
});
_get3dUrl = async (paymentData) => {
const response = await axios({
method: "POST",
url: "/api/payments",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
data: this._getAPIData(paymentData)
});
const result = response.data;
if (result.redirectUrl) {
window.location.assign(result.redirectUrl);
return;
}
return result;
};
前端提交的内容应尽量精简。如果后端可以根据登录用户和订单记录确定 amount、currency、customer_email 等数据,就不应直接信任客户端传入的值。
后端请求示例
下面用 Node.js/Express 展示处理流程。签名算法、字段顺序、请求格式和响应字段,应以你当前使用的 PayFort 接口文档为准:
import express from "express";
import axios from "axios";
const app = express();
app.use(express.json());
app.post("/api/payments", async (req, res) => {
const {
token_name,
merchant_reference,
remember_me,
language
} = req.body;
// 必须从可信数据源读取订单,不要采用客户端提交的金额。
const order = await loadOrder(merchant_reference);
if (!order || order.status !== "pending") {
return res.status(400).json({
error: "Invalid order"
});
}
const apiData = {
command: "PURCHASE",
access_code: process.env.PAYFORT_ACCESS_CODE,
merchant_identifier: process.env.PAYFORT_MERCHANT_IDENTIFIER,
merchant_reference: order.reference,
amount: order.amountInMinorUnits,
currency: order.currency,
customer_email: order.customerEmail,
customer_ip: getClientIp(req),
token_name,
remember_me,
language,
return_url: "https://merchant.example/payment/return"
};
apiData.signature = createPayFortSignature(
apiData,
process.env.PAYFORT_REQUEST_PHRASE
);
try {
const payfortResponse = await axios.post(
process.env.PAYFORT_PAYMENT_URL,
apiData,
{
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}
);
const data = payfortResponse.data;
res.json({
status: data.status,
responseCode: data.response_code,
responseMessage: data.response_message,
redirectUrl: data["3ds_url"] ?? null
});
} catch (error) {
res.status(502).json({
error: "Payment provider request failed"
});
}
});
createPayFortSignature() 只能存在于后端。不要将 request phrase、SHA phrase、API 密钥或其他商户凭证发送给浏览器。
3-D Secure 可能需要表单跳转
部分支付流程返回的不是可供 Axios 继续调用的 JSON API,而是要求浏览器跳转到发卡行的验证页面。这时应根据接口响应和对应的集成文档执行重定向,或者动态创建并提交 HTML 表单:
function submitRedirectForm(url, fields) {
const form = document.createElement("form");
form.method = "POST";
form.action = url;
Object.entries(fields).forEach(([name, value]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = String(value);
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
普通页面跳转或表单提交属于浏览器导航,与通过 Axios 读取跨域响应不同,因此不会以同样的方式受到 CORS 限制。
需要检查的事项
- 确认
payFortUrl2是令牌化接口、支付接口,还是 3-D Secure 跳转地址。 - 根据所用接口确认请求格式。某些端点可能要求使用 HTML form 或
application/x-www-form-urlencoded,未必接受 JSON。 amount通常要用最小货币单位表示,具体换算规则以币种和接口文档为准。customer_ip应由后端从可信代理头或连接信息中获取,不能直接相信前端提交的值。- 在后端校验 PayFort 返回的
signature,不要只根据前端跳转页面判断支付是否成功。 - 最终订单状态应以后端查询、回调通知或经过签名验证的支付响应为准。
- 不要通过浏览器扩展、公共 CORS 代理或关闭浏览器安全策略来绕过错误。这些方法不适用于生产支付环境。
当前代码最需要调整的是保留客户端令牌化流程,将第二步的 axios(payfortConfig) 移到商户后端,由后端完成签名、支付请求和响应验证。
备注:内容仅供参考。
