Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Banxico Php Laravel Package

xint0/banxico-php

PHP client for Banxico's SIE REST API to fetch USD-MXN exchange rates (SF43718 determination and SF60653 liquidation series), with methods for latest, specific-date, or date-range queries and any SIE series by name.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and get a token

composer require xint0/banxico-php

PHP 8.2+ required (v3.0.0 supports 8.5; v2.0.0 dropped 7.4/8.0). The package declares psr/http-client-implementation and psr/http-factory-implementation and uses php-http/discovery, so in a normal Laravel app Guzzle is already present and discovery resolves it — no extra config needed.

Request a free token from the SIE API page and put it in config/services.php, never in code:

// config/services.php
'banxico' => [
    'token' => env('BANXICO_TOKEN'),
],

2. Where to look first

Read src/SieClient.php — it is the entire public surface. There are only three meaningful methods:

  • exchangeRateUsdDetermination($start = null, $end = null) — series SF43718 (Fix)
  • exchangeRateUsdLiquidation($start = null, $end = null) — series SF60653
  • fetchSeries($code, $start = null, $end = null) — any series from the catalog

The CHANGELOG 1.0.0 entry is the most important historical note: return types are string for a single value, array keyed by YYYY-MM-DD for a range, and nested arrays keyed by series code when fetching multiple series.

3. Bind it once

// app/Providers/AppServiceProvider.php
use Xint0\BanxicoPHP\SieClient;

$this->app->singleton(SieClient::class, fn () => new SieClient(config('services.banxico.token')));

4. First use case: invoice conversion

use Xint0\BanxicoPHP\SieClient;

$rate = app(SieClient::class)->exchangeRateUsdLiquidation('2024-01-15');
$mxn  = bcmul((string) $invoice->usd_amount, $rate, 2);

Implementation Patterns

Wrap it in a domain service, not just a container binding

The client is a thin transport. Day-to-day you want caching, retries, persistence, and type safety around it. Introduce App\Services\ExchangeRateService that composes SieClient and is what your controllers and jobs inject. This also gives you a single fake point for tests (see Gotchas).

final class ExchangeRateService
{
    public function __construct(private SieClient $client) {}

    public function usdFix(CarbonInterface $date): ?float
    {
        $key = "banxico:fix:{$date->toDateString()}";

        return Cache::remember($key, now()->addDay(), function () use ($date) {
            $value = $this->client->exchangeRateUsdDetermination($date->toDateString());
            return is_numeric($value) ? (float) $value : null;
        });
    }
}

Cache aggressively, and cache ranges differently from single days

Banxico's token quota is shared and finite. A historical date that is already published never changes, so cache single past dates forever (Cache::forever). For "today" or a range ending today, cache for minutes only:

$ttl = $end->isPast() && ! $end->isToday() ? null : now()->addMinutes(30);

For range lookups, key the cache on series:start:end and return the whole date-keyed array.

Persist history, fetch incrementally

The natural Laravel pattern is a exchange_rates table (series, rate_date, value, unique on series + rate_date) filled by a scheduled job. Then daily reads hit your DB, not the API:

// app/Jobs/SyncBanxicoRates.php
$last = ExchangeRate::where('series', 'SF43718')->max('rate_date');
$from = $last ? Carbon::parse($last)->addDay() : now()->subYear();

$data = $client->exchangeRateUsdDetermination($from->toDateString(), today()->toDateString());

foreach ($data as $date => $value) {
    ExchangeRate::updateOrCreate(
        ['series' => 'SF43718', 'rate_date' => $date],
        ['value' => $value]
    );
}

Schedule it on business days only, mid-afternoon Mexico City time (the Fix is published around midday CT):

$schedule->job(new SyncBanxicoRates)->weekdays()->at('13:30')->timezone('America/Mexico_City');

Use it for non-FX series too

fetchSeries() unlocks the rest of the SIE catalog with the same client — inflation (SP74665-style codes), target rate (SF61745), reserves, etc. Treat series codes as config constants, not literals scattered through code, because Banxico occasionally renames/deprecates series:

