如何用 JavaScript 创建 NBG Checkout Session
结论
不要在浏览器端 JavaScript 中直接使用 Basic Authentication 请求 NBG API。Basic Auth 会携带商户用户名和 API 密钥。如果把这些凭据写进前端代码,任何访问者都能通过开发者工具获取它们。
应按以下流程处理:
- 浏览器向自己的后端申请创建支付会话。
- 后端使用 Basic Authentication 调用 NBG 的 Create Checkout Session API。
- 后端从响应中读取
session.id。部分接口可能返回session_id,具体字段以实际文档和响应为准。 - 浏览器收到 Session ID 后,再初始化 Checkout 或执行后续支付代码。
服务端创建 Checkout Session
下面使用 Node.js 18+ 和 Express 演示。NBG 的接口版本、请求方法和字段可能因商户接入配置而不同,请以商户后台提供的 API 文档为准。
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/checkout-session", async (req, res) => {
try {
const merchantId = process.env.NBG_MERCHANT_ID;
const apiPassword = process.env.NBG_API_PASSWORD;
const apiVersion = process.env.NBG_API_VERSION;
const apiBaseUrl = process.env.NBG_API_BASE_URL;
if (!merchantId || !apiPassword || !apiVersion || !apiBaseUrl) {
return res.status(500).json({
error: "NBG API configuration is incomplete"
});
}
/*
* 很多基于 Mastercard Payment Gateway 的接口使用:
* username = merchant.<merchantId>
*
* 请以 NBG 为你的商户提供的认证用户名为准。
*/
const apiUsername =
process.env.NBG_API_USERNAME || `merchant.${merchantId}`;
const authorization = Buffer.from(
`${apiUsername}:${apiPassword}`,
"utf8"
).toString("base64");
const endpoint =
`${apiBaseUrl}/api/rest/version/${encodeURIComponent(apiVersion)}` +
`/merchant/${encodeURIComponent(merchantId)}/session`;
const orderId = req.body.orderId;
const amount = req.body.amount;
const currency = req.body.currency || "EUR";
if (!orderId || !amount) {
return res.status(400).json({
error: "orderId and amount are required"
});
}
const nbgResponse = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Basic ${authorization}`,
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
apiOperation: "CREATE_CHECKOUT_SESSION",
interaction: {
operation: "PURCHASE"
},
order: {
id: String(orderId),
amount: String(amount),
currency
}
})
});
const responseText = await nbgResponse.text();
let responseData;
try {
responseData = responseText ? JSON.parse(responseText) : {};
} catch {
responseData = { rawResponse: responseText };
}
if (!nbgResponse.ok) {
console.error("NBG Create Checkout Session failed", {
status: nbgResponse.status,
response: responseData
});
return res.status(502).json({
error: "Unable to create checkout session",
gatewayStatus: nbgResponse.status
});
}
const sessionId =
responseData.session?.id ??
responseData.session_id ??
responseData.sessionId;
if (!sessionId) {
console.error("NBG response does not contain a session ID", responseData);
return res.status(502).json({
error: "NBG response did not contain a session ID"
});
}
return res.json({ sessionId });
} catch (error) {
console.error("Checkout session error", error);
return res.status(500).json({
error: "Internal server error"
});
}
});
app.listen(3000, () => {
console.log("Server listening on http://localhost:3000");
});
环境变量可以按下面的形式配置,但不要将其提交到 Git:
NBG_MERCHANT_ID=your_merchant_id
NBG_API_USERNAME=merchant.your_merchant_id
NBG_API_PASSWORD=your_api_password
NBG_API_VERSION=your_api_version
NBG_API_BASE_URL=https://your-nbg-gateway-host
NBG_API_BASE_URL、版本号、认证用户名格式和请求体字段,都必须使用 NBG 为当前商户环境分配的实际值。测试环境与生产环境通常使用不同的地址和凭据。
浏览器端获取 Session ID
前端只请求自己的后端,不接触 NBG API 密钥:
async function createCheckoutSession(order) {
const response = await fetch("/api/checkout-session", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
orderId: order.id,
amount: order.amount,
currency: order.currency
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || "Failed to create checkout session");
}
return result.sessionId;
}
async function startCheckout() {
try {
const sessionId = await createCheckoutSession({
id: "ORDER-20260901-001",
amount: "25.00",
currency: "EUR"
});
console.log("Checkout session created:", sessionId);
/*
* 在这里调用 NBG Checkout SDK。
* 具体方法名称取决于 NBG 当前提供的前端 SDK。
*
* 例如:
*
* Checkout.configure({
* session: {
* id: sessionId
* }
* });
*
* Checkout.showPaymentPage();
*/
} catch (error) {
console.error("Unable to start checkout", error);
}
}
startCheckout();
如果页面中的其他代码依赖 Session ID,应将这些代码放在 await createCheckoutSession(...) 之后执行,不能在异步请求完成前读取结果。
Basic Authentication 的格式
Basic Authentication 使用以下请求头格式:
Authorization: Basic base64(username:password)
在 Node.js 中可以这样生成:
const encodedCredentials = Buffer.from(
`${username}:${password}`,
"utf8"
).toString("base64");
const authorizationHeader = `Basic ${encodedCredentials}`;
Base64 只是编码,不是加密,因此请求必须通过 HTTPS 发送。即使使用 HTTPS,也不能把商户密码写进浏览器代码。
金额和订单信息不能完全信任前端
为了说明调用过程,上面的示例从浏览器接收了 amount。正式系统不应直接使用用户提交的金额创建支付会话,因为请求内容可以被修改。
更安全的做法是让前端只提交订单编号:
body: JSON.stringify({
orderId: "ORDER-20260901-001"
})
后端再从数据库读取该订单的实际金额和币种:
const order = await findOrderById(req.body.orderId);
const requestBody = {
apiOperation: "CREATE_CHECKOUT_SESSION",
interaction: {
operation: "PURCHASE"
},
order: {
id: order.id,
amount: order.amount,
currency: order.currency
}
};
后端还应检查订单归属和支付状态,并防止同一订单被重复创建或重复支付。
常见问题
返回 401 Unauthorized
请检查以下内容:
- Basic Auth 用户名是否符合 NBG 要求的格式;
- API 密码是否属于当前商户;
- 是否在生产环境误用了测试凭据,或在测试环境误用了生产凭据;
- Merchant ID、API 版本和请求地址是否匹配;
Authorization是否包含Basic前缀;- 是否对完整的
username:password字符串进行了 Base64 编码。
浏览器报告 CORS 错误
这通常说明浏览器把请求直接发到了 NBG 网关。支付网关未必允许任意网站跨域调用,浏览器请求也会暴露认证凭据。应改由自己的后端请求 NBG。
找不到 session_id
不要预先认定返回字段一定是 session_id。不少支付网关会返回以下结构:
{
"session": {
"id": "SESSION_IDENTIFIER"
}
}
这种情况下应读取:
const sessionId = responseData.session.id;
可以在测试环境记录脱敏后的响应结构,再按实际字段进行处理。日志中不要输出 API 密码、完整银行卡信息或其他敏感支付数据。
接口不接受 POST
请求方法必须符合当前 NBG 文档对 Create Checkout Session 的定义。如果商户使用的版本要求 PUT,将示例中的方法改为:
method: "PUT"
不要根据其他银行或其他网关版本的示例推断请求方法。接口版本、Endpoint、请求字段和 Checkout SDK 初始化方式,都应以 NBG 向当前商户提供的文档为准。
备注:内容仅供参考。
