Reactjs 集成 Square CreditCard payment 的字段与金额设置
结论
CreditCard 中的卡号、有效期、CVV 和邮编由 Square Web Payments SDK 托管,不能像普通的 React 输入框那样随意增删。
- Cardholder name 需要单独添加在
CreditCard外部,并由业务代码管理。 - 不建议用 CSS 隐藏邮编。邮编是否可以关闭,取决于 Square SDK、商户所在国家和当前使用的 SDK 版本。如果该版本没有提供公开的关闭选项,就应保留这个字段。
- 扣款金额不在
<CreditCard>上设置。前端可以显示金额,但后端调用 Square Payments API 时,必须通过amountMoney指定实际扣款金额。 - 后端不能直接相信前端提交的金额。生产环境中,前端应提交订单 ID,再由后端重新计算并校验金额。
在前端添加持卡人姓名和金额
可以把 Cardholder name 作为普通的 React 输入框放在 CreditCard 上方:
import { useState } from "react";
import {
PaymentForm,
CreditCard,
} from "react-square-web-payments-sdk";
const Payment = ({ orderId, amount = "10.00", currency = "USD" }) => {
const [cardholderName, setCardholderName] = useState("");
const [errorMessage, setErrorMessage] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleTokenReceived = async (token, verifiedBuyer) => {
if (token.status !== "OK" || !token.token) {
const message =
token.errors?.map((error) => error.message).join(", ") ||
"Card tokenization failed";
setErrorMessage(message);
return;
}
if (!cardholderName.trim()) {
setErrorMessage("Please enter the cardholder name");
return;
}
try {
setIsSubmitting(true);
setErrorMessage(null);
const response = await fetch("/api/pay", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceId: token.token,
orderId,
cardholderName: cardholderName.trim(),
verificationToken: verifiedBuyer?.token,
}),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || "Failed to process payment");
}
alert("Payment successful!");
} catch (error) {
setErrorMessage(error.message);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="container">
<p>
Amount: {currency} {amount}
</p>
<label htmlFor="cardholder-name">Cardholder name</label>
<input
id="cardholder-name"
name="cardholderName"
type="text"
autoComplete="cc-name"
value={cardholderName}
onChange={(event) => setCardholderName(event.target.value)}
disabled={isSubmitting}
required
/>
{errorMessage && <p role="alert">{errorMessage}</p>}
<PaymentForm
applicationId="sandbox-application-id"
locationId="location-id"
cardTokenizeResponseReceived={handleTokenReceived}
createVerificationDetails={() => ({
amount,
currencyCode: currency,
intent: "CHARGE",
billingContact: {
givenName: cardholderName.trim(),
},
})}
>
<CreditCard
buttonProps={{
disabled: isSubmitting,
css: {
backgroundColor: "#E56B6F",
fontSize: "14px",
color: "#fff",
"&:hover": {
backgroundColor: "#E56B6F",
},
},
}}
/>
</PaymentForm>
</div>
);
};
export default Payment;
createVerificationDetails 中的金额用于买家验证,例如 3-D Secure。这里的金额和币种必须与后端创建支付时使用的值一致,但这个配置本身不会完成扣款。
如果当前安装的版本不支持 createVerificationDetails,请按照该版本的类型定义或文档调整,不能自行添加一个不存在的属性并传给 CreditCard。
在后端设置扣款金额
Square 卡片组件只负责安全地生成一次性支付令牌,也就是 sourceId。真正的扣款由后端调用 Create Payment API 完成。
下面是后端逻辑的示意代码。具体的导入方式可能随 Square Node SDK 版本变化:
app.post("/api/pay", async (req, res) => {
try {
const { sourceId, orderId, verificationToken, cardholderName } = req.body;
if (!sourceId || !orderId) {
return res.status(400).json({
message: "Missing sourceId or orderId",
});
}
// 必须从数据库读取订单并在服务端计算金额。
const order = await getOrderFromDatabase(orderId);
if (!order || order.status === "paid") {
return res.status(400).json({
message: "Invalid or already paid order",
});
}
const paymentRequest = {
sourceId,
idempotencyKey: crypto.randomUUID(),
locationId: process.env.SQUARE_LOCATION_ID,
amountMoney: {
amount: order.totalInMinorUnits,
currency: order.currency,
},
note: `Cardholder: ${cardholderName}`,
};
if (verificationToken) {
paymentRequest.verificationToken = verificationToken;
}
const payment = await squareClient.payments.create(paymentRequest);
await markOrderAsPaid(orderId, payment);
res.json({
paymentId: payment.id,
status: payment.status,
});
} catch (error) {
res.status(500).json({
message: "Payment could not be completed",
});
}
});
amountMoney.amount 必须是以货币最小单位表示的整数。例如:
amountMoney: {
amount: 1050,
currency: "USD",
}
这段代码表示 USD 10.50,而不是 USD 1050.00。部分 Square Node SDK 版本可能要求写成 BigInt(1050),具体应以项目当前 SDK 的类型定义为准。
邮编字段
邮编是 Square 托管的安全卡片表单字段,通常用于地址验证和风险控制。不要这样处理:
/* 不建议这样处理 */
.postal-code-field {
display: none;
}
Square 的输入框通常放在受控 iframe 中,外部 CSS 不一定能够访问。强行隐藏邮编还可能影响验证、风控或支付成功率。
如果当前版本的 react-square-web-payments-sdk 明确提供了关闭 postal code 的公开配置,可以按照对应版本的文档设置。如果没有公开配置,就无法可靠地从 <CreditCard> 中移除邮编。即使业务不需要保存这个字段,也可以让 Square 收集并用于支付验证,无须写入自己的数据库。
其他需要调整的地方
原代码中的 onNonceGenerated 没有传给任何组件,所以不会执行。可以直接删除,也可以把相关错误处理并入 cardTokenizeResponseReceived。
另外还要注意:
applicationId和locationId必须属于同一个 Square 环境,不能混用 Sandbox 和 Production。- 后端需要保存并检查订单状态,防止重复扣款。
- 每次创建支付时都要使用唯一的
idempotencyKey。 - 前端只提交
orderId和支付令牌。最终金额、币种、折扣和税费均由后端确认。 - 不要记录完整 token、卡号、CVV 或其他敏感支付信息。
- Cardholder name 是业务自行采集的字段,不会自动成为 Square 卡片组件内部的认证字段。如果要把姓名传入买家验证流程,应分别采集
givenName和familyName,不能假设所有姓名都可以按空格准确拆分。
备注:内容仅供参考。
