PAYATHON 2026

如何用 JavaScript 创建 NBG Checkout Session

支付阿杰

结论

不要在浏览器端 JavaScript 中直接使用 Basic Authentication 请求 NBG API。Basic Auth 会携带商户用户名和 API 密钥。如果把这些凭据写进前端代码,任何访问者都能通过开发者工具获取它们。

应按以下流程处理:

  1. 浏览器向自己的后端申请创建支付会话。
  2. 后端使用 Basic Authentication 调用 NBG 的 Create Checkout Session API。
  3. 后端从响应中读取 session.id。部分接口可能返回 session_id,具体字段以实际文档和响应为准。
  4. 浏览器收到 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 向当前商户提供的文档为准。

备注:内容仅供参考。