代码 跨境收款金额差 100 倍:Stripe 零小数币种、PayPal 两位小数与「分」的换算坑

2026-10-04 09:01:23

跨境收款金额差 100 倍:Stripe 零小数币种、PayPal 两位小数与「分」的换算坑

同一笔订单,Stripe 收到的是 100 倍金额,PayPal 直接返回 400。问题不在业务逻辑,而在「金额」这个字段:Stripe 要的是币种最小计价单位的整数,PayPal 要两位小数字符串,微信支付 v3 是整数「分」,支付宝是两位小数字符串的「元」。本文按渠道逐一对照金额格式与零小数币种,并给出 ISK/UGX/HUF/TWD 特例、排查日志字段和基于 Decimal 的换算实现。

现象

踩坑后的表现分两类:

  • 反向放大:代码无脑 ×100,遇到 JPY、KRW 这类零小数币种,金额被放大 100 倍。应收 500 JPY,实收 50000 JPY。
  • 直接拒绝 / 对不上:PayPal 因格式问题返回 400,或购物项金额与汇总金额对不上。
INVALID_CURRENCY_AMOUNT_FORMAT   # 400,Amount 构造失败
ITEM_TOTAL_MISMATCH              # unit_amount × quantity 与 amount 汇总对不上

根因:最小计价单位不统一

每个渠道对 amount 的定义不是同一个东西:

  • 有的用币种最小计价单位(minor unit),必须是整数,不带小数点;
  • 有的用主单位小数字符串,比如 "10.00"。

而「最小计价单位」本身又随币种变化。Stripe 的规则是:除非文档另有标注,币种默认两位小数,×100;被标成 zero-decimal 的币种不乘。收 10 USD 传 1000,收 500 JPY 传 500,收 10 JPY 传 10。

一旦把这两套规则混在一个 amount * 100 里,零小数币种必然差 100 倍。

逐渠道对照

渠道字段类型单位
Stripeamount整数币种最小计价单位
PayPalamount.value字符串元,两位小数
微信支付 v3amount.total整数分
支付宝total_amount字符串元,两位小数

Stripe:minor unit 整数,外加四个特例

大部分币种是 ×100,zero-decimal 币种不乘。完整的币种清单以官方文档标注为准:Stripe 货币与最小单位、零小数货币锚点。

官方文档 "Special cases" 里还有四个容易漏的:

  • ISK 冰岛克朗:已转为零小数,但为向后兼容,必须按两位小数表示,小数位恒为 00。收 5 ISK 传 500,且不能收 ISK 小数。
  • UGX 乌干达先令:同上,收 5 UGX 传 500。
  • HUF 匈牙利福林:收款可以是两位小数,但提现(payout) 被当作零小数处理,手动 payout 必须是能被 100 整除的整数。余额 HUF 10.45 只能提 1000(即 HUF 10),传 1045 不行。
  • TWD 新台币:同 HUF,payout 必须能被 100 整除。

金额上下限也要注意:每笔有最低额,按结算币种,例如 0.50 USD、50 JPY、50 KRW、10 THB、175.00 HUF、4.00 HKD,完整表见官方文档;iDEAL 这类支付方式可以低到 amount=1。最高额按位数限制:多数卡 12 位(最多 999,999,999,999 minor unit),JPY 是 10 位(最大 9,999,999,999)。

PayPal:字符串,且必须是两位小数

PayPal 的金额是字符串,必须写成两位小数形式,例如 "100.00"。传 "12" 或 "12.0" 会被拒;即使不被拒,参与求和时也会对不上。

它还有一层校验:subtotal + tax + shipping 必须等于 total,社区里大量 400 都来自这里。购物项的 unit_amount × quantity 也必须与 amount 汇总一致,否则报 ITEM_TOTAL_MISMATCH。

金额小数位的要求随币种类型而定,以当期 REST v2 文档为准:PayPal Orders v2(amount 字段)。

微信支付 v3

amount.total 是整数,单位「分」。人民币是两位小数币种,1 元 = 100 分,所以这里可以放心 ×100。

