#DevOps#独立开发

多币种定价与结算:Minor Unit、舍入分摊和 FX 证据链

把美元价格乘实时汇率再保留两位小数,会在零小数币种、供应商特殊 exponent、退款与结算换汇时产生不可解释差额。本文版本化价目表、FX Quote 和舍入点,并对账展示、扣款和到账币种。

本文要点

  • 主题聚焦:DevOps、独立开发
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年3月12日·更新于 2026年7月20日

产品把 99 美元乘汇率显示成日元,后端仍按“两位小数”把 10543.7 JPY 传给支付平台;退款服务又用当天汇率反算美元。客户、支付平台和总账最终出现三个都“差不多正确”的金额。ISO 4217 提供币种代码和 minor unit 基础,但支付平台可能对少数币种使用不同 exponent 或收单限制;展示金额、charge 金额和 settlement 到账也不是同一个事实。

AI 定价迁移处理价格版本与生效 cohort,AI 毛利版本化归因把最终收入和成本归到经营窗口。本篇负责一次购买从价目表、FX quote、舍入到 provider settlement 的金额身份,不用浮点近似替代资金账。

价目表必须直接声明币种语义

发布 pricing.currencyPriceBook.v1,每个 market/price 绑定币种代码、整数 minor amount、ISO exponent、provider exponent override、含税模式、有效 [start,end)、产品/周期、最大折扣、舍入 mode/scale、负数和退款规则、provider account 与 API version。不能只存 base USD 和一个实时公式,否则历史 checkout 无法复算,汇率停机也会让商品失去价格。

yaml
schema: pricing.currencyPriceBook.v1
priceBook: pro-global@11
market: JP
price: pro-monthly
presentment: {currency: JPY, amountMinor: 10544, exponent: 0}
settlementCurrency: USD
rounding: HALF_EVEN_AT_FINAL_LINE
providerCurrencyTable: adyen-currency-codes@2026-07-01
validFrom: 2026-08-01T00:00:00Z

所有资金 API 边界使用 (currency, integerMinor),中间比例用有界十进制或整数分数,并明确在哪一级舍入。先逐行舍入和先对订单总额舍入会得到不同结果;合同选择一种,并用稳定 largest-remainder 规则把余数分给 line ID,保证 sum(lines)=orderTotal。Stripe 的零小数和特殊币种规则、Adyen 的 minor-unit 差异必须锁定到 provider table generation,而不是把 ISO exponent 写死成永久事实。

动态本地化价格使用 fxQuote.v1:source/target、rate numerator/denominator、方向、provider、observedAt、expiresAt、spread、rounding 和 quote ID。Quote 只影响绑定它的 cart digest;过期后不能后台换率继续扣款。若产品承诺固定本地价,则直接从 price book 取整数金额,结算汇率变化属于平台损益,不回写客户订单。

Dispatch fence 与财务接受点

checkout 状态为 PRICED -> QUOTED -> CUSTOMER_CONFIRMED -> PREPARED -> DISPATCHING -> CHARGED -> SETTLEMENT_PENDING -> SETTLED -> RECONCILED,并显式包含 QUOTE_EXPIRED、CHARGE_UNKNOWN、CHARGE_REJECTED、SETTLEMENT_UNKNOWN 和 MISMATCH。客户确认 receipt 绑定 price-book/quote/cart/discount/tax root 与精确 presentment amount,但确认本身还不允许外部扣款。

payment ledger 以 CAS 匹配未过期 quote、customer-confirmed digest、当前 checkout generation 和 PREPARED,写入稳定 charge operation 后推进 DISPATCHING;这只是 dispatch fence,不是扣款已经发生。只有赢得 CAS 的 dispatcher 能向 provider 发送同一个 amount/currency/idempotency key。响应超时保持 CHARGE_UNKNOWN,按 provider payment ID 或幂等查询对账;禁止根据余额变化猜测成功或换键重扣。HTTP 幂等键正确性提供请求身份边界。

财务接受点是取回权威 provider payment receipt 后:接受函数匹配 provider account、payment ID、charge operation、amount、currency 和 idempotency key,再原子写 canonical charge receipt、平衡资金分录与 outbox,并推进 CHARGED。永久拒绝进入 CHARGE_REJECTED;无法证明成功或拒绝仍留在 CHARGE_UNKNOWN。charge receipt 固定 presentment 金额,balance transaction 再记录 settlement currency、provider FX rate、fee、net 和 availableAt。各阶段用多币种分录保存,不能在一个“amount”列中互相覆盖。退款引用原 charge 和原 presentment currency;部分退款按原 line allocation 生成新腿,实际 settlement reversal 单独记录。若账户使用预付余额,预付 Credits 账本仍在自己的币种批次内预留,不能把不同货币数字直接相加。AI 用量计量账本只提供可计费数量,不决定 FX。

注入 exponent、过期和结算错配

故障夹具覆盖 JPY 等零小数币种、provider 与 ISO exponent 不同、1/3 汇率、负 credit、三行各有半单位余数、quote 在点击与 CAS 之间过期、两个标签并发确认、provider 接受后超时、settlement 延迟、settle 到意外币种、部分退款、chargeback 和价目表边界。断言任何 binary float 进入资金路径为 0,同 fixture 的 line/total 摘要一致率 100%,currency/exponent 错配接受为 0。

发布门还要求所有 charge 的 priceBook/quote root 覆盖率 100%、line sum 与 order total 差额为 0、provider charge amount/currency 未解释差异为 0、settlement/fee 分录平衡率 100%、CHARGE_UNKNOWN 盲重试为 0,到账后 24 小时内 reconciliation 完成率至少 99.9%。上线先 shadow 现有单币种计算,再开放一个无自动换汇市场,最后按 1%、10% checkout 扩大。异常时关闭新 quote、把 price alias 切回上一代并阻止未确认订单 dispatch;已扣款交易只能原币退款或补偿,不能靠改汇率记录伪造回滚。

Related

相关推荐