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 返回给前端。
推荐流程
- React 收集并验证表单数据。
- React 将数据发送到自己的后端接口。
- 后端将允许预填的字段转换成 Bambora API 要求的结构。
- 后端使用商户凭证创建 Checkout session。
- 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 创建成功,但支付页面仍未显示预填内容,可以检查以下几项:
- 字段是否放在 Bambora 要求的对象和层级中。
- 当前 Checkout 产品是否支持预填这些字段。
- 字段名、国家代码和地址格式是否符合接口要求。
- 创建 session 的响应或日志中是否有字段校验错误。
- 商户后台是否配置为由支付页面重新收集相关信息。
还要注意,银行卡号、CVC 等敏感支付字段通常不能由商户页面预填。React 表单适合收集订单联系人或账单信息,银行卡数据应交由 Bambora 托管的支付页面安全采集。
备注:内容仅供参考。
