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.

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.



