Payment Integration

Multi-Currency Payments With Laravel

In brief

A technical guide to building payment systems that handle UGX, USD, USDT and more, with real-time exchange rates, currency conversion and multi-gateway support.

On this page 5 sections
Multi-Currency Payments With Laravel

Businesses operating across borders need payment systems that handle multiple currencies gracefully. A customer in Kenya pays in KES, a supplier in China invoices in USD, and your books are in UGX. Here is how to build a Laravel payment system that handles all of them.

The currency layer

Start with a currencies table:

Schema::create('currencies', function (Blueprint $table) {
    $table->id();
    $table->string('code', 3)->unique();     // UGX, USD, KES, USDT
    $table->string('name');                    // Ugandan Shilling
    $table->string('symbol');                  // USh, $, KSh
    $table->integer('decimals')->default(2);   // UGX=0, USD=2, USDT=2
    $table->decimal('exchange_rate', 20, 8);   // vs base currency
    $table->timestamp('rate_updated_at');
});

Never hard-code exchange rates. Fetch from an API (ExchangeRate-API, Open Exchange Rates) daily via a scheduled job:

// App\Jobs\UpdateExchangeRates
foreach (Currency::all() as $currency) {
    $rate = Http::get("https://api.exchangerate-api.com/v4/latest/{$base}")['rates'][$currency->code];
    $currency->update(['exchange_rate' => $rate, 'rate_updated_at' => now()]);
}

Amount handling, never use floats

Floating-point arithmetic causes rounding errors in financial calculations. Store all amounts as integers in the smallest unit (cents, satoshis):

// Store 50,000 UGX as 50000 (UGX has 0 decimal places)
// Store $29.99 as 2999 (cents)
// Store 100.50 USDT as 100500000 (6 decimal places for USDT on TRC20)

$amount = new Money(50000, 'UGX');  // 50,000 UGX
$amount->toDecimal();               // "50000"
$amount->convertTo('USD');          // Money(13.25, 'USD') at current rate

Use a Money value object, Martin Fowler's pattern, implemented in libraries like brick/money for PHP.

Multi-gateway architecture

Abstract every payment gateway behind a common interface:

interface PaymentGateway
{
    public function createDeposit(Deposit $deposit): PaymentResponse;
    public function checkStatus(string $reference): PaymentStatus;
    public function supports(string $currencyCode): bool;
    public function getFee(string $currencyCode): Money;
}

Then implement per gateway:

class FlutterwaveGateway implements PaymentGateway { /* ... */ }
class BinancePayGateway implements PaymentGateway { /* ... */ }
class MTNMoMoGateway implements PaymentGateway { /* ... */ }
class StripeGateway implements PaymentGateway { /* ... */ }

Route payments to the best gateway based on currency, amount and fees:

class PaymentRouter
{
    public function route(Money $amount, string $preferredMethod = null): PaymentGateway
    {
        $gateways = $this->getAvailableGateways($amount->currency);

        if ($preferredMethod) {
            return $gateways->first(fn($g) => $g instanceof $preferredMethod);
        }

        // Pick lowest fee for this amount + currency
        return $gateways->sortBy(fn($g) => $g->getFee($amount->currency))->first();
    }
}

Idempotency, prevent double charges

Every payment request includes an idempotency key (a UUID generated client-side). The server stores {key → result} and returns the stored result on duplicate requests. This prevents charging a customer twice if they double-click or retry.

$key = $request->header('Idempotency-Key');
$existing = Payment::where('idempotency_key', $key)->first();
if ($existing) {
    return response()->json($existing->result);
}
// Process payment, store with idempotency key

Webhook reconciliation

The golden rule: never trust client-side confirmation. Always verify via webhook or server-side status check. Your reconciliation job runs every 5 minutes, comparing gateway records against your database and flagging discrepancies.

TFast Digital Agency builds multi-currency payment systems for businesses operating across East Africa and globally: integrating mobile money, cards, bank transfers and crypto into one unified payment infrastructure.

<!-- SEO keyword optimization: multi currency payment system laravel -->

Focusing on multi currency payment system laravel is essential for modern business success.