多币种与外汇报价
Quick Reference
为什么要提供多币种
当客户在结账页面看到外币金额时,会因为不确定实际扣款金额而放弃付款。多币种定价消除了这种不确定性。
| 没有多币种 | 使用 A55 多币种 |
|---|---|
| 客户看到 R$ 500.00,但不知道自己的卡会被扣多少钱 | 客户看到 US$ 87.26——这就是卡片扣款的确切金额 |
| 客户不知道商户使用了什么汇率 | 您展示银行间市场汇率,完全透明 |
| 客户收到账单后因金额不符发起争议 | 价格与扣款金额一致,不会产生争议 |
| 跨境客户在结账环节流失 | 国际访客看到自己货币的价格,完成支付 |
可衡量的业务影响:
- 更高的转化率:客户看到自己货币的价格时更愿意购买。
- 更少的 chargeback(拒付):透明定价减少了"金额不符"的争议。
- 竞争优势:提供与 Amazon、Shopify、Stripe 等全球平台相同的体验。
- 拉美覆盖:8 种货币,7 个国家——USD、BRL、EUR、MXN、ARS、COP、CLP、PEN。
巴西商户可以为国际买家显示 USD、EUR 或 MXN 价格。SaaS(软件即服务)平台可以按客户所在地货币显示订阅费用。电商网站可以让买家在结账时切换货币。
工作原理——三个步骤
多币种收费分为三步:获取汇率、展示价格、创建收费。
标准流程(2 位小数货币——BRL):
零小数流程(CLP——金额必须为整数):
步骤 1——获取汇率
调用 FX(外汇)端点,获取两种货币之间的当前银行间市场汇率。
端点: POST /api/v1/bank/wallet/fx/rate/
请求参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
from_currency | string | 是 | 源货币,ISO 4217(国际标准货币代码)。示例:USD |
to_currency | string | 是 | 目标货币,ISO 4217 代码。示例:BRL |
响应:
| 字段 | 类型 | 说明 |
|---|---|---|
price | float | 汇率,四舍五入到小数点后 2 位。含义:1 单位源货币 = price 单位目标货币 |
- cURL
- Python
- JavaScript
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/' \
-H 'Authorization: Bearer 您的_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"from_currency": "USD",
"to_currency": "BRL"
}'
import requests
url = "https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/"
headers = {
"Authorization": "Bearer 您的_ACCESS_TOKEN",
"Content-Type": "application/json",
}
payload = {
"from_currency": "USD",
"to_currency": "BRL",
}
response = requests.post(url, json=payload, headers=headers)
rate = response.json()["price"]
print(f"1 USD = {rate} BRL") # 输出当前汇率
const response = await fetch(
"https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/",
{
method: "POST",
headers: {
"Authorization": "Bearer 您的_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
from_currency: "USD",
to_currency: "BRL",
}),
}
);
const { price: rate } = await response.json();
console.log(`1 USD = ${rate} BRL`); // 输出当前汇率
响应示例:
{
"price": 5.73
}
含义:此刻 1 USD = 5.73 BRL。
步骤 2——向客户展示转换后的价格
用汇率计算目标货币的价格,在结账页面展示给客户。这一步在您的应用中完成,无需调用 A55 API(应用程序编程接口)。
示例(BRL——2 位小数): 产品售价 US$ 100.00,汇率 5.73。
BRL 价格 = US$ 100.00 × 5.73 = R$ 573.00
| 客户看到的内容 | 值 |
|---|---|
| 产品价格 | US$ 100.00 |
| 汇率 | 1 USD = 5.73 BRL |
| 应付金额 | R$ 573.00 |
示例(CLP——零小数货币): 产品售价 US$ 100.00,汇率 950.73。
CLP 价格 = US$ 100.00 × 950.73 = CLP 95,073(四舍五入为整数)
| 客户看到的内容 | 值 |
|---|---|
| 产品价格 | US$ 100.00 |
| 汇率 | 1 USD = 950.73 CLP |
| 应付金额 | CLP $95,073 |
对于零小数货币,收费请求中的金额必须为整数。发送 95073,而非 95073.00。后端会截断小数部分。
在结账页面同时显示原始价格和转换后的价格。客户能看到确切的汇率和卡片将被扣除的确切金额,这能显著提升信任度。Stripe、Wise、Adyen 等全球领先支付平台均采用此做法。
步骤 3——创建收费
用结算货币(钱包的货币)创建收费。金额为步骤 2 中计算出的转换值。
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/' \
-H 'Authorization: Bearer 您的_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"wallet_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"merchant_id": "11111111-1111-1111-1111-111111111111",
"payer_name": "张伟",
"payer_email": "zhangwei@example.com",
"payer_tax_id": "12345678909",
"payer_cell_phone": "+5511999999999",
"installment_value": 573.00,
"currency": "BRL",
"due_date": "2026-04-30",
"description": "订单 #12345(US$ 100.00,汇率 5.73)",
"type_charge": "credit_card",
"installment_count": 1,
"payer_address": {
"street": "Av. Paulista",
"address_number": "1000",
"complement": "Sala 101",
"neighborhood": "Bela Vista",
"city": "São Paulo",
"state": "SP",
"postal_code": "01310-100",
"country": "BR"
}
}'
将汇率写入 description 字段(如 "订单 #12345(US$ 100.00,汇率 5.73)")。这为对账和争议处理提供了审计记录。
收费响应包含自动多币种转换:
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"local_currency": 573.00,
"currency": "BRL",
"usd_currency": 100.00,
"eur_currency": 91.45,
"type": "credit_card",
"status": "confirmed",
"installment_count": 1,
"installments": [
{
"local_currency": 573.00,
"currency": "BRL",
"usd_currency": 100.00,
"eur_currency": 91.45,
"due_date": "2026-04-30",
"status": "confirmed",
"installment_number": 1
}
]
}
每笔收费响应自动包含三种货币的金额:
| 字段 | 说明 | 示例 |
|---|---|---|
local_currency | 钱包结算货币的金额 | 573.00(BRL) |
usd_currency | 等值美元金额 | 100.00(USD) |
eur_currency | 等值欧元金额 | 91.45(EUR) |
这三个字段可直接用于多币种报告、对账和数据分析,无需再次调用汇率端点。
完整集成示例
此示例演示完整流程——认证、获取汇率、计算价格、创建收费。
- Python
- JavaScript
from decimal import Decimal, ROUND_HALF_UP
import requests
BASE = "https://sandbox.api.a55.tech/api/v1"
AUTH_URL = "https://auth.a55.tech/oauth2/token"
# 认证——获取访问令牌
token_resp = requests.post(AUTH_URL, data={
"grant_type": "client_credentials",
"client_id": "您的_CLIENT_ID",
"client_secret": "您的_CLIENT_SECRET",
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
token = token_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
# 步骤 1:获取最新汇率
target_currency = "BRL" # 改为 "CLP" 即可切换至智利比索
fx_resp = requests.post(f"{BASE}/bank/wallet/fx/rate/", json={
"from_currency": "USD",
"to_currency": target_currency,
}, headers=headers)
rate = fx_resp.json()["price"]
print(f"汇率:1 USD = {rate} {target_currency}")
# 步骤 2:使用 Decimal 精确计算转换后的价格
product_price_usd = 100.00
ZERO_DECIMAL_CURRENCIES = {"CLP", "COP"}
amount = Decimal(str(product_price_usd)) * Decimal(str(rate))
if target_currency in ZERO_DECIMAL_CURRENCIES:
charge_amount = int(amount.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
else:
charge_amount = float(amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
print(f"收费金额:{charge_amount} {target_currency}")
# 步骤 3:以结算货币创建收费
charge_resp = requests.post(f"{BASE}/bank/wallet/charge/", json={
"wallet_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"merchant_id": "11111111-1111-1111-1111-111111111111",
"payer_name": "张伟",
"payer_email": "zhangwei@example.com",
"payer_tax_id": "12345678909",
"payer_cell_phone": "+5511999999999",
"installment_value": charge_amount,
"currency": target_currency,
"due_date": "2026-04-30",
"description": f"订单 #12345(US$ {product_price_usd},汇率 {rate})",
"type_charge": "credit_card",
"installment_count": 1,
"payer_address": {
"street": "Av. Paulista", "address_number": "1000",
"complement": "Sala 101", "neighborhood": "Bela Vista",
"city": "São Paulo", "state": "SP",
"postal_code": "01310-100", "country": "BR",
},
}, headers=headers)
charge = charge_resp.json()
print(f"收费 UUID:{charge['charge_uuid']}")
print(f"本地货币({target_currency}):{charge['local_currency']}")
print(f"美元:US$ {charge['usd_currency']}")
print(f"欧元:€{charge['eur_currency']}")
const BASE = "https://sandbox.api.a55.tech/api/v1";
const AUTH_URL = "https://auth.a55.tech/oauth2/token";
// 认证——获取访问令牌
const tokenResp = await fetch(AUTH_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: "您的_CLIENT_ID",
client_secret: "您的_CLIENT_SECRET",
}),
});
const { access_token } = await tokenResp.json();
const headers = {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
};
// 步骤 1:获取最新汇率
const targetCurrency = "BRL"; // 改为 "CLP" 即可切换至智利比索
const fxResp = await fetch(`${BASE}/bank/wallet/fx/rate/`, {
method: "POST",
headers,
body: JSON.stringify({ from_currency: "USD", to_currency: targetCurrency }),
});
const { price: rate } = await fxResp.json();
console.log(`汇率:1 USD = ${rate} ${targetCurrency}`);
// 步骤 2:根据货币类型选择正确的精度
const productPriceUsd = 100.0;
const ZERO_DECIMAL = new Set(["CLP", "COP"]);
const chargeAmount = ZERO_DECIMAL.has(targetCurrency)
? Math.round(productPriceUsd * rate) // 零小数货币:四舍五入为整数
: Math.round(productPriceUsd * rate * 100) / 100; // 其他货币:保留 2 位小数
console.log(`收费金额:${chargeAmount} ${targetCurrency}`);
// 步骤 3:创建收费
const chargeResp = await fetch(`${BASE}/bank/wallet/charge/`, {
method: "POST",
headers,
body: JSON.stringify({
wallet_uuid: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
merchant_id: "11111111-1111-1111-1111-111111111111",
payer_name: "张伟",
payer_email: "zhangwei@example.com",
payer_tax_id: "12345678909",
payer_cell_phone: "+5511999999999",
installment_value: chargeAmount,
currency: targetCurrency,
due_date: "2026-04-30",
description: `订单 #12345(US$ ${productPriceUsd},汇率 ${rate})`,
type_charge: "credit_card",
installment_count: 1,
payer_address: {
street: "Av. Paulista", address_number: "1000",
complement: "Sala 101", neighborhood: "Bela Vista",
city: "São Paulo", state: "SP",
postal_code: "01310-100", country: "BR",
},
}),
});
const charge = await chargeResp.json();
console.log(`收费 UUID:${charge.charge_uuid}`);
console.log(`本地货币(${targetCurrency}):${charge.local_currency}`);
console.log(`美元:US$ ${charge.usd_currency}`);
console.log(`欧元:€${charge.eur_currency}`);
智利比索(CLP)深度指南——零小数货币的精确报价与收费
智利比索(CLP)是一种零小数货币(ISO 4217 指数为 0)。CLP 没有"分"(centavo)——最小单位是 CLP 1。当您从 USD、EUR 或任何 2 位小数货币转换为 CLP 时,会遇到独特的计算精度挑战。本节详细解释计算陷阱、正确的舍入方法,并提供完整的端到端代码示例。
为什么 CLP 需要特别关注
CLP 与 BRL、MXN 等 2 位小数货币有三个关键差异:
| 属性 | BRL(2 位小数) | CLP(零小数) | 影响 |
|---|---|---|---|
| 最小单位 | R$ 0.01(分) | CLP $1(比索) | CLP 金额必须是整数 |
| 每 USD 对应数量级 | 约 5.7 BRL | 约 950 CLP | 舍入误差被放大约 167 倍 |
| 汇率 × 金额精度 | 100.00 × 5.73 = 573.00(精确) | 100.00 × 950.73 = 95073.0(浮点运算中可能不精确) | 浮点运算可能产生小数结果 |
| 后端行为 | 存储为 Numeric(11,2) | 截断为整数后发送至收单机构 | 95072.7 变为 95072——损失 CLP 1 |
1 美分的 USD 舍入误差(约 CLP 10)在 BRL 中几乎不可见(R$ 0.01),但在 CLP 中会产生显著差异。跨数百万笔交易,系统性的舍入偏差会累积为重大定价错误。2001 年比利时法郎转换为欧元时出现了相同的模式:累积舍入误差导致消费者价格指数上升了 0.54%–0.72%。
精度陷阱——IEEE 754 与浮点运算
计算机使用二进制表示十进制数(IEEE 754(浮点数标准))。大多数十进制小数无法被精确表示:
// IEEE 754 浮点精度演示
0.1 + 0.2 // 0.30000000000000004(不是 0.3)
99.99 * 950.73 // 95063.4927——可能有尾随数字
100.00 * 950.73 // 95073.0(恰好精确)
149.99 * 950.73 // 142599.9927——不是整数,必须四舍五入为 142600
Math.floor(142599.9927) // 142599——错误,损失了 CLP 1(正确值为 142600)
危险在于间歇性:某些乘法结果是精确的,另一些则不是。您不能依赖浮点数"足够接近"——必须显式执行舍入。
比利时法郎的教训(2001 年): 比利时以固定汇率 40.3399 将 BEF(比利时法郎)转换为 EUR(欧元)时,商户对转换后的价格使用了 floor()(向下取整)。跨数百万笔交易,这种系统性截断使每 101 EUR 交易中约 0.01 EUR 从消费者转移到商户手中。比利时消费者组织 Test-Achats 记录了这一模式。教训:永远不要截断货币转换结果——始终四舍五入到最接近的整数。
"延迟舍入"原则: 所有中间运算保持完整的浮点精度(IEEE 754 double 类型中的 15–17 位有效数字)。仅在最后一步舍入到目标货币的小数位数。每次中间舍入都会引入误差并逐步累积。这是 Stripe、Wise、Adyen 等全球领先支付平台在生产环境中使用的方法。
CLP 的正确舍入——为什么 round() 还不够
Python 和 JavaScript 对 0.5 中间值采用不同的舍入策略:
| 语言 | 表达式 | 结果 | 舍入模式 |
|---|---|---|---|
| Python | round(95072.5) | 95072 | 银行家舍入(half-to-even,向偶数舍入) |
| JavaScript | Math.round(95072.5) | 95073 | 四舍五入(half-up) |
| Python | round(95073.5) | 95074 | 银行家舍入(向偶数舍入) |
| JavaScript | Math.round(95073.5) | 95074 | 四舍五入 |
银行家舍入在大型数据集上最小化统计偏差,但对于单笔支付交易,客户和商户期望的是四舍五入(0.5 远离零方向舍入)。如果您的后端使用 Python 而前端使用 JavaScript,同一个乘法运算可能产生不同的整数金额。
推荐方案——使用 Decimal 类型:
- Python(Decimal)
- JavaScript(Math.round)
from decimal import Decimal, ROUND_HALF_UP
rate = 950.73
product_price = 149.99
amount = Decimal(str(product_price)) * Decimal(str(rate))
charge_amount = int(amount.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
# charge_amount = 142600(CLP)——确定性结果,无浮点意外
const rate = 950.73;
const productPrice = 149.99;
// Math.round 使用四舍五入——对 CLP 安全
const chargeAmount = Math.round(productPrice * rate);
// chargeAmount = 142600(CLP)——与 Python Decimal 结果一致
Math.floor(142599.9927) = 142599——但正确的收费金额是 CLP 142,600。parseInt("142599.9927") = 142599。两者都会静默丢弃小数部分,导致每笔交易系统性少收 CLP 1。跨数百万笔交易,这会造成可度量的收入损失。A55 后端同样使用截断(非舍入)——如果您发送 142599.99,它将变为 142599。
截断陷阱——A55 后端对 CLP 的处理方式
A55 后端在将金额路由至收单机构(acquirer)之前,对 CLP 和 COP 执行截断(非四舍五入)。这意味着:
| 您发送的值 | 后端接收 | 收单机构接收 | 问题 |
|---|---|---|---|
95073 | 95073 | 95073 | 无——正确 |
95073.00 | 95073 | 95073 | 无——截断无影响 |
95072.7 | 95072 | 95072 | 损失 CLP 1——客户被报价 CLP 95,073 |
95072.999 | 95072 | 95072 | 损失 CLP 1——浮点运算产生了错误值 |
提交前的防御性验证:
- Python
- JavaScript
def validate_clp_amount(amount: float) -> int:
"""验证 CLP 金额是否为整数"""
if amount != int(amount):
raise ValueError(f"CLP 金额必须为整数,收到 {amount}")
return int(amount)
function validateClpAmount(amount) {
// 验证 CLP 金额是否为整数
if (!Number.isInteger(amount)) {
throw new Error(`CLP 金额必须为整数,收到 ${amount}`);
}
return amount;
}
完整 CLP 示例——美元转智利比索
此示例展示完整流程:以 USD 报价产品,使用精确舍入,在 A55 中以 CLP 创建收费。
- cURL
- Python
- JavaScript
# 步骤 1:获取 USD → CLP 汇率
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/' \
-H 'Authorization: Bearer 您的_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"from_currency": "USD", "to_currency": "CLP"}'
# 响应:{"price": 950.73}
# 步骤 2:计算——US$ 100.00 × 950.73 = CLP 95,073(整数)
# 步骤 3:以 CLP 创建收费
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/' \
-H 'Authorization: Bearer 您的_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"wallet_uuid": "您的_CLP_钱包_UUID",
"merchant_id": "您的_MERCHANT_ID",
"payer_name": "张伟",
"payer_email": "zhangwei@example.com",
"payer_tax_id": "12.345.678-9",
"payer_cell_phone": "+56911111111",
"installment_value": 95073,
"currency": "CLP",
"due_date": "2026-04-30",
"description": "订单 #12345(US$ 100.00,汇率 950.73)",
"type_charge": "credit_card",
"installment_count": 1,
"payer_address": {
"street": "Av. Providencia",
"address_number": "1234",
"complement": "Depto 501",
"neighborhood": "Providencia",
"city": "Santiago",
"state": "RM",
"postal_code": "7500000",
"country": "CL"
}
}'
from decimal import Decimal, ROUND_HALF_UP
import requests
BASE = "https://sandbox.api.a55.tech/api/v1"
AUTH_URL = "https://auth.a55.tech/oauth2/token"
# 认证——获取访问令牌
token_resp = requests.post(AUTH_URL, data={
"grant_type": "client_credentials",
"client_id": "您的_CLIENT_ID",
"client_secret": "您的_CLIENT_SECRET",
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
token = token_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
# 步骤 1:获取最新的 USD → CLP 汇率
fx_resp = requests.post(f"{BASE}/bank/wallet/fx/rate/", json={
"from_currency": "USD",
"to_currency": "CLP",
}, headers=headers)
rate = fx_resp.json()["price"]
# 步骤 2:使用 Decimal 精确转换——ROUND_HALF_UP 舍入为整数
product_price_usd = 100.00
amount_decimal = Decimal(str(product_price_usd)) * Decimal(str(rate))
charge_amount = int(amount_decimal.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
# 步骤 3:验证并创建收费
assert charge_amount == int(charge_amount), "CLP 金额必须为整数"
charge_resp = requests.post(f"{BASE}/bank/wallet/charge/", json={
"wallet_uuid": "您的_CLP_钱包_UUID",
"merchant_id": "您的_MERCHANT_ID",
"payer_name": "张伟",
"payer_email": "zhangwei@example.com",
"payer_tax_id": "12.345.678-9",
"payer_cell_phone": "+56911111111",
"installment_value": charge_amount,
"currency": "CLP",
"due_date": "2026-04-30",
"description": f"订单 #12345(US$ {product_price_usd},汇率 {rate})",
"type_charge": "credit_card",
"installment_count": 1,
"payer_address": {
"street": "Av. Providencia", "address_number": "1234",
"complement": "Depto 501", "neighborhood": "Providencia",
"city": "Santiago", "state": "RM",
"postal_code": "7500000", "country": "CL",
},
}, headers=headers)
charge = charge_resp.json()
print(f"收费 UUID:{charge['charge_uuid']}")
print(f"CLP:{charge['local_currency']}")
print(f"美元:US$ {charge['usd_currency']}")
print(f"欧元:€{charge['eur_currency']}")
const BASE = "https://sandbox.api.a55.tech/api/v1";
const AUTH_URL = "https://auth.a55.tech/oauth2/token";
// 认证——获取访问令牌
const tokenResp = await fetch(AUTH_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: "您的_CLIENT_ID",
client_secret: "您的_CLIENT_SECRET",
}),
});
const { access_token } = await tokenResp.json();
const headers = {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
};
// 步骤 1:获取最新的 USD → CLP 汇率
const fxResp = await fetch(`${BASE}/bank/wallet/fx/rate/`, {
method: "POST",
headers,
body: JSON.stringify({ from_currency: "USD", to_currency: "CLP" }),
});
const { price: rate } = await fxResp.json();
// 步骤 2:转换——Math.round 使用四舍五入,对 CLP 安全
const productPriceUsd = 100.0;
const chargeAmount = Math.round(productPriceUsd * rate);
// 步骤 3:验证整数后创建收费
if (!Number.isInteger(chargeAmount)) {
throw new Error(`CLP 金额必须为整数,收到 ${chargeAmount}`);
}
const chargeResp = await fetch(`${BASE}/bank/wallet/charge/`, {
method: "POST",
headers,
body: JSON.stringify({
wallet_uuid: "您的_CLP_钱包_UUID",
merchant_id: "您的_MERCHANT_ID",
payer_name: "张伟",
payer_email: "zhangwei@example.com",
payer_tax_id: "12.345.678-9",
payer_cell_phone: "+56911111111",
installment_value: chargeAmount,
currency: "CLP",
due_date: "2026-04-30",
description: `订单 #12345(US$ ${productPriceUsd},汇率 ${rate})`,
type_charge: "credit_card",
installment_count: 1,
payer_address: {
street: "Av. Providencia", address_number: "1234",
complement: "Depto 501", neighborhood: "Providencia",
city: "Santiago", state: "RM",
postal_code: "7500000", country: "CL",
},
}),
});
const charge = await chargeResp.json();
console.log(`收费 UUID:${charge.charge_uuid}`);
console.log(`CLP:${charge.local_currency}`);
console.log(`美元:US$ ${charge.usd_currency}`);
console.log(`欧元:€${charge.eur_currency}`);
预期响应:
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"local_currency": 95073,
"currency": "CLP",
"usd_currency": 100.00,
"eur_currency": 91.45,
"type": "credit_card",
"status": "confirmed",
"installment_count": 1,
"installments": [
{
"local_currency": 95073,
"currency": "CLP",
"usd_currency": 100.00,
"eur_currency": 91.45,
"due_date": "2026-04-30",
"status": "confirmed",
"installment_number": 1
}
]
}
注意 local_currency 是整数(95073,而非 95073.00)——这是 CLP 的正确格式。
CLP 拒付场景——过期汇率放大多收金额
CLP 的数量级较大(每 USD 约 950 CLP),意味着汇率偏移会产生比 BRL(每 USD 约 5.7 BRL)更大比例的差异。
场景: 您的产品售价 US$ 100.00。一位智利客户使用美元信用卡支付。您的钱包货币为 CLP。
| 时间 | 事件 | 汇率(USD→CLP) | 金额 |
|---|---|---|---|
| 10:00 | 您获取汇率并展示价格 | 960.00 | 页面显示"CLP $96,000" |
| 10:45 | 客户点击"确认支付",您用 10:00 的旧汇率创建收费 | 实际汇率已变为 940.00 | 您提交 CLP 96,000 |
| 10:45 | 发卡行按当前汇率将 CLP 96,000 换算回 USD | 发卡行使用 940.00 | 账单显示 US$ 102.13 |
| 10:45 | 发卡行加收 3% 外币交易手续费 | —— | 账单显示 US$ 105.19 |
| 次日 | 持卡人查看账单 | —— | "我同意支付 US$ 100.00,为什么扣了 US$ 105.19?" |
| 3 天后 | 持卡人发起争议 | —— | Mastercard 拒付码 4834:"交易金额不符" |
数学分析: 汇率偏移了 2.1%(960→940)。由于 CLP 金额在数万级别,2.1% 的偏移 = CLP 2,000 = US$ 2.13。加上 3% 的银行手续费,总多收金额达到 US$ 5.19——远超拒付触发门槛。
正确做法: 在 10:45 重新获取汇率(940.00),计算 CLP 94,000,提交 CLP 94,000。发卡行换算后约为 US$ 100.00,不会产生争议。
零小数货币决策树
在为任何 A55 支持的货币实现转换时,请使用以下流程图:
支持的货币
FX API(应用程序编程接口)支持 8 种货币,覆盖 7 个拉美国家加上 USD 和 EUR,共 56 个兑换货币对。
| 代码 | 货币 | 国家 / 地区 | 小数位 | 支持钱包 |
|---|---|---|---|---|
USD | 美元 | 美国 | 2 | —— |
BRL | 巴西雷亚尔 | 巴西 | 2 | 是 |
EUR | 欧元 | 欧元区 | 2 | —— |
MXN | 墨西哥比索 | 墨西哥 | 2 | 是 |
ARS | 阿根廷比索 | 阿根廷 | 2 | 是 |
COP | 哥伦比亚比索 | 哥伦比亚 | 0 | —— |
CLP | 智利比索 | 智利 | 0 | 是 |
PEN | 秘鲁索尔 | 秘鲁 | 2 | —— |
转换为 CLP 或 COP 时,收费金额必须为整数。乘以汇率后使用 ROUND_HALF_UP 四舍五入(不要使用 floor() 向下取整,也不要使用银行家舍入)。示例:USD 100.00 × 950.73 = CLP 95,073。后端会截断所有小数部分——95072.7 将变为 95072。详见智利比索深度指南。
主要跨境走廊
| 货币对 | 典型场景 |
|---|---|
| USD → BRL | 美国企业向巴西客户销售 |
| USD → MXN | 美国企业向墨西哥客户销售 |
| EUR → BRL | 欧洲企业进入巴西市场 |
| BRL → USD | 巴西 SaaS 企业、出口、跨境汇款 |
| USD → CLP | 美国企业向智利客户销售 |
| USD → ARS | 美国企业向阿根廷客户销售 |
| USD → COP | 美国企业向哥伦比亚客户销售 |
| USD → PEN | 美国企业向秘鲁客户销售 |
汇率来源与更新机制
| 属性 | 值 |
|---|---|
| 汇率来源 | 银行间中间价(mid-market),即 Bid(买入价)与 Ask(卖出价)的中间值。由第三方数据源提供,A55 不加任何加价 |
| 缓存窗口 | 约 17 分钟(1,000 秒) |
| 数据源 | 级联回退——系统依次查询多个外部数据源以确保汇率可用性 |
| 精度 | 汇率四舍五入到小数点后 2 位 |
| API 可用性 | 24/7——端点全天候响应,包括周末和节假日 |
| 周末汇率 | 外汇市场休市期间(周末、节假日),数据源返回最近一个交易时段的汇率 |
银行间市场汇率是全球银行之间买卖货币的中间价,不含任何加价。央行、金融监管机构和金融科技公司将其作为最公平的货币转换参考。全球外汇市场日均交易量超过 9.6 万亿美元(BIS(国际清算银行),2025 年数据)。如此庞大的交易量确保了汇率具有极高的流动性和准确性。
卡组织和发卡行如何在汇率基础上加收费用
A55 的 FX 端点返回的是银行间市场汇率——零加价。但从 A55 到持卡人的信用卡账单之间,还有两个环节会加收费用。理解这个链条,能帮助您区分"正常的银行费用"和"集成错误"。
货币转换链路
当一位美国持卡人在结算货币为 BRL 的商户网站上付款时,账单金额由五个步骤决定:
| 步骤 | 参与方 | 发生了什么 |
|---|---|---|
| 1 | A55 FX 端点 | 返回银行间汇率(例如 1 USD = 5.50 BRL) |
| 2 | 商户 | 以 BRL 创建收费 R$ 550.00(结算货币) |
| 3 | 卡组织(Visa 或 Mastercard) | 按其每日公布的汇率将 R$ 550.00 换算为 USD——银行间汇率加上卡组织加价(约 1%) |
| 4 | 发卡行 | 加收外币交易手续费(0%–3%,大多数信用卡收取 1%–2%) |
| 5 | 持卡人账单 | 显示最终金额:约 US$ 101–104 |
各环节费用明细
| 环节 | 含义 | 由谁承担 | 典型范围 |
|---|---|---|---|
| A55 汇率报价 | 银行间汇率,零加价 | —— | 0% |
| 卡组织跨境评估费 | Visa ISA(国际服务评估费)或 Mastercard 跨境评估费 | 商户(通过支付处理商传递) | 0.6%–1.4% |
| 卡组织货币转换加价 | 内嵌在卡组织公布的汇率中 | 持卡人 | 约 1% |
| 发卡行外币交易手续费 | 发卡行对外币交易收取的费用 | 持卡人 | 0%–3% |
| 持卡人合计影响 | 银行间汇率与账单金额的差异 | 持卡人 | 约 1.6%–4.4% |
Visa 与 Mastercard 费用对比
| Visa | Mastercard | |
|---|---|---|
| 跨境评估费(商户侧) | ISA:1.00%(USD 结算)/ 1.40%(非 USD) | 0.60%(USD 结算)/ 1.00%(非 USD) |
| 货币转换加价(持卡人侧) | 银行间汇率基础上约 1% | 银行间汇率基础上约 1% |
| 汇率适用时间 | 通常在结算时(授权后 1–2 个工作日) | 在授权时(交易发生时) |
| 官方汇率查询 | Visa 汇率计算器 | Mastercard 货币转换器 |
持卡人可通过上述链接输入交易日期和货币对,查询 Visa 或 Mastercard 当日实际使用的汇率。
商户能控制什么,不能控制什么
| 您能控制的 | 您无法控制的 |
|---|---|
| 何时获取汇率(时效性) | 卡组织公布的每日汇率 |
| 提交收费的 BRL 金额 | 发卡行收取的外币交易手续费(0%–3%) |
| 向客户展示的价格和汇率 | 持卡人的信用卡是收 0% 还是 3% 的外汇手续费 |
| 为争议处理保存的审计记录 | 卡组织适用汇率的具体日期 |
实际场景对照表
| 场景 | 持卡人账单预期金额 | 拒付风险 |
|---|---|---|
| 最新汇率 + 0% 外汇手续费的卡 | 约 US$ 101.00 | 无 |
| 最新汇率 + 2% 外汇手续费的卡 | 约 US$ 103.00 | 无 |
| 最新汇率 + 3% 外汇手续费的卡 | 约 US$ 104.00 | 无——在预期范围内 |
| 过期汇率(45 分钟前)+ 2% 手续费 | 约 US$ 106–108 | 高——可触发 Mastercard 4834 拒付 |
前三行是银行费用导致的正常、可预期差异,不会引发争议。最后一行是过期汇率叠加银行费用——这种组合才会导致拒付。
汇率适用时间与周末交易
Mastercard 在授权时(持卡人付款的时刻)适用汇率。Visa 通常在结算时(授权后 1–2 个工作日)适用汇率。这意味着:
- Mastercard 交易:适用的汇率与交易时刻非常接近。
- Visa 交易:汇率可能在授权和结算之间发生偏移。在汇率波动剧烈的市场中,这会产生额外差异。
周末交易:外汇市场于纽约时间周五 17:00 EST 收盘,周日晚间重新开盘。在此期间,Visa 和 Mastercard 均使用周五的收盘汇率。如果周末期间市场出现大幅波动,周末交易的汇率偏差风险更高。
汇率使用最佳实践
全球领先的支付平台(Stripe、Wise、Adyen)将汇率用途分为两个场景。A55 的 FX 端点同时适用于这两种场景,但刷新策略不同。
展示定价——产品页面和目录
在产品列表页或定价页展示多币种价格时,可以在服务端缓存汇率并定期刷新。
| 建议 | 说明 |
|---|---|
| 刷新频率 | 每 10–15 分钟调用一次 FX 端点 |
| 缓存策略 | 在服务端缓存汇率,避免每次页面加载都调用 API |
| 价格标注 | 标注"价格按参考汇率折算,最终金额以结账时为准" |
交易结账——创建实际收费
在客户确认付款、创建收费时,需要使用最新汇率以确保展示金额与扣款金额一致。
| 建议 | 说明 |
|---|---|
| 刷新时机 | 在客户点击"确认支付"时重新调用 FX 端点 |
| 展示汇率 | 在支付确认页同时展示原始金额、汇率和转换后的金额 |
| 记录汇率 | 将汇率存入收费的 description 字段,并保存到您的数据库 |
| 金额一致 | 确保展示给客户的金额与请求中的 installment_value 完全一致 |
汇率在缓存窗口内(约 17 分钟)保持不变。如果客户在结账页面停留较长时间,请在创建收费前重新调用 FX 端点获取最新汇率。使用过期汇率可能导致客户实际被扣金额与页面展示金额不符。
过期汇率如何导致拒付——以真实场景说明
如果您刚接触跨境支付集成,这一节将帮助您理解为什么汇率时效性至关重要。下面是一个真实的拒付场景:
场景: 您的产品售价 US$ 100.00,客户使用美元信用卡支付,您的钱包结算货币为 BRL。
| 时间 | 事件 | 汇率 | 金额 |
|---|---|---|---|
| 10:00 | 您获取汇率并展示价格 | 1 USD = 5.73 BRL | 页面显示"R$ 573.00" |
| 10:45 | 客户点击"确认支付",您用 10:00 的旧汇率创建收费 | 实际汇率已变为 5.50 | 您提交 R$ 573.00 |
| 10:45 | 发卡行按当前汇率将 R$ 573.00 换算回 USD | 发卡行使用 5.50 | 持卡人账单显示 US$ 104.18 |
| 次日 | 持卡人查看账单 | —— | "我只同意支付 US$ 100.00,为什么扣了 US$ 104.18?" |
| 3 天后 | 持卡人向发卡行发起争议 | —— | Mastercard 拒付码 4834:"交易金额不符" |
发生了什么: 您用 45 分钟前的旧汇率(5.73)计算了 BRL 金额。但持卡人的发卡行按当前汇率(5.50)将 BRL 换算回 USD,得到 US$ 104.18 而非 US$ 100.00。差额 US$ 4.18 导致了拒付。
正确做法: 在客户点击"确认支付"时(10:45)重新调用 FX 端点获取最新汇率 5.50,计算 R$ 550.00,提交 R$ 550.00。发卡行换算后约为 US$ 100.00,账单金额与展示金额一致,不会产生争议。
即使汇率完全实时,持卡人的发卡行也会在银行间汇率基础上加收自身的外汇加价(通常为 1%–3%)。这是 Visa 和 Mastercard 网络的行业惯例,在持卡人与银行的协议中已披露。以银行间汇率换算恰好为 US$ 100.00 的收费,在持卡人账单上可能显示为 US$ 101.00–103.00。这种小幅、可预期的差异很少引发争议。真正导致 Mastercard 4834 拒付的是使用过期汇率造成的大额差异,如上述场景所示。
在创建收费前的最后一刻获取汇率。获取汇率和创建收费之间的时间间隔越短,持卡人账单金额与展示金额的差异越小。
为什么"确认时重新获取汇率"是最佳方案
跨境支付中防止汇率相关拒付的策略有三种:
| 策略 | 原理 | A55 是否支持 |
|---|---|---|
| 确认时重新获取汇率 | 在创建收费前获取最新的银行间汇率,最大程度缩小汇率偏移 | 支持——在最后一刻调用 FX 端点 |
| DCC(动态货币转换) | 以持卡人的本国货币收费,发卡行无需再次换算 | 不支持——需要 DCC 认证和多币种结算能力 |
| 锁定汇率 | 将报价汇率锁定一段时间,保证有效期内不变 | 不支持——FX 端点返回实时报价,不锁定汇率 |
确认时重新获取汇率能最大程度减少汇率偏移(拒付的最主要原因)。结合透明的信息披露和完整的审计记录,这是 Stripe、Adyen、Wise 等全球领先支付平台为单一结算货币商户推荐的行业标准方案。
拒付防范清单
在每笔跨境收费中,请确认以下各项:
- 在创建收费前立即调用 FX 端点获取最新汇率
- 在支付确认页同时展示原始货币金额、汇率和转换后金额
- 在支付确认页注明:"最终账单金额可能因发卡行汇率略有差异"
- 将汇率和原始金额写入收费的
description字段 - 确保
installment_value与页面展示给客户的金额完全一致 - 在您的数据库中记录:汇率值、获取时间、展示金额、收费金额
- 对于零小数货币(CLP、COP),确保金额为整数
使用场景
| 场景 | 如何使用 FX 端点 |
|---|---|
| 跨境结账 | 在客户确认支付前获取汇率,展示转换后的价格 |
| 动态定价 | 定期刷新汇率,更新网站上的多币种产品价格 |
| 多币种仪表板 | 将所有交易金额转换为 USD 或 EUR,用于合并报告 |
| 财务对账 | 记录交易时汇率与结算时汇率,进行差异分析 |
| 订阅定价 | 按客户所在地货币显示月费金额 |
| 发票生成 | 在发票上注明汇率和两种货币的金额 |
重要说明
FX 端点返回当前银行间市场汇率,供展示和计算使用。它不会锁定汇率。实际收费使用的汇率取决于收单机构在处理交易时的汇率。为将差异降到最低,请在创建收费前立即获取最新报价。
阿根廷比索(ARS)存在资本管制。本端点返回的银行间汇率可能与阿根廷国内的官方汇率或平行市场汇率不同。涉及 ARS 的交易,请确认与客户沟通时使用的汇率口径一致。
在每笔跨境收费中记录以下信息:获取汇率的时间、使用的汇率值、展示给客户的金额、实际收费金额。将汇率写入收费的 description 字段(如 "订单 #12345(US$ 100.00,汇率 5.73)"),并在您的系统中保存完整记录。这些数据在对账和争议处理时不可或缺。