Square Payment API 如何处理最小货币单位金额?
结论
收取 52.50 CAD 时,提交给 Square Payments API 的金额应使用最小货币单位:
52.50 CAD = 5250 cents
银行卡 nonce/token 只用于标识支付凭证。生成 nonce/token 时,通常不需要传入 5250。前端可以显示并接收 52.50,后端在创建支付请求前将其转换为 5250,再传给 amountMoney.amount。
数据流可以简化为:
前端显示 52.50 CAD
↓
前端生成 card nonce/token
↓
后端确认实际应付金额
↓
后端转换:52.50 → 5250
↓
Payments API:amountMoney.amount = 5250
前端如何处理
前端负责收集支付信息并生成 nonce/token。不要把金额写入银行卡号、nonce 或 token,也不要把 nonce 当成包含订单金额的数据。
前端可以将用户输入作为字符串发送给后端:
<input id="amount" type="text" inputmode="decimal" value="52.50">
const amount = document.querySelector('#amount').value;
// nonce/token 的具体获取方式取决于使用的 Square 前端 SDK。
const requestBody = {
sourceId: cardNonceOrToken,
amount: amount,
currency: 'CAD'
};
await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
更安全的做法是让前端只发送订单 ID 和支付 token:
await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderId: 'order_123',
sourceId: cardNonceOrToken
})
});
后端根据 orderId 从数据库读取商品价格、税费和优惠,再计算最终应收金额。浏览器提交的 amount 不能作为最终价格,因为用户可以修改前端请求。
后端如何转换金额
不要直接使用:
Math.round(parseFloat(amount) * 100)
二进制浮点数可能带来精度问题,parseFloat() 也会接受部分格式错误的输入。例如,parseFloat('52.50abc') 仍可能得到 52.5。
如果只处理固定为两位小数的 CAD,可以严格按字符串转换:
function cadToCents(value) {
const normalized = String(value).trim();
if (!/^\d+(?:\.\d{1,2})?$/.test(normalized)) {
throw new Error('Invalid CAD amount');
}
const [dollars, fraction = ''] = normalized.split('.');
const cents = fraction.padEnd(2, '0');
return Number(dollars) * 100 + Number(cents);
}
console.log(cadToCents('52.50')); // 5250
console.log(cadToCents('52.5')); // 5250
console.log(cadToCents('52')); // 5200
生产环境还要检查金额是否为正数,以及是否超出系统和支付接口允许的范围。
如果应用支持多种货币,不要假设所有货币都有两位小数。应使用可靠的货币元数据或十进制定点库,根据对应货币的小数位数进行转换。
在 Square SDK 中创建支付
创建支付时,将转换后的整数放入 amountMoney.amount:
const amountInCents = cadToCents('52.50');
const paymentRequest = {
sourceId: cardNonceOrToken,
idempotencyKey: crypto.randomUUID(),
amountMoney: {
amount: amountInCents,
currency: 'CAD'
}
};
// 根据实际安装的 Square Node.js SDK 版本调用 createPayment。
const response = await paymentsApi.createPayment(paymentRequest);
其中的金额应为:
amountMoney: {
amount: 5250,
currency: 'CAD'
}
不同版本的 Square Node.js SDK 对整数金额的 JavaScript 类型和调用方式可能有不同要求。有些版本接受 number,有些生成版本可能要求 BigInt:
amountMoney: {
amount: BigInt(5250),
currency: 'CAD'
}
具体应以当前安装版本的类型定义和方法签名为准,不要直接照搬其他版本示例中的类型。
关于前端金额参数
以下两种情况需要分别处理:
- 生成普通银行卡 nonce/token 时,金额通常不是 tokenization 的必要参数。
- 使用身份验证、数字钱包或其他支付流程时,前端 SDK 的特定方法可能要求提供金额。
如果某个前端方法明确要求 amount,就要按照该方法规定的格式传值。它可能要求正常货币格式,例如字符串 "52.50",而不是整数 5250。这个参数用于身份验证或钱包支付上下文,不能代替服务端 Payments API 中的 amountMoney.amount。
因此,即使前端流程使用了 "52.50",服务端创建支付时仍需提交:
amountMoney.amount = 5250
amountMoney.currency = CAD
注意事项
- 金额转换和最终校验应在后端完成。
- 前端提交的价格不能作为可信的最终价格。
- 保存金额时应尽量使用字符串或最小货币单位整数,避免使用浮点数。
- 每次创建支付都应使用唯一的
idempotencyKey。重试同一笔逻辑支付时,应复用原来的 key,避免重复扣款。 - 前端显示金额、身份验证金额和服务端实际扣款金额必须一致,否则可能造成验证失败、订单对账异常,或用户看到的价格与实际扣款不符。
备注:内容仅供参考。