# 币种与金额校验

> 在创建支付请求前校验交易币种、金额精度和支付方式能力，避免因金额不合法被拒绝。

创建支付请求前，请在服务端一起校验 `orderCurrency`、`orderAmount` 和可用支付方式。`orderCurrency` 是三位 ISO 4217 交易币种；`orderAmount` 是按交易币种展示单位提交的正数十进制字符串，例如 USD 99.99 传 `99.99`，JPY 1000 可传 `1000` 或 `1000.00`。

<warning>

不要用 JavaScript `number`、二进制浮点数或临时四舍五入后的值直接生成 `orderAmount`。建议内部用整数 minor unit 值或 decimal library 计算，确认金额后再格式化成 Onerway API 需要的字符串。

</warning>

## 校验顺序

<steps level="3">

### 校验币种代码

`orderCurrency` 必须使用大写三位 ISO 4217 字母代码，例如 `USD`、`EUR`、`JPY`。不要传货币符号、国家代码、非官方缩写或大小写混用的值。

如果某个 ISO 4217 代码是基金、贵金属、测试代码或“无币种”代码，例如 `XAU`、`XTS`、`XXX`，不要用于支付请求，除非 Onerway 已明确为你的商户、支付方式和交易场景开通。

### 校验金额格式与精度

`orderAmount` 必须是正数十进制字符串，以 `.` 作为小数分隔符。不要包含逗号、空格、货币符号、正负号或指数写法。

| Minor unit | 校验规则 | 常见示例 |
| --- | --- | --- |
| `0` | 必须表示整数金额；可以不带小数，也可以只带全 0 小数 | `JPY` 可传 `1000` 或 `1000.00`；不要传 `4.12` |
| `2` | 最多两位小数；整数金额可以不带 `.00` | `USD` 可传 `100`、`100.00`、`99.99` |
| `3` | 最多三位小数；支付方式可能还有额外限制 | `KWD` 可传 `10`、`10.500`、`0.001` |

<note>

币种精度应来自你们维护的已开通币种配置，或来自 ISO 4217 minor unit 数据。不要把所有币种都固定成两位小数；零小数币种和三位小数币种需要单独处理。

</note>

### 校验支付方式能力

金额格式合法不代表交易一定可处理。支付方式、国家或地区、发卡机构、本地支付提供方、商户配置以及单笔限额都可能影响可用性。

在展示币种或让客户输入金额前，建议先调用[可用支付方式查询](/zh/payments/api-reference/endpoints/list-available-payment-methods)，按国家或地区、币种、金额和商户配置筛选可用方式。对本地支付方式，不要只按币种判断可用性。

### 校验订单合计

如果请求包含 `txnOrderMsg.products`、折扣、运费或其他订单明细，请用同一币种和同一精度计算合计，并确保明细金额与 `orderAmount` 一致。折扣项应按接口字段要求使用负数。

</steps>

## 请求示例

```json
{
  "orderAmount": "99.99",
  "orderCurrency": "USD"
}
```

```json
{
  "orderAmount": "1000.00",
  "orderCurrency": "JPY"
}
```

```json
{
  "orderAmount": "10.500",
  "orderCurrency": "KWD"
}
```

## 服务端校验示例

下面的示例只演示金额格式校验。生产环境应从你们的商户配置读取已开通币种、minor unit、最小金额、最大金额和可用支付方式。

```ts
type CurrencyRule = {
  minorUnit: number
  minAmount?: string
  maxAmount?: string
}

const enabledCurrencies: Record<string, CurrencyRule> = {
  USD: { minorUnit: 2, minAmount: '0.01' },
  JPY: { minorUnit: 0, minAmount: '1' },
  KWD: { minorUnit: 3, minAmount: '0.001' }
}

function validateOrderAmount(amount: string, currency: string) {
  const code = currency.trim().toUpperCase()
  const rule = enabledCurrencies[code]

  if (!rule) {
    return { valid: false, reason: 'invalid_currency_code' }
  }

  if (!/^\d+(?:\.\d+)?$/.test(amount)) {
    return { valid: false, reason: 'invalid_amount_format' }
  }

  const [, fraction = ''] = amount.split('.')
  const hasTooManyDecimals = fraction.length > rule.minorUnit
  const isWholeAmountWithZeroFraction = rule.minorUnit === 0 && /^0*$/.test(fraction)
  if (hasTooManyDecimals && !isWholeAmountWithZeroFraction) {
    return { valid: false, reason: 'invalid_minor_unit' }
  }

  if (/^0+(?:\.0+)?$/.test(amount)) {
    return { valid: false, reason: 'amount_must_be_positive' }
  }

  return { valid: true, code, minorUnit: rule.minorUnit }
}
```

<tip>

前端可以根据币种动态限制小数位并格式化展示；最终仍应以服务端校验结果为准。不要在服务端请求前静默截断或四舍五入客户已经确认的金额。

</tip>

## 常见拦截原因

| 场景 | 原因 | 处理方式 |
| --- | --- | --- |
| `JPY` 传 `4.12` | 零小数币种不能包含非零小数 | 改为整数金额或全 0 小数，例如 `1000` 或 `1000.00`；否则可能返回 `respCode=40000` 和 `Illegal parameter orderAmount` |
| `USD` 传 `99.999` | 超过两位小数 | 在客户确认金额前按业务规则处理精度 |
| `KWD` 被固定成两位小数 | 三位小数币种被通用金额组件误处理 | 输入控件和服务端校验都按币种配置动态设置精度 |
| 使用 `1,000.00` | API 金额不接受千分位分隔符 | 提交前移除展示格式，只保留 decimal string |
| 支付方式不可用 | 币种格式正确，但支付方式、地区或限额不匹配 | 先查询可用支付方式，或联系 Onerway 确认商户配置 |

## 参考资料

- [ISO 4217 currency codes](https://www.iso.org/iso-4217-currency-codes.html) — 核对三位字母代码、数字代码和 minor unit
- [SIX Financial Data Standards](https://www.six-group.com/en/products-services/financial-information/market-reference-data/data-standards.html) — 下载 ISO 4217 List One
- [可用支付方式查询](/zh/payments/api-reference/endpoints/list-available-payment-methods) — 按交易条件筛选可用支付方式
