Skip to main content

Overview

The HopNow API supports multiple currencies for accounts, balances, payins, payouts, and FX operations. This guide covers best practices for handling currencies correctly.

Supported Currencies

The API supports all major fiat currencies (ISO 4217) and popular cryptocurrencies:

Fiat Currencies

  • USD - US Dollar
  • EUR - Euro
  • GBP - British Pound
  • MXN - Mexican Peso
  • BRL - Brazilian Real
  • And 150+ other ISO 4217 currencies

Cryptocurrencies

  • BTC - Bitcoin
  • ETH - Ethereum
  • USDC_ETHEREUM - USD Coin on Ethereum
  • USDC_POLYGON - USD Coin on Polygon
  • USDT - Tether

Currency Codes

Always use standard currency codes:
# Good - ISO 4217 codes
amount_usd = "100.00"
currency = "USD"

# Good - Crypto with network
currency = "USDC_ETHEREUM"

# Bad - Do not use symbols
currency = "$"  # ❌ Invalid

Decimal Precision

Different currencies have different decimal precision requirements:

Standard Fiat (2 decimals)

{
  "amount": "100.50",
  "currency": "USD"
}

Zero-Decimal Currencies

Some currencies don’t use decimal places (e.g., JPY, KRW):
{
  "amount": "10000",
  "currency": "JPY"
}

Cryptocurrencies (up to 8 decimals)

{
  "amount": "0.00123456",
  "currency": "BTC"
}

Amount Formatting

Always use string format for amounts to avoid floating-point precision errors:
# Good - String format
payout_data = {
    "amount": "100.50",
    "currency": "USD"
}

# Bad - Float (can cause precision errors)
payout_data = {
    "amount": 100.5,  # ❌ May cause rounding errors
    "currency": "USD"
}

Multi-Currency Accounts

Accounts can hold balances in multiple currencies simultaneously:
def get_balance_by_currency(account, currency):
    """Get balance for specific currency"""
    for balance in account["balances"]:
        if balance["currency"] == currency:
            return balance
    return None

# Usage
account = get_account("acc_123")
usd_balance = get_balance_by_currency(account, "USD")
eur_balance = get_balance_by_currency(account, "EUR")

Currency Conversion (FX)

Use the FX endpoints for currency conversion:
# 1. Create a quote
quote = create_fx_quote(
    account_id="acc_123",
    from_currency="USD",
    to_currency="EUR",
    amount="1000.00"
)

# 2. Review the rate
print(f"Rate: {quote['exchange_rate']}")
print(f"You'll receive: {quote['target_amount']} EUR")

# 3. Execute if satisfied (before expiration)
if quote['status'] == 'active':
    trade = execute_fx_quote(account_id="acc_123", quote_id=quote['id'])

Currency Matching

Always ensure currency matches across related operations:

Payout Destinations

# Payout destination currency must match payout currency
destination = create_payout_destination(
    beneficiary_id="ben_123",
    currency="USD",  # Destination currency
    account_number="..."
)

# Later, payout must use same currency
payout = create_payout(
    beneficiary_id="ben_123",
    payout_destination_id=destination["id"],
    amount="100.00",
    currency="USD"  # Must match destination
)

Virtual Accounts

Each virtual account supports one currency:
# Create separate virtual accounts for each currency
va_usd = create_virtual_account(
    account_id="acc_123",
    currency="USD",
    label="USD Collections"
)

va_eur = create_virtual_account(
    account_id="acc_123",
    currency="EUR",
    label="EUR Collections"
)

Balance Checks

Always check sufficient balance before creating payouts:
def can_create_payout(account_id, currency, amount):
    """Check if account has sufficient balance"""
    account = get_account(account_id)
    balance = get_balance_by_currency(account, currency)

    if not balance:
        return False, f"No {currency} balance"

    available = Decimal(balance["available_balance"])
    required = Decimal(amount)

    if available < required:
        return False, f"Insufficient funds: {available} < {required}"

    return True, "Sufficient balance"

# Usage
can_pay, message = can_create_payout("acc_123", "USD", "100.00")
if not can_pay:
    print(f"Cannot create payout: {message}")

Currency Display

Format currencies correctly for display:
def format_currency(amount, currency):
    """Format amount with proper currency symbol and precision"""
    amount_decimal = Decimal(amount)

    # Currency symbols
    symbols = {
        "USD": "$",
        "EUR": "€",
        "GBP": "£",
        "JPY": "¥"
    }

    # Precision by currency
    if currency in ["JPY", "KRW"]:
        # Zero-decimal currencies
        return f"{symbols.get(currency, currency)} {amount_decimal:,.0f}"
    else:
        # Standard 2-decimal currencies
        return f"{symbols.get(currency, currency)} {amount_decimal:,.2f}"

# Usage
print(format_currency("1000.50", "USD"))  # $ 1,000.50
print(format_currency("10000", "JPY"))     # ¥ 10,000

Common Pitfalls

1. Floating Point Arithmetic

Never use float for money calculations:
# Bad
total = 100.10 + 200.20  # May result in 300.2999999999

# Good
from decimal import Decimal
total = Decimal("100.10") + Decimal("200.20")  # Exactly 300.30

2. Currency Mismatch

❌ Don’t create payouts with mismatched currencies:
# Bad - will fail
create_payout(
    beneficiary_id="ben_123",
    payout_destination_id="dest_usd",  # USD destination
    amount="100.00",
    currency="EUR"  # ❌ Currency mismatch
)

3. Ignoring Balance Currency

❌ Don’t assume single currency:
# Bad - assumes single currency
balance = account["balances"][0]["available_balance"]

# Good - specify currency
usd_balance = get_balance_by_currency(account, "USD")

Best Practices Summary

  1. ✅ Always use string format for amounts
  2. ✅ Use standard ISO 4217 currency codes
  3. ✅ Check currency matches for related operations
  4. ✅ Verify sufficient balance before payouts
  5. ✅ Use Decimal for money calculations
  6. ✅ Handle multi-currency accounts properly
  7. ✅ Respect currency-specific decimal precision
  8. ✅ Use FX endpoints for currency conversion