支付宝

total_amount 是字符串,单位「元」,两位小数。它和微信走的是同一个币种规则,但字段类型完全不同。

定位排查

出问题时先看日志。每条发往渠道的请求,至少同时打印四样:

  1. 业务金额(Decimal 对象);
  2. 币种代码;
  3. 发往渠道的原始 amount 字符串或整数;
  4. 渠道返回的原始响应。

这四样凑齐,「是换算错还是格式错」一眼能分辨:第 3 项多两个 0 就是单位问题,第 3 项是 12 而不是 12.00 就是格式问题。

可复用换算实现

换算不要用 float,0.1 + 0.2 的坑在金额上会直接变成对账差异。用 Decimal 或整数分,并且维护一张显式的币种表,而不是把判断散在一堆 if 里。

from decimal import Decimal, ROUND_HALF_UP

# 文档中标记为 zero-decimal 的币种:不乘 100
# 完整清单以 https://docs.stripe.com/currencies#zero-decimal 为准
ZERO_DECIMAL = {"jpy", "krw"}

# 已转零小数、但为向后兼容仍须按两位小数表示的币种
TWO_DECIMAL_COMPAT = {"isk", "ugx"}

# payout 必须能被 100 整除的币种
PAYOUT_MULTIPLE_100 = {"huf", "twd"}


def to_stripe_minor(amount: Decimal, currency: str) -> int:
    """Stripe:币种最小计价单位的整数,币种代码用小写"""
    cur = currency.lower()
    if cur in ZERO_DECIMAL:
        if amount != amount.to_integral_value():
            raise ValueError(f"{currency} 不支持小数金额")
        return int(amount)
    # ISK / UGX 虽为零小数,仍按两位小数传入
    return int((amount * 100).to_integral_value(rounding=ROUND_HALF_UP))


def to_paypal_value(amount: Decimal, currency: str) -> str:
    """PayPal:两位小数字符串,币种代码用大写 ISO"""
    return str(amount.quantize(Decimal("0.01")))


def to_wechat_total(amount: Decimal) -> int:
    """微信支付 v3:整数,单位分"""
    return int((amount * 100).to_integral_value(rounding=ROUND_HALF_UP))


def to_alipay_total(amount: Decimal) -> str:
    """支付宝:字符串,单位元,两位小数"""
    return str(amount.quantize(Decimal("0.01")))

币种代码的大小写也要按渠道来:Stripe 要求小写,PayPal 用大写 ISO 代码("USD")。把「是否零小数」「是否有特例(ISK/UGX/HUF/TWD)」做成上面这种显式集合,比散在业务代码里的 if currency == "JPY" 好排查得多。

不适用与边界

  • 币种的小数位规则会调整,ISK、UGX 就是从两位小数转成零小数的。清单要跟着当期文档走,不能一次性写死了事。
  • ISK、UGX 属于「名义零小数、API 仍按两位」的兼容处理,不能直接扔进 ZERO_DECIMAL。
  • HUF、TWD 的限制只作用在 payout 上,收款路径不受「必须能被 100 整除」的约束;这两条链路要用不同分支。
  • PayPal 的金额小数位要求随币种类型而定,本文没有逐币种实测,接入时以当期 REST v2 文档为准。
  • 最低、最高金额限制按币种和支付方式各不相同,上线前要按实际结算币种核对。

自检清单

  • 每个渠道的 amount 字段类型确认过:整数还是字符串。
  • 零小数币种集合显式列出,换算时先查表再决定是否 ×100。
  • ISK/UGX 走「两位小数兼容」分支,HUF/TWD 的 payout 单独校验能否被 100 整除。
  • 全程 Decimal 或整数分,结算路径上没有 float。
  • 发往渠道的币种代码大小写符合该渠道要求。
  • PayPal 侧 subtotal + tax + shipping == total,购物项乘积与汇总一致。
  • 日志同时记录业务金额、币种、原始 amount、渠道原始返回。
复制全文 生成海报 支付 出海 Stripe PayPal 微信支付 接口对接

推荐文章

程序员茄子在线接单