Skip to main content
Cookbooks

Cross-Border Fintech

Accept a USD card charge, settle to a BRL account, issue the fiscal document. One transaction, explicit FX surcharge, LGPD-ready audit trail.

1 min read · updated
View MarkdownEdit on GitHub
TIME
~15 min
STACK
Node.jsno LLM
SERVERS
ebanxcirclenuvem-fiscal

Accept USD from a foreign buyer, settle to a BRL account in Brazil, and issue a Nota Fiscal de Servicos — the full cross-border flow. One charge = one settled transaction. The 0.5% cross-border FX surcharge applies to the converted amount on top of the base $0.10.

What the billing looks like

A $100 USD charge settled to BRL at R$5.00:

ComponentAmount
Base transaction fee$0.10
Cross-border FX (0.5% × R$500 converted)R$2.50 (~$0.50)
Your total~$0.60 per settled charge

Invoicing (NF-e / NFS-e) and audit trail are included.

See Billing for the full surcharge rules. Only the leg that crosses currencies attracts the 0.5% — domestic-only transactions stay at the flat $0.10.

Prerequisites

npm install @codespar/sdk

Accounts required: EBANX (or Circle for USDC), Nuvem Fiscal for the NFS-e.

The flow

cross-border-charge.ts
import { CodeSpar } from "@codespar/sdk";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY! });

interface CrossBorderCharge {
  buyer_email: string;
  card_token: string;       // from a tokenization frontend
  usd_amount: number;       // cents, e.g. 10000 = $100.00
  service_description: string;
  service_code: string;     // municipal NFS-e service code
  customer_country: "US" | "MX" | "AR" | "CO";
}

async function collectCrossBorder(charge: CrossBorderCharge) {
  const session = await codespar.create(charge.buyer_email, {
    servers: ["ebanx", "nuvem-fiscal"],
    metadata: { flow: "cross_border_v1", country: charge.customer_country },
  });

  try {
    const result = await session.loop({
      steps: [
        // 1. Charge USD card via EBANX — settles to your BRL account at EBANX's spot rate
        {
          tool: "codespar_pay",
          params: {
            method: "card",
            amount: charge.usd_amount,
            currency: "USD",
            settle_currency: "BRL",
            card_token: charge.card_token,
            customer_email: charge.buyer_email,
            provider: "ebanx",
            description: charge.service_description,
          },
        },
        // 2. Issue NFS-e for the BRL amount the merchant received
        {
          tool: "codespar_invoice",
          params: (prev) => ({
            type: "nfse",
            amount_brl: prev[0].data.settled_amount_brl,
            fx_rate: prev[0].data.fx_rate,
            service_code: charge.service_code,
            customer: {
              name: prev[0].data.customer.name,
              tax_id_country: charge.customer_country,
            },
            reference_payment_id: prev[0].data.payment_id,
          }),
        },
      ],
      abortOnError: true,
    });

    return {
      charge_id: result.results[0].data.payment_id,
      settled_brl: result.results[0].data.settled_amount_brl,
      fx_rate: result.results[0].data.fx_rate,
      nfse_access_key: result.results[1].data.access_key,
      nfse_pdf: result.results[1].data.pdf_url,
    };
  } finally {
    await session.close();
  }
}

Why step 2 passes fx_rate explicitly

The NFS-e is issued in BRL, but the revenue came from a USD charge. Tax authorities (SEFAZ, some municipal) require the original foreign amount + the exchange rate used for settlement on the invoice. CodeSpar surfaces both values in the codespar_pay result so you can forward them verbatim.

USDC alternative via Circle

For buyers who want to pay in stablecoin, swap EBANX for Circle:

{
  tool: "codespar_pay",
  params: {
    method: "usdc",
    amount: charge.usd_amount,
    currency: "USD",
    settle_currency: "BRL",
    provider: "circle",
    wallet_address: charge.payer_wallet,
  },
}

Same pricing model — one settled transaction + the 0.5% FX surcharge on the BRL leg.

Handling the 0.5% transparently

If you want to pass the FX cost to the buyer explicitly (not absorb it), inspect the settlement metadata before issuing the receipt:

const settlement = result.results[0].data;
const receiptAmountUsd = charge.usd_amount;
const receiptAmountBrl = settlement.settled_amount_brl;
const fxSurchargeBrl = settlement.fx_surcharge_bps
  ? (receiptAmountBrl * settlement.fx_surcharge_bps) / 10000
  : 0;

// Your app shows: "Total: $100.00 USD · R$500.00 · FX fee R$2.50"

Compliance notes

  • LGPD: the buyer email + card metadata are stored in CodeSpar's audit ledger encrypted at rest (AES-256) and retained for 5 years (Brazilian SOX-equivalent requirements). You can export or delete on subject request via the dashboard.
  • SPB: if the settlement destination is a Brazilian bank, CodeSpar routes through the EBANX SPB rails; no DOC/TED fee applies.
  • Receipt language: for non-BR buyers, the NFS-e XML includes the translated service description if you pass customer.language. The legal document stays in PT-BR.

Next steps

Last updated on