PAYATHON 2026

Bambora Payment Service 与 React Application 集成

支付老李

结论

RedirectCheckout 不接收表单对象,也不会读取 React state 来自动预填支付页面。它的构造函数只需要 Bambora Checkout session token:

new RedirectCheckout(sessionToken);

如果需要预填信息,应在服务端创建 Checkout session 时,将 Bambora Checkout API 支持的字段写入 session 请求。React 负责把表单数据提交给后端;后端验证数据并创建 Bambora session,再把 session token 返回给前端。

推荐流程

  1. React 收集并验证表单数据。
  2. React 将数据发送到自己的后端接口。
  3. 后端将允许预填的字段转换成 Bambora API 要求的结构。
  4. 后端使用商户凭证创建 Checkout session。
  5. React 收到 session token 后调用 RedirectCheckout。

整体流程如下:

React form
    ↓
Your backend
    ↓
Bambora Checkout API(创建 session)
    ↓
session token
    ↓
new RedirectCheckout(sessionToken)

不要把表单对象作为第二个参数传入:

// 通常不会生效
new RedirectCheckout(sessionToken, formData);

只有当前使用的 SDK 文档明确声明支持该参数时,才能按文档使用。否则,这个对象不会成为支付页面的预填数据。

React 示例

下面的组件用受控表单保存数据。用户提交表单后,组件会向自己的后端申请 Checkout session:

import { useState } from "react";
import { RedirectCheckout } from "@bambora/checkout-sdk-web";

export default function CheckoutForm() {
  const [formData, setFormData] = useState({
    firstName: "",
    lastName: "",
    email: "",
    phone: "",
    address: "",
    postalCode: "",
    city: "",
    country: "DK",
  });

  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");

  function handleChange(event) {
    const { name, value } = event.target;

    setFormData((current) => ({
      ...current,
      [name]: value,
    }));
  }

  async function handleSubmit(event) {
    event.preventDefault();
    setError("");
    setSubmitting(true);

    try {
      const response = await fetch("/api/bambora/checkout-session", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(formData),
      });

      if (!response.ok) {
        throw new Error("无法创建支付会话");
      }

      const result = await response.json();

      if (!result.sessionToken) {
        throw new Error("服务端没有返回 session token");
      }

      new RedirectCheckout(result.sessionToken);
    } catch (err) {
      setError(err instanceof Error ? err.message : "支付初始化失败");
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="firstName"
        value={formData.firstName}
        onChange={handleChange}
        placeholder="First name"
        required
      />

      <input
        name="lastName"
        value={formData.lastName}
        onChange={handleChange}
        placeholder="Last name"
        required
      />

      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
        required
      />

      <input
        name="phone"
        value={formData.phone}
        onChange={handleChange}
        placeholder="Phone"
      />

      <input
        name="address"
        value={formData.address}
        onChange={handleChange}
        placeholder="Address"
      />

      <input
        name="postalCode"
        value={formData.postalCode}
        onChange={handleChange}
        placeholder="Postal code"
      />

      <input
        name="city"
        value={formData.city}
        onChange={handleChange}
        placeholder="City"
      />

      <button type="submit" disabled={submitting}>
        {submitting ? "正在跳转…" : "继续支付"}
      </button>

      {error && <p role="alert">{error}</p>}
    </form>
  );
}

这里的 state object 只是前端表单状态。支付页面能否显示预填内容,取决于后端创建的 Bambora session,而不是传给 RedirectCheckout 的参数。

后端示例

后端接口可以按下面的方式组织。Bambora 的具体 endpoint、认证方式和字段名称可能因账号所在地区、Checkout 产品和 API 版本而异。因此,示例中的请求结构需要替换为当前官方文档规定的实际结构。

import express from "express";

const app = express();

app.use(express.json());

app.post("/api/bambora/checkout-session", async (req, res) => {
  const {
    firstName,
    lastName,
    email,
    phone,
    address,
    postalCode,
    city,
    country,
  } = req.body;

  if (!firstName || !lastName || !email) {
    return res.status(400).json({
      error: "Missing required customer information",
    });
  }

  const checkoutRequest = {
    // 金额、货币、订单号等交易信息应由服务端生成或核验。
    order: {
      // amount: ...,
      // currency: ...,
      // orderId: ...,
    },

    // 请根据当前 Bambora Checkout API 文档替换字段名称和层级。
    customer: {
      firstName,
      lastName,
      email,
      phone,
      address,
      postalCode,
      city,
      country,
    },

    // returnUrl: "https://example.com/payment/complete",
  };

  try {
    const bamboraResponse = await fetch(process.env.BAMBORA_CHECKOUT_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",

        // 按 Bambora 文档要求设置认证信息。
        Authorization: process.env.BAMBORA_AUTHORIZATION,
      },
      body: JSON.stringify(checkoutRequest),
    });

    if (!bamboraResponse.ok) {
      const responseBody = await bamboraResponse.text();

      console.error("Bambora session creation failed:", responseBody);

      return res.status(502).json({
        error: "Unable to create checkout session",
      });
    }

    const session = await bamboraResponse.json();

    // 根据实际响应结构提取 token。
    const sessionToken = session.token;

    if (!sessionToken) {
      return res.status(502).json({
        error: "Bambora did not return a session token",
      });
    }

    return res.json({ sessionToken });
  } catch (error) {
    console.error("Checkout request failed:", error);

    return res.status(500).json({
      error: "Checkout initialization failed",
    });
  }
});

示例中的 customer、order、token 和 Authorization 只是用来说明处理方式,不代表所有 Bambora Checkout API 版本都采用这些确切名称。实施时,应以当前账号对应的欧洲 Checkout API 文档和实际响应为准。

为什么必须通过后端创建 session

支付 session 往往包含金额、货币、订单号、回调地址和客户信息。如果直接在浏览器中创建 session,可能出现以下问题:

  • 商户密钥或认证信息可能暴露。
  • 用户可以修改金额、货币或订单号。
  • 服务端无法可靠地将支付结果关联到内部订单。
  • 前端提交的姓名、地址和金额都不能视为可信数据。

金额和商品信息应从服务端的订单记录中读取。即使 React 提交了金额,后端也必须重新计算或核验,不能直接采用。

预填字段仍为空时

如果 session 创建成功,但支付页面仍未显示预填内容,可以检查以下几项:

  1. 字段是否放在 Bambora 要求的对象和层级中。
  2. 当前 Checkout 产品是否支持预填这些字段。
  3. 字段名、国家代码和地址格式是否符合接口要求。
  4. 创建 session 的响应或日志中是否有字段校验错误。
  5. 商户后台是否配置为由支付页面重新收集相关信息。

还要注意,银行卡号、CVC 等敏感支付字段通常不能由商户页面预填。React 表单适合收集订单联系人或账单信息,银行卡数据应交由 Bambora 托管的支付页面安全采集。

备注:内容仅供参考。