Múltiplas moedas e cotações FX
Quick Reference
Por que oferecer múltiplas moedas
Seus clientes abandonam o checkout quando veem preços em moeda estrangeira. Múltiplas moedas resolvem esse problema.
| Sem múltiplas moedas | Com múltiplas moedas A55 |
|---|---|
| Cliente vê R$ 500,00 mas paga em USD | Cliente vê US$ 87,26 — o valor exato que ele paga |
| Cliente não sabe a taxa de câmbio real | Você mostra a taxa de mercado com total transparência |
| Cliente liga para o banco para contestar o valor | Sem surpresas — o preço corresponde à cobrança |
| Você perde vendas internacionais | Você converte visitantes internacionais em clientes pagantes |
O impacto no negócio é mensurável:
- Maior conversão: clientes compram quando entendem o preço na própria moeda.
- Menos chargebacks: precificação transparente reduz disputas do tipo "não reconheço esse valor".
- Vantagem competitiva: você oferece a mesma experiência de plataformas globais como Amazon, Shopify e Stripe.
- Cobertura LATAM: 8 moedas em 7 países — USD, BRL, EUR, MXN, ARS, COP, CLP, PEN.
Um lojista no Brasil pode exibir preços em USD, EUR ou MXN para compradores internacionais. Uma plataforma SaaS pode mostrar o valor da assinatura na moeda local de cada cliente. Um e-commerce pode permitir que compradores troquem de moeda no checkout.
Como funciona — três passos
O fluxo de múltiplas moedas tem três passos. Você chama o endpoint de FX, exibe o preço convertido e cria a cobrança.
Fluxo padrão (moeda com 2 decimais — BRL):
Fluxo para moeda sem decimais (CLP — valores inteiros):
Passo 1 — Obter a taxa de câmbio
Chame o endpoint de FX para obter a taxa de mercado atual entre duas moedas.
Endpoint: POST /api/v1/bank/wallet/fx/rate/
Request:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
from_currency | string | Sim | Moeda de origem (ISO 4217). Exemplo: USD |
to_currency | string | Sim | Moeda de destino (ISO 4217). Exemplo: BRL |
Response:
| Campo | Tipo | Descrição |
|---|---|---|
price | float | A taxa de câmbio, arredondada para 2 casas decimais. Ao converter para moedas sem decimais (CLP, COP), arredonde o valor final para inteiro |
- cURL
- Python
- JavaScript
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/' \
-H 'Authorization: Bearer SEU_TOKEN_DE_ACESSO' \
-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 SEU_TOKEN_DE_ACESSO",
"Content-Type": "application/json",
}
payload = {
"from_currency": "USD",
"to_currency": "BRL",
}
response = requests.post(url, json=payload, headers=headers)
taxa = response.json()["price"]
print(f"1 USD = {taxa} BRL")
const response = await fetch(
"https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/",
{
method: "POST",
headers: {
"Authorization": "Bearer SEU_TOKEN_DE_ACESSO",
"Content-Type": "application/json",
},
body: JSON.stringify({
from_currency: "USD",
to_currency: "BRL",
}),
}
);
const { price: taxa } = await response.json();
console.log(`1 USD = ${taxa} BRL`);
Response:
{
"price": 5.73
}
Isso significa 1 USD = 5,73 BRL neste momento.
Passo 2 — Exibir o preço convertido para seu cliente
Use a taxa para mostrar ao cliente o preço na moeda dele. Isso acontece na sua aplicação — nenhuma chamada à API da A55 é necessária.
Exemplo (BRL — 2 decimais): Seu produto custa US$ 100,00. A taxa é 5,73.
Preço em BRL = US$ 100,00 × 5,73 = R$ 573,00
| O que o cliente vê | Valor |
|---|---|
| Preço do produto | US$ 100,00 |
| Taxa de câmbio | 1 USD = 5,73 BRL |
| Valor a pagar | R$ 573,00 |
Exemplo (CLP — sem decimais): Seu produto custa US$ 100,00. A taxa é 950,73.
Preço em CLP = US$ 100,00 × 950,73 = CLP 95.073 (arredonde para inteiro)
| O que o cliente vê | Valor |
|---|---|
| Preço do produto | US$ 100,00 |
| Taxa de câmbio | 1 USD = 950,73 CLP |
| Valor a pagar | CLP $95.073 |
Para moedas sem decimais, o valor enviado na cobrança deve ser um inteiro. 95073, não 95073.00. O backend trunca casas decimais para CLP e COP.
Exiba o preço original E o preço convertido. Isso gera confiança. Seu cliente sabe a taxa exata e o valor exato que o cartão ou conta bancária dele será cobrado.
Passo 3 — Criar a cobrança
Crie a cobrança na moeda de liquidação (a moeda da carteira). O valor da cobrança é o valor convertido no Passo 2.
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/' \
-H 'Authorization: Bearer SEU_TOKEN_DE_ACESSO' \
-H 'Content-Type: application/json' \
-d '{
"wallet_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"merchant_id": "11111111-1111-1111-1111-111111111111",
"payer_name": "Maria Silva",
"payer_email": "maria@exemplo.com.br",
"payer_tax_id": "123.456.789-09",
"payer_cell_phone": "+5511978663027",
"installment_value": 573.00,
"currency": "BRL",
"due_date": "2026-04-30",
"description": "Pedido #12345 (US$ 100,00 a 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"
}
}'
Armazene a taxa no campo description da cobrança (por exemplo: "Pedido #12345 (US$ 100,00 a 5,73)"). Isso cria uma trilha de auditoria para reconciliação e resolução de disputas.
A resposta da cobrança inclui conversão automática em múltiplas moedas:
{
"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
}
]
}
Cada resposta de cobrança inclui três valores de moeda:
| Campo | Descrição | Exemplo |
|---|---|---|
local_currency | Valor na moeda de liquidação da carteira | 573,00 (BRL) |
usd_currency | Valor equivalente em dólares americanos | 100,00 (USD) |
eur_currency | Valor equivalente em euros | 91,45 (EUR) |
Você pode usar esses valores para relatórios, reconciliação e dashboards de analytics em múltiplas moedas sem chamar o endpoint de FX novamente.
Exemplo de integração completo
Este exemplo mostra o fluxo completo — autenticar, obter a taxa de câmbio, calcular o preço e criar a cobrança.
- 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"
# Autenticar
token_resp = requests.post(AUTH_URL, data={
"grant_type": "client_credentials",
"client_id": "SEU_CLIENT_ID",
"client_secret": "SEU_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"}
# Passo 1: Obter a taxa de câmbio
moeda_destino = "BRL" # Altere para "CLP" para Peso Chileno
fx_resp = requests.post(f"{BASE}/bank/wallet/fx/rate/", json={
"from_currency": "USD",
"to_currency": moeda_destino,
}, headers=headers)
taxa = fx_resp.json()["price"]
print(f"Taxa de câmbio: 1 USD = {taxa} {moeda_destino}")
# Passo 2: Calcular o preço convertido com precisão Decimal
preco_usd = 100.00
MOEDAS_SEM_DECIMAIS = {"CLP", "COP"}
valor = Decimal(str(preco_usd)) * Decimal(str(taxa))
if moeda_destino in MOEDAS_SEM_DECIMAIS:
valor_cobranca = int(valor.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
else:
valor_cobranca = float(valor.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
print(f"Valor da cobrança: {valor_cobranca} {moeda_destino}")
# Passo 3: Criar a cobrança
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": "Maria Silva",
"payer_email": "maria@exemplo.com.br",
"payer_tax_id": "123.456.789-09",
"payer_cell_phone": "+5511978663027",
"installment_value": valor_cobranca,
"currency": moeda_destino,
"due_date": "2026-04-30",
"description": f"Pedido #12345 (US$ {preco_usd} a {taxa})",
"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)
cobranca = charge_resp.json()
print(f"UUID da cobrança: {cobranca['charge_uuid']}")
print(f"Local ({moeda_destino}): {cobranca['local_currency']}")
print(f"USD: US$ {cobranca['usd_currency']}")
print(f"EUR: € {cobranca['eur_currency']}")
const BASE = "https://sandbox.api.a55.tech/api/v1";
const AUTH_URL = "https://auth.a55.tech/oauth2/token";
// Autenticar
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: "SEU_CLIENT_ID",
client_secret: "SEU_CLIENT_SECRET",
}),
});
const { access_token } = await tokenResp.json();
const headers = {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
};
// Passo 1: Obter a taxa de câmbio
const moedaDestino = "BRL"; // Altere para "CLP" para Peso Chileno
const fxResp = await fetch(`${BASE}/bank/wallet/fx/rate/`, {
method: "POST",
headers,
body: JSON.stringify({ from_currency: "USD", to_currency: moedaDestino }),
});
const { price: taxa } = await fxResp.json();
console.log(`Taxa de câmbio: 1 USD = ${taxa} ${moedaDestino}`);
// Passo 2: Calcular com precisão correta por tipo de moeda
const precoUsd = 100.0;
const MOEDAS_SEM_DECIMAIS = new Set(["CLP", "COP"]);
const valorCobranca = MOEDAS_SEM_DECIMAIS.has(moedaDestino)
? Math.round(precoUsd * taxa)
: Math.round(precoUsd * taxa * 100) / 100;
console.log(`Valor da cobrança: ${valorCobranca} ${moedaDestino}`);
// Passo 3: Criar a cobrança
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: "Maria Silva",
payer_email: "maria@exemplo.com.br",
payer_tax_id: "123.456.789-09",
payer_cell_phone: "+5511978663027",
installment_value: valorCobranca,
currency: moedaDestino,
due_date: "2026-04-30",
description: `Pedido #12345 (US$ ${precoUsd} a ${taxa})`,
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 cobranca = await chargeResp.json();
console.log(`UUID da cobrança: ${cobranca.charge_uuid}`);
console.log(`Local (${moedaDestino}): ${cobranca.local_currency}`);
console.log(`USD: US$ ${cobranca.usd_currency}`);
console.log(`EUR: € ${cobranca.eur_currency}`);
CLP — guia completo para moedas sem decimais
O Peso Chileno (CLP) é uma moeda sem decimais (ISO 4217 expoente 0). Não existe centavo — a menor unidade é CLP 1. Isso cria desafios únicos ao converter de USD, EUR ou qualquer moeda com 2 decimais. Esta seção explica as armadilhas computacionais, a técnica correta de arredondamento e fornece um exemplo completo de ponta a ponta.
Por que o CLP exige atenção especial
Três propriedades diferenciam o CLP do BRL, MXN e outras moedas com 2 decimais:
| Propriedade | BRL (2 decimais) | CLP (sem decimais) | Impacto |
|---|---|---|---|
| Menor unidade | R$ 0,01 (centavo) | CLP $1 (peso) | Valores CLP devem ser inteiros |
| Magnitude por USD | ~5,7 BRL | ~950 CLP | Erros de arredondamento são amplificados ~167x |
| Precisão taxa × valor | 100,00 × 5,73 = 573,00 (exato) | 100,00 × 950,73 = 95073,0 (pode não ser exato em ponto flutuante) | Aritmética de ponto flutuante pode produzir resultados fracionários |
| Comportamento do backend | Armazenado como Numeric(11,2) | Truncado para inteiro antes do adquirente | 95072,7 se torna 95072 — você perde CLP 1 |
Um erro de arredondamento de 1 centavo de USD (~CLP 10) é invisível em BRL (R$ 0,01) mas produz uma discrepância visível em CLP. Ao longo de milhões de transações, o viés sistemático de arredondamento se acumula em erros de precificação materiais — o mesmo padrão documentado na conversão do Franco Belga para Euro em 2001, quando o arredondamento acumulado causou inflação de 0,54–0,72% nos preços ao consumidor.
A armadilha de precisão — IEEE 754 e aritmética de ponto flutuante
Computadores representam números decimais em binário (IEEE 754). A maioria das frações decimais não pode ser representada com exatidão:
// Demonstração IEEE 754
0.1 + 0.2 // 0.30000000000000004 (não 0.3)
99.99 * 950.73 // 95063.4927 — pode ter dígitos extras
100.00 * 950.73 // 95073.0 (por acaso exato aqui)
149.99 * 950.73 // 142599.9927 — NÃO é inteiro, deve arredondar para 142600
Math.floor(142599.9927) // 142599 — ERRADO, perdeu CLP 1 (correto é 142600)
O perigo é intermitente: algumas multiplicações são exatas, outras não. Você não pode confiar que o ponto flutuante será "próximo o suficiente" — é necessário arredondar explicitamente.
A lição do Franco Belga (2001): Quando a Bélgica converteu preços de BEF para EUR à taxa fixa de 40,3399, os comerciantes aplicaram floor() ao preço convertido. Ao longo de milhões de transações, esse truncamento sistemático transferiu ~€ 0,01 por cada € 101 dos consumidores para os comerciantes. A organização de consumidores belga (Test-Achats) documentou o padrão. A lição: nunca truncar conversões de moeda — sempre arredonde para o inteiro mais próximo.
O princípio "arredonde tarde, não frequentemente": Realize toda a aritmética intermediária com precisão total de ponto flutuante (15–17 dígitos significativos em IEEE 754 double). Arredonde apenas uma vez, no passo final, para as casas decimais da moeda de destino. Cada arredondamento intermediário introduz erro que se acumula. Essa é a abordagem usada por Stripe, Wise e Adyen em produção.
Arredondamento correto para CLP — por que round() não é suficiente
Python e JavaScript implementam estratégias de arredondamento diferentes para o ponto médio 0,5:
| Linguagem | Expressão | Resultado | Modo de arredondamento |
|---|---|---|---|
| Python | round(95072.5) | 95072 | Arredondamento bancário (half-to-even) |
| JavaScript | Math.round(95072.5) | 95073 | Arredondamento convencional (half-up) |
| Python | round(95073.5) | 95074 | Arredondamento bancário (arredonda para par) |
| JavaScript | Math.round(95073.5) | 95074 | Arredondamento convencional |
O arredondamento bancário minimiza o viés estatístico em grandes conjuntos de dados, mas para transações de pagamento individuais, o cliente e o lojista esperam o arredondamento convencional (0,5 arredonda para cima). Se o seu backend usa Python e o frontend usa JavaScript, a mesma multiplicação pode produzir valores inteiros diferentes.
Abordagem recomendada — use tipos Decimal:
- Python (Decimal)
- JavaScript (Math.round)
from decimal import Decimal, ROUND_HALF_UP
taxa = 950.73
preco_produto = 149.99
valor = Decimal(str(preco_produto)) * Decimal(str(taxa))
valor_cobranca = int(valor.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
# valor_cobranca = 142600 (CLP) — determinístico, sem surpresa de ponto flutuante
const taxa = 950.73;
const precoProduto = 149.99;
// Math.round usa arredondamento convencional — seguro para CLP
const valorCobranca = Math.round(precoProduto * taxa);
// valorCobranca = 142600 (CLP) — mesmo resultado que Python Decimal
Math.floor(142599.9927) = 142599 — mas a cobrança correta é CLP 142.600. parseInt("142599.9927") = 142599. Ambos descartam silenciosamente a parte fracionária, cobrando sistematicamente CLP 1 a menos. Ao longo de milhões de transações, isso cria uma perda de receita mensurável. O backend da A55 também trunca (não arredonda) — se você enviar 142599.99, ele se torna 142599.
A armadilha de truncamento — comportamento do backend A55 para CLP
O backend da A55 trunca (não arredonda) casas decimais para CLP e COP antes de rotear para o adquirente. Isso significa:
| Você envia | Backend recebe | Adquirente recebe | Problema |
|---|---|---|---|
95073 | 95073 | 95073 | Nenhum — correto |
95073.00 | 95073 | 95073 | Nenhum — truncamento sem efeito |
95072.7 | 95072 | 95072 | Perdeu CLP 1 — cliente foi cotado a CLP 95.073 |
95072.999 | 95072 | 95072 | Perdeu CLP 1 — ponto flutuante produziu valor errado |
Validação defensiva antes de enviar:
- Python
- JavaScript
def validar_valor_clp(valor: float) -> int:
if valor != int(valor):
raise ValueError(f"Valor CLP deve ser inteiro, recebido {valor}")
return int(valor)
function validarValorClp(valor) {
if (!Number.isInteger(valor)) {
throw new Error(`Valor CLP deve ser inteiro, recebido ${valor}`);
}
return valor;
}
Exemplo completo CLP — USD para Peso Chileno
Este exemplo mostra o fluxo completo para cotar um produto em USD e cobrar em CLP com arredondamento preciso.
- cURL
- Python
- JavaScript
# Passo 1: Obter taxa USD → CLP
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/fx/rate/' \
-H 'Authorization: Bearer SEU_TOKEN_DE_ACESSO' \
-H 'Content-Type: application/json' \
-d '{"from_currency": "USD", "to_currency": "CLP"}'
# Response: {"price": 950.73}
# Passo 2: Calcular — US$ 100,00 × 950,73 = CLP 95.073 (inteiro)
# Passo 3: Criar cobrança em CLP
curl -X POST 'https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/' \
-H 'Authorization: Bearer SEU_TOKEN_DE_ACESSO' \
-H 'Content-Type: application/json' \
-d '{
"wallet_uuid": "SEU_UUID_CARTEIRA_CLP",
"merchant_id": "SEU_MERCHANT_ID",
"payer_name": "Carlos Muñoz",
"payer_email": "carlos@example.cl",
"payer_tax_id": "12.345.678-9",
"payer_cell_phone": "+56911111111",
"installment_value": 95073,
"currency": "CLP",
"due_date": "2026-04-30",
"description": "Pedido #12345 (US$ 100,00 a 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": "SEU_CLIENT_ID",
"client_secret": "SEU_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"}
# Passo 1: Obter taxa USD → CLP atualizada
fx_resp = requests.post(f"{BASE}/bank/wallet/fx/rate/", json={
"from_currency": "USD",
"to_currency": "CLP",
}, headers=headers)
taxa = fx_resp.json()["price"]
# Passo 2: Converter com precisão Decimal — arredondar HALF_UP para inteiro
preco_usd = 100.00
valor_decimal = Decimal(str(preco_usd)) * Decimal(str(taxa))
valor_cobranca = int(valor_decimal.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
# Passo 3: Validar e criar cobrança
assert valor_cobranca == int(valor_cobranca), "Valor CLP deve ser inteiro"
charge_resp = requests.post(f"{BASE}/bank/wallet/charge/", json={
"wallet_uuid": "SEU_UUID_CARTEIRA_CLP",
"merchant_id": "SEU_MERCHANT_ID",
"payer_name": "Carlos Muñoz",
"payer_email": "carlos@example.cl",
"payer_tax_id": "12.345.678-9",
"payer_cell_phone": "+56911111111",
"installment_value": valor_cobranca,
"currency": "CLP",
"due_date": "2026-04-30",
"description": f"Pedido #12345 (US$ {preco_usd} a {taxa})",
"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)
cobranca = charge_resp.json()
print(f"UUID da cobrança: {cobranca['charge_uuid']}")
print(f"CLP: {cobranca['local_currency']}")
print(f"USD: US$ {cobranca['usd_currency']}")
print(f"EUR: € {cobranca['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: "SEU_CLIENT_ID",
client_secret: "SEU_CLIENT_SECRET",
}),
});
const { access_token } = await tokenResp.json();
const headers = {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
};
// Passo 1: Obter taxa USD → CLP atualizada
const fxResp = await fetch(`${BASE}/bank/wallet/fx/rate/`, {
method: "POST",
headers,
body: JSON.stringify({ from_currency: "USD", to_currency: "CLP" }),
});
const { price: taxa } = await fxResp.json();
// Passo 2: Converter — Math.round usa arredondamento convencional, seguro para CLP
const precoUsd = 100.0;
const valorCobranca = Math.round(precoUsd * taxa);
// Passo 3: Validar inteiro e criar cobrança
if (!Number.isInteger(valorCobranca)) {
throw new Error(`Valor CLP deve ser inteiro, recebido ${valorCobranca}`);
}
const chargeResp = await fetch(`${BASE}/bank/wallet/charge/`, {
method: "POST",
headers,
body: JSON.stringify({
wallet_uuid: "SEU_UUID_CARTEIRA_CLP",
merchant_id: "SEU_MERCHANT_ID",
payer_name: "Carlos Muñoz",
payer_email: "carlos@example.cl",
payer_tax_id: "12.345.678-9",
payer_cell_phone: "+56911111111",
installment_value: valorCobranca,
currency: "CLP",
due_date: "2026-04-30",
description: `Pedido #12345 (US$ ${precoUsd} a ${taxa})`,
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 cobranca = await chargeResp.json();
console.log(`UUID da cobrança: ${cobranca.charge_uuid}`);
console.log(`CLP: ${cobranca.local_currency}`);
console.log(`USD: US$ ${cobranca.usd_currency}`);
console.log(`EUR: € ${cobranca.eur_currency}`);
Resposta esperada:
{
"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
}
]
}
Note que local_currency é um inteiro (95073, não 95073.00) para CLP.
Cenário de chargeback CLP — taxas defasadas amplificam cobranças a maior
A magnitude elevada do CLP (~950 por USD) faz com que a variação cambial produza discrepâncias proporcionalmente maiores do que em BRL (~5,7 por USD).
Cenário: Seu produto custa US$ 100,00. Um cliente chileno paga com cartão de crédito em USD. Sua carteira é em CLP.
| Hora | Evento | Taxa (USD→CLP) | Valor |
|---|---|---|---|
| 10:00 | Você obtém a taxa e exibe o preço | 960,00 | Página mostra "CLP $96.000" |
| 10:45 | Cliente clica "Confirmar" — você cria a cobrança com a taxa das 10:00 | Taxa real agora é 940,00 | Você envia CLP 96.000 |
| 10:45 | Banco emissor converte CLP 96.000 de volta para USD à taxa atual | Emissor usa 940,00 | Fatura mostra US$ 102,13 |
| 10:45 | Emissor adiciona 3% de taxa de câmbio | — | Fatura mostra US$ 105,19 |
| Dia seguinte | Portador do cartão confere a fatura | — | "Eu concordei com US$ 100,00, por que cobrou US$ 105,19?" |
| 3 dias depois | Portador disputa a cobrança | — | Mastercard 4834: "Valor da transação diverge" |
A matemática: A taxa variou 2,1% (960→940). Com valores CLP na casa das dezenas de milhares, 2,1% = CLP 2.000 = US$ 2,13. Some a taxa bancária de 3% e a cobrança a maior chega a US$ 5,19 — bem acima do limiar de chargeback.
A correção: Reobtém a taxa às 10:45 (940,00), calcula CLP 94.000, envia CLP 94.000. O emissor converte de volta para ~US$ 100,00. Sem disputa.
Árvore de decisão para moedas sem decimais
Use este fluxograma ao implementar conversão de moeda para qualquer moeda suportada pela A55:
Moedas suportadas
A API de FX suporta 8 moedas em 7 países LATAM mais USD e EUR, totalizando 56 pares de conversão.
| Código | Moeda | País / Região | Decimais | Carteira suportada |
|---|---|---|---|---|
USD | Dólar Americano | Estados Unidos | 2 | — |
BRL | Real Brasileiro | Brasil | 2 | Sim |
EUR | Euro | Zona do Euro | 2 | — |
MXN | Peso Mexicano | México | 2 | Sim |
ARS | Peso Argentino | Argentina | 2 | Sim |
COP | Peso Colombiano | Colômbia | 0 | — |
CLP | Peso Chileno | Chile | 0 | Sim |
PEN | Sol Peruano | Peru | 2 | — |
Ao converter para CLP ou COP, o valor final da cobrança deve ser um inteiro. Após multiplicar pela taxa FX, arredonde para o número inteiro mais próximo usando ROUND_HALF_UP (não floor(), não arredondamento bancário). Exemplo: USD 100,00 × 950,73 = CLP 95.073. O backend trunca qualquer decimal — 95072,7 se torna 95072. Veja o guia completo de CLP para orientações detalhadas de precisão.
Principais corredores de câmbio
| Par | Caso de uso |
|---|---|
| USD → BRL | Empresas americanas vendendo para clientes brasileiros |
| USD → MXN | Empresas americanas vendendo para clientes mexicanos |
| EUR → BRL | Empresas europeias entrando no Brasil |
| BRL → USD | SaaS brasileiro, exportações, remessas |
| USD → CLP | Empresas americanas vendendo para clientes chilenos |
| USD → ARS | Empresas americanas vendendo para clientes argentinos |
| USD → COP | Empresas americanas vendendo para clientes colombianos |
| USD → PEN | Empresas americanas vendendo para clientes peruanos |
Fonte e atualização da taxa
| Propriedade | Valor |
|---|---|
| Fonte da taxa | Taxa de mercado interbancário (mid-market) — o ponto médio entre Bid e Ask, sem markup da A55 |
| Janela de cache | ~17 minutos (1.000 segundos) |
| Fontes de dados | Fallback em cascata — o sistema consulta múltiplas fontes externas em sequência para garantir disponibilidade |
| Precisão | Taxa arredondada para 2 casas decimais; valor final da cobrança segue os decimais da moeda (0 para CLP/COP, 2 para as demais) |
| Disponibilidade da API | 24/7 — o endpoint responde a qualquer momento, incluindo fins de semana e feriados |
| Taxas de fim de semana | Fora do horário do mercado Forex (fins de semana, feriados), as fontes retornam a última taxa de negociação disponível |
A taxa mid-market é o ponto médio entre o preço de compra e venda no mercado interbancário. Ela não tem nenhum markup. Bancos centrais, reguladores e fintechs a usam como a referência mais justa para conversão de moedas. O mercado Forex movimenta mais de US$ 9,6 trilhões por dia (BIS 2025) — nenhum outro mercado financeiro oferece esse nível de liquidez e precisão.
Como redes de cartão e emissores adicionam taxas à taxa de câmbio
O endpoint de FX da A55 retorna a taxa mid-market sem markup. Porém, entre a A55 e a fatura do cartão do portador, duas camadas adicionais inserem taxas. Entender essa cadeia ajuda a distinguir taxas bancárias esperadas de erros de integração.
A cadeia de conversão
Quando um portador de cartão nos EUA paga em um site de lojista com liquidação em BRL, cinco etapas determinam o valor final na fatura:
| Etapa | Ator | O que acontece |
|---|---|---|
| 1 | Endpoint FX da A55 | Retorna a taxa mid-market (por exemplo, 5,50 BRL/USD) |
| 2 | Lojista | Cobra R$ 550,00 em BRL (moeda de liquidação) |
| 3 | Rede de cartão (Visa ou Mastercard) | Converte R$ 550,00 para USD à sua taxa diária publicada — mid-market mais markup da rede (~1%) |
| 4 | Banco emissor | Adiciona taxa de transação internacional (0–3%, maioria dos cartões cobra 1–2%) |
| 5 | Fatura do portador | Mostra o valor final: aproximadamente US$ 101–104 |
Detalhamento de taxas por camada
| Camada | O que é | Quem paga | Faixa típica |
|---|---|---|---|
| Cotação FX da A55 | Taxa interbancária mid-market, sem markup | — | 0% |
| Taxa de avaliação cross-border da rede | Visa ISA ou Mastercard Cross-Border Assessment | Lojista (repassado pelo processador) | 0,6–1,4% |
| Conversão de moeda da rede | Embutida na taxa de câmbio publicada pela rede | Portador do cartão | ~1% |
| Taxa de transação internacional do emissor | Cobrada pelo banco do portador para transações em moeda estrangeira | Portador do cartão | 0–3% |
| Impacto total no portador | Diferença entre taxa mid-market e valor na fatura | Portador do cartão | ~1,6–4,4% |
Comparação de taxas Visa vs Mastercard
| Visa | Mastercard | |
|---|---|---|
| Taxa cross-border (lado lojista) | ISA: 1,00% (liquidação USD) / 1,40% (não-USD) | 0,60% (liquidação USD) / 1,00% (não-USD) |
| Markup de conversão (lado portador) | ~1% sobre mid-market | ~1% sobre mid-market |
| Momento de aplicação da taxa | Geralmente na liquidação (1–2 dias úteis após autorização) | Na autorização (momento da venda) |
| Consulta de taxa publicada | Calculadora Visa | Conversor Mastercard |
O que você controla vs o que não controla
| Você controla | Você não controla |
|---|---|
| Quando obtém a taxa de câmbio (frescor) | A taxa de câmbio publicada pela rede de cartão |
| O valor em BRL enviado na cobrança | A taxa de transação internacional do emissor (0–3%) |
| O preço e a taxa exibidos ao cliente | Se o cartão do portador cobra 0% ou 3% de taxa de câmbio |
| A trilha de auditoria armazenada para defesa de disputas | A data de conversão aplicada pela rede |
Cenários reais
| Cenário | Valor esperado na fatura | Risco |
|---|---|---|
| Taxa atualizada + cartão com 0% de taxa internacional | ~US$ 101,00 | Nenhum |
| Taxa atualizada + cartão com 2% de taxa internacional | ~US$ 103,00 | Nenhum |
| Taxa atualizada + cartão com 3% de taxa internacional | ~US$ 104,00 | Nenhum — dentro do esperado |
| Taxa defasada (45 min) + 2% de taxa internacional | ~US$ 106–108 | Alto — chargeback Mastercard 4834 |
As três primeiras linhas mostram variação esperada e não disputável causada por taxas bancárias. A última linha mostra o problema de taxa defasada somado às taxas — essa combinação produz chargebacks.
Momento de conversão e fins de semana
Mastercard aplica a taxa de câmbio no momento da autorização (quando o portador paga). Visa geralmente aplica a taxa na liquidação (1–2 dias úteis depois). Isso significa:
- Cobranças Mastercard: a taxa aplicada é muito próxima da taxa no momento da compra.
- Cobranças Visa: a taxa pode variar entre autorização e liquidação. Em mercados voláteis, isso cria variação adicional.
Para transações de fim de semana, ambas as redes usam a última taxa disponível de sexta-feira (Nova York, 17h EST) até o mercado Forex reabrir no domingo à noite. Transações de fim de semana carregam maior risco de divergência de taxa se o mercado se mover significativamente durante as 48 horas de fechamento.
Boas práticas para frescor da taxa
Plataformas de pagamento líderes (Stripe, Wise, Adyen) distinguem dois usos de taxas de câmbio. O endpoint FX da A55 atende ambos, mas a estratégia de atualização difere.
Precificação de exibição — páginas de produto e catálogos
Ao mostrar preços em múltiplas moedas em páginas de listagem ou precificação, você pode cachear a taxa no servidor e atualizar periodicamente.
| Recomendação | Detalhes |
|---|---|
| Frequência de atualização | Chame o endpoint de FX a cada 10–15 minutos |
| Cache | Armazene a taxa no servidor para evitar chamada de API a cada carregamento de página |
| Aviso de preço | Identifique preços como "aproximado — valor final determinado no checkout" |
Checkout transacional — criação de cobranças
Quando o cliente confirma o pagamento e você cria a cobrança, use a taxa mais recente possível para garantir que o valor exibido corresponda ao valor cobrado.
| Recomendação | Detalhes |
|---|---|
| Momento de atualização | Reobtém a taxa quando o cliente clica "Confirmar pagamento" |
| Exibir a taxa | Mostre o valor original, a taxa de câmbio e o valor convertido na página de confirmação |
| Registrar a taxa | Armazene a taxa no campo description da cobrança e no seu banco de dados |
| Consistência de valor | Garanta que o valor exibido ao cliente corresponda exatamente ao installment_value |
A taxa de câmbio permanece constante dentro da janela de cache (~17 minutos). Se o seu cliente passa tempo na página de checkout, reobtém a taxa antes de criar a cobrança. Usar uma taxa defasada pode fazer com que o valor cobrado difira do valor exibido.
Como uma taxa defasada causa chargebacks — cenário real
Se você é novo em pagamentos, esta seção explica por que o frescor da taxa importa. Eis um cenário real de chargeback:
Cenário: Seu produto custa US$ 100,00. O cliente paga com cartão de crédito em USD. Sua carteira de liquidação é em BRL.
| Hora | Evento | Taxa | Valor |
|---|---|---|---|
| 10:00 | Você obtém a taxa e exibe o preço | 1 USD = 5,73 BRL | Página mostra "R$ 573,00" |
| 10:45 | Cliente clica "Confirmar" — você cria a cobrança com a taxa das 10:00 | Taxa real agora é 5,50 | Você envia R$ 573,00 |
| 10:45 | Banco emissor converte R$ 573,00 de volta para USD à taxa atual | Emissor usa 5,50 | Fatura mostra US$ 104,18 |
| Dia seguinte | Portador confere a fatura | — | "Eu concordei em pagar US$ 100,00, por que cobrou US$ 104,18?" |
| 3 dias depois | Portador abre disputa com o emissor | — | Mastercard código 4834: "Valor da transação diverge" |
O que aconteceu: Você calculou o valor em BRL usando uma taxa de 45 minutos atrás (5,73). O banco emissor converteu o BRL de volta para USD à taxa corrente (5,50), resultando em US$ 104,18 em vez de US$ 100,00. A diferença de US$ 4,18 disparou o chargeback.
A correção: No momento em que o cliente clica "Confirmar" (10:45), chame novamente o endpoint de FX para obter a taxa corrente (5,50), calcule R$ 550,00 e envie R$ 550,00. O emissor converte de volta para aproximadamente US$ 100,00 — a fatura corresponde ao preço exibido, sem disputa.
Mesmo com uma taxa perfeitamente atualizada, o banco emissor do portador adiciona seu próprio markup (tipicamente 1–3% sobre mid-market). Isso é padrão nas redes Visa e Mastercard — o contrato do portador com o banco cobre esse markup. Uma cobrança que converte para exatamente US$ 100,00 na taxa mid-market pode aparecer como US$ 101,00–103,00 na fatura. Essa pequena variação esperada raramente gera disputas. O problema crítico — e o que causa chargebacks Mastercard 4834 — é a grande discrepância causada por taxas defasadas, como mostrado no cenário acima.
Obtenha a taxa no último momento possível antes de criar a cobrança. Quanto menor o intervalo entre obter a taxa e criar a cobrança, menor a discrepância na fatura do portador.
Por que reobtém a taxa na confirmação é a melhor abordagem
Existem três estratégias para prevenir chargebacks relacionados a câmbio em pagamentos cross-border:
| Estratégia | Como funciona | Disponível na A55 |
|---|---|---|
| Reobtém na confirmação | Obtém a taxa mid-market mais recente antes de criar a cobrança | Sim — chame o endpoint FX no último momento |
| DCC (Conversão Dinâmica de Moeda) | Cobra o portador na moeda dele para que o emissor não precise converter | Não — requer certificação DCC e liquidação multi-moeda |
| Trava de taxa | Garante a taxa cotada por um período determinado | Não — o endpoint FX retorna cotação em tempo real, não taxa travada |
Reobtém na confirmação minimiza a variação cambial (a maior fonte de chargebacks). Combinado com divulgação transparente e registro de auditoria, essa é a abordagem padrão da indústria usada por Stripe, Adyen e Wise para lojistas que liquidam em uma única moeda.
Checklist de prevenção de chargeback
Para cada cobrança cross-border, verifique:
- Você obteve uma taxa atualizada imediatamente antes de criar a cobrança
- A página de confirmação mostra o valor original, a taxa de câmbio e o valor convertido juntos
- A página de confirmação inclui um aviso: "O valor final na fatura do seu cartão pode variar ligeiramente devido à taxa de câmbio do seu banco"
- A taxa e o valor original estão armazenados no campo
descriptionda cobrança - O
installment_valuecorresponde exatamente ao valor exibido ao cliente - Seu banco de dados registra: valor da taxa, timestamp da consulta, valor exibido, valor cobrado
- Para moedas sem decimais (CLP, COP), o valor é um inteiro
Casos de uso
| Cenário | Como usar o endpoint de FX |
|---|---|
| Checkout cross-border | Obtenha a taxa, exiba o preço convertido para o comprador antes da confirmação |
| Precificação dinâmica | Atualize os preços no seu site com base na taxa de câmbio atual |
| Dashboard multi-moeda | Converta todos os valores de transações para USD ou EUR para relatórios consolidados |
| Reconciliação financeira | Compare a taxa do momento da transação com a taxa do momento da liquidação |
| Preço de assinatura | Mostre aos assinantes o valor mensal na moeda local deles |
| Geração de faturas | Inclua a taxa de câmbio e os valores em ambas as moedas nas faturas |
Observações importantes
Para moedas com controles de capital — especialmente o Peso Argentino (ARS) — a taxa mid-market pode divergir das taxas oficiais ou paralelas praticadas localmente. A taxa retornada reflete o mercado interbancário internacional.
O endpoint de FX retorna a taxa atual do mercado interbancário para exibição e cálculo. Ele não trava a taxa. A conversão efetiva aplicada à cobrança depende do adquirente no momento do processamento da transação. Para minimizar discrepâncias, obtenha uma cotação atualizada imediatamente antes de criar a cobrança.
Para cada cobrança cross-border, registre: o momento em que você obteve a taxa, o valor da taxa, o valor exibido ao cliente e o valor cobrado. Inclua a taxa no campo description da cobrança (por exemplo: "Pedido #12345 (US$ 100,00 a 5,73)") e persista o registro completo no seu sistema. Esses dados são essenciais para reconciliação e resolução de disputas.