PAYATHON 2026

支付接口下单时 total_amount 应使用 number 还是字符串?

支付老李

明确结论

biz_content 中的 total_amount 应按照接口文档的定义传入 JSON 字符串,例如:

{
  "total_amount": "88.00"
}

双引号属于 JSON 字符串语法,并不是金额内容的一部分。实际金额值是 88.00,不要传成包含引号字符的 "\"88.00\""。

使用 SDK 或 JSON 序列化工具时,直接给字段赋普通字符串即可,双引号会由工具自动生成,无需手动拼接或转义。

正确的传参方式

以 Java 为例:

Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", "ORDER_202609090001");
bizContent.put("total_amount", "88.00");
bizContent.put("subject", "订单标题");

String bizContentJson = objectMapper.writeValueAsString(bizContent);

序列化结果应类似:

{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "88.00",
  "subject": "订单标题"
}

如果使用支付 SDK 提供的业务模型,也应直接设置字符串:

model.setTotalAmount("88.00");

JavaScript 的处理方式相同:

const bizContent = {
  out_trade_no: "ORDER_202609090001",
  total_amount: "88.00",
  subject: "订单标题"
};

const serialized = JSON.stringify(bizContent);

不要把双引号写进字段值:

// 错误:金额值实际包含了双引号字符
const bizContent = {
  total_amount: '"88.00"'
};

这种写法序列化后会变成:

{
  "total_amount": "\"88.00\""
}

此时服务端收到的金额已经不是合法的十进制金额。

为什么传 number 有时也能成功

下面两个 JSON 中,total_amount 的字段类型不同:

{
  "total_amount": "88.00"
}
{
  "total_amount": 88.00
}

前者是字符串,后者是 number。部分服务端、SDK 或网关会自动转换类型,所以传入 number 未必会立即报错,但不能据此认为两种写法在所有接口、SDK 和签名流程中都等价。

金额通常更适合用十进制字符串表示,原因主要有两点:

  1. 可以避免浮点数精度问题。某些语言中的 0.1 + 0.2 并不严格等于 0.3。
  2. 可以保留明确的小数格式。number 类型的 88.00 序列化后可能变成 88,字符串 "88.00" 则能保留原格式。

只要接口文档将 total_amount 定义为 String,就应按字符串传递。

“订单信息有误”不一定是金额类型导致的

“订单信息有误,请与卖家联系”通常只是通用的页面提示,不能仅凭这句话判断 total_amount 的类型有误。排查时还要检查:

  • total_amount 是否为空、为零、为负数或超出接口允许的范围。
  • 金额中是否包含货币符号、千分位、空格或其他字符,例如 ¥88.00、1,000.00。
  • 小数位数是否符合接口要求。人民币金额通常最多保留两位小数,具体仍以所调用接口的字段说明为准。
  • biz_content 是否经过了重复 JSON 序列化。
  • 请求参数是否在签名后被修改。
  • 签名所用的参数值是否与实际发送的值完全一致。
  • app_id、商户签约状态、产品权限、网关环境和通知地址等参数是否正确。
  • out_trade_no 是否重复,当前订单状态是否允许再次发起支付。
  • 商品标题、超时时间和其他必填字段是否符合接口约束。

其中,重复序列化尤其需要留意。如果业务对象已经由 SDK 序列化,就不要再执行一次 JSON.stringify():

// 可能导致重复序列化
const first = JSON.stringify({
  total_amount: "88.00"
});

const second = JSON.stringify(first);

这时的 second 是一个 JSON 字符串,不再是原来的 JSON 对象。

推荐的排查步骤

  1. 在签名前记录最终的 biz_content,确认其中是:

    "total_amount":"88.00"

    而不是:

    "total_amount":"\"88.00\""
  2. 检查最终发出的请求参数,确认没有发生二次转义或二次序列化。

  3. 核对签名参数与实际发送参数是否一致,特别要注意字符集、空值过滤和 URL 编码的处理顺序。

  4. 查看接口同步响应中的 code、sub_code、msg 和 sub_msg。与支付页面上的通用提示相比,这些字段通常更有助于定位问题。

  5. 使用官方 SDK 时,优先通过业务模型或对象设置字段,避免手工拼接 biz_content。

注意事项

不要使用 float 或 double 计算金额。需要进行金额运算时,应使用十进制定点类型,例如 Java 的 BigDecimal,提交前再转换成普通的十进制字符串:

BigDecimal amount = new BigDecimal("88.00");
String totalAmount = amount.setScale(2, RoundingMode.UNNECESSARY).toPlainString();

model.setTotalAmount(totalAmount);

最终传参方式仍要以当前支付接口的参数定义为准。即使不同接口使用了相同的字段名,也不能只凭经验判断其类型和取值范围。如果文档明确将 total_amount 标为字符串,正确写法就是传入普通金额字符串 "88.00",不要在字符串内容中额外加入双引号。

备注:内容仅供参考。