import { Inject, Injectable } from "@nestjs/common"; import { CbeExchangeProvider } from "./cbe.provider"; import { EXCHANGE_OPTIONS, ExchangeOptions } from "./exchange.options"; import { CurrencyCode } from "./exchange.types"; /** * Currency exchange service. Resolves the rate between any supported currency * pair and converts amounts, backed by a rate provider (currently CBE). * * Resolution order for `getRate(from, to)`: * 1. `from === to` → `1`. * 2. Provider supplies the pair directly (e.g. CBE → USD→ETB). * 3. Provider supplies the inverse → return `1 / inverse` (e.g. ETB→USD). * * Configure via {@link ExchangeModule.forRoot} / `forRootAsync`. */ @Injectable() export class ExchangeService { private readonly provider: CbeExchangeProvider; constructor(@Inject(EXCHANGE_OPTIONS) options: ExchangeOptions) { this.provider = new CbeExchangeProvider(options); } /** * Returns the rate to convert 1 unit of `from` into `to` * (i.e. `amountInTo = amountInFrom * getRate(from, to)`). */ async getRate(from: CurrencyCode, to: CurrencyCode): Promise { if (from === to) { return 1; } const direct = await this.provider.getBaseRate({ from, to }); if (direct !== null) { return direct; } const inverse = await this.provider.getBaseRate({ from: to, to: from }); if (inverse !== null && inverse > 0) { return 1 / inverse; } throw new Error( `No exchange rate available for ${from}→${to} from provider ${this.provider.name}`, ); } /** Converts `amount` from one currency to another using {@link getRate}. */ async convert( amount: number, from: CurrencyCode, to: CurrencyCode, ): Promise { const rate = await this.getRate(from, to); return amount * rate; } /** * Convenience alias for `getRate('USD', 'ETB')`. * @deprecated Prefer {@link getRate}; kept for existing callers. */ getUsdToEtbRate(): Promise { return this.getRate("USD", "ETB"); } /** Convenience alias for `getRate('ETB', 'USD')`. */ getEtbToUsdRate(): Promise { return this.getRate("ETB", "USD"); } }