PAYATHON 2026

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,避免重复扣款。
  • 前端显示金额、身份验证金额和服务端实际扣款金额必须一致,否则可能造成验证失败、订单对账异常,或用户看到的价格与实际扣款不符。

备注:内容仅供参考。