// config/banxico.php
'series' => [
    'usd_fix'         => 'SF43718',
    'usd_liquidation' => 'SF60653',
    'target_rate'     => 'SF61745',
],

Integration tips

  • Convert to float/int at the boundary (your service), keep string inside — that preserves the 4-decimal precision Banxico returns and avoids float drift in stored values.
  • Return null rather than throwing for missing dates (weekends/holidays return nothing); throw only for transport/auth failures.
  • If you fetch multiple series at once, fetchSeries(['SF43718','SF60653'], ...) returns a nested array keyed by series code — normalize it into a flat [code, date, value] shape before persisting so your schema stays uniform.
  • In Blade/API resources, always present a formatted string (number_format($rate, 4, '.', '')), not the raw value, so the decimal contract is explicit.

Gotchas and Tips

Weekends and holidays return empty arrays, not errors

Banxico only publishes on business days. exchangeRateUsdLiquidation('2024-01-13', '2024-01-14') (Sat–Sun) returns []. Guard with $data[$date] ?? null and never assume a requested date exists. This is the #1 source of Undefined array key errors in production.

Values can be non-numeric

The SIE returns sentinel strings like "N/E" (no existe) inside otherwise valid payloads. is_numeric() before casting, or you'll silently store 0.0 for a rate that was never published.

Timezone off-by-one

Carbon::now() in a UTC app is a day ahead of Mexico City for part of the evening. Always resolve "today's rate" with now('America/Mexico_City')->toDateString(), otherwise your scheduled job asks for a date Banxico hasn't published yet and writes nothing.

PSR-18 discovery can surprise you

Because discovery is automatic, tests that use Http::fake() do not work — Laravel's HTTP fake only intercepts the Illuminate\Http\Client stack, and this package talks to a PSR-18 client resolved by php-http/discovery. Options, in order of preference:

  1. Mock ExchangeRateService (your wrapper) in feature tests — fast, no network.
  2. Unit-test the wrapper against a stub Psr\Http\Client\ClientInterface if the package's constructor lets you inject one.
  3. Use SieClient only inside integration tests tagged @group external, skipped by default in CI.

If discovery picks the wrong implementation in an app with several HTTP clients, install php-http/guzzle7-adapter explicitly and pass the client instance yourself.

Exception handling is two-layered

The package throws its own exception type (ClienteBanxicoException) for API-level errors (bad token, unknown series). Transport failures surface first from the PSR-18 client (Psr\Http\Client\ClientExceptionInterface), so catch both at the job level:

try {
    $this->client->fetchSeries($code, $from, $to);
} catch (ClientExceptionInterface|ClienteBanxicoException $e) {
    report($e);
    $this->release(300); // rate limit / network — retry, don't fail
}

401 and 429 deserve different handling: 401 is a config error (fail loudly, alert), 429 is quota exhaustion (back off with jitter and do not retry immediately).

Token leakage risk is real

Never log the constructor argument or the request URL — the SIE token is passed as a path segment (/Series/{token}/{series}/...), so it will show up in any HTTP client debug log or Laravel's guzzle middleware stack traces. Add a scrubber on Handler::pushProcessor if you run Sentry/Bugsnag.

Low adoption means you own the edge cases

With 0 dependents and 5 stars, you're effectively the only production user of the version you pin. Two consequences:

  • Pin exactly ("xint0/banxico-php": "3.0.0") and read the diff before bumping. Major versions here are breaking (0.3.0 changed the HTTP layer, 1.0.0 renamed Cliente → SieClient), and there is no deprecation runway.
  • The escape hatch is trivial. The whole library is a handful of files wrapping GET /v1/series/{token}/{series}/{fechaInicio}/{fechaFin}. If you need streaming, a different auth pattern, or the package stalls, replacing it with a 30-line Http::withHeaders(...)->get(...) in your own service is a day's work — which is exactly why you should keep the SieClient call behind your own ExchangeRateService interface. That wrapper is the real abstraction; the package is an implementation detail.

No service provider, no facade, no publishable config

Unlike most Laravel packages, there's nothing to vendor:publish. Binding, config, caching, and scheduling are all yours to write — budget an hour for the initial wiring rather than assuming it's a one-liner.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource