Skip to content

feat: MTN Cameroon + Orange Guinea + Wave Senegal provider integration (#1795-#1798) - #1891

Merged
sublime247 merged 1 commit into
sublime247:mainfrom
devsimze:feat/issues-1795-1798
Aug 27, 2026
Merged

feat: MTN Cameroon + Orange Guinea + Wave Senegal provider integration (#1795-#1798)#1891
sublime247 merged 1 commit into
sublime247:mainfrom
devsimze:feat/issues-1795-1798

Conversation

@devsimze

Copy link
Copy Markdown
Contributor

Summary

Hardens the shared provider authorization module and integrates three
country-specific payment providers into the Mobile Money ↔ Stellar Bridge.

Closes #1795
Closes #1796
Closes #1797
Closes #1798


#1795 — Fix token renewal scheduler in bridge authorization module

src/services/providers/baseProvider.ts (the base class every provider extends).

  • Bug: cacheToken(token, expiresIn) did Date.now() + expiresIn * 1000
    unconditionally. When a token endpoint omits or garbles expires_in,
    tokenExpiresAt became NaN, isTokenValid() was permanently false, and
    every API call re-authenticated (token-exchange storm against the
    provider). Added normalizeExpiresInSeconds() (coerces numeric strings,
    falls back to DEFAULT_TOKEN_TTL_SECONDS for missing / NaN / ≤ 0 /
    Infinity) plus a Number.isFinite guard in isTokenValid().
  • Feature: the class documented "refreshed proactively" but only refreshed
    lazily. Added a real scheduler: scheduleTokenRenewal() /
    cancelTokenRenewal() / destroy(). The timer fires leeway before
    expiry (clamped to a minimum), is unref()ed, replaces any prior timer,
    no-ops after destroy(), and retries once on failure. Implemented with
    #private fields so it never collides with subclasses that already declare
    private destroyed (e.g. OrangeProvider).

#1796 — Integrate MTN Cameroon Mobile Money API

src/services/mobilemoney/providers/mtn.ts

  • requestPayment:
    • currency was hard-coded "EUR" → now MTN_CURRENCY (default XAF, the
      Cameroon MoMo settlement currency);
    • the collection POST sent no Authorization header (always 401) →
      now sends Authorization: Bearer <token>;
    • added the required X-Reference-Id UUID and return it to the caller so a
      transaction can actually be polled afterwards;
    • X-Target-Environment now comes from config instead of the literal
      "sandbox".
  • sendBatchPayout: implemented the status polling loop and
    (phoneNumber, amount) fallback matching that the existing test suite
    already expected (MTN_BATCH_PAYOUT_MAX_ATTEMPTS /
    MTN_BATCH_PAYOUT_POLL_DELAY_MS).
  • Wired scheduleTokenRenewal into getAccessToken; removed a field
    initializer that silently ignored MTN_BASE_URL.
  • Fixed the pre-existing mtn.sendBatchPayout.test.ts (referenced an
    undefined env, so the whole suite failed to run).

#1797 — Integrate Orange Guinea payment gateway

  • Bug: verifyOrangeGuineaCallbackSignature reads
    providers.orangeGuinea.callbackSecret /
    providers.orangeGuinea.callbackSignatureHeader, but those keys were never
    declared in appConfig (Orange Madagascar has them). Result:
    cannot find configuration param thrown on every request to the
    already-wired /api/orange-guinea/callback route. Added both keys.
  • Added the middleware + route test coverage Orange Guinea was missing,
    mirroring the Orange Madagascar suites.

#1798 — Implement Wave Senegal payment routing

  • wave_senegal is now a first-class routable provider:
    • enum + limits in src/config/providers.ts,
    • config block in appConfig.ts,
    • ProviderName + health-check entry in healthCheck.ts,
    • KnownProvider + fallback chain (["orange", "sms_portal"]) in
      networkOutageFallbackService.ts.
  • New src/services/mobilemoney/waveSenegalRouting.ts:
    • isWaveSenegalRoute() — routes when the wave_senegal key is chosen
      explicitly or, absent a forced provider, when the MSISDN is a valid
      Senegalese number (+221 + 9 digits);
    • routeWaveSenegalPayment / routeWaveSenegalPayout — validate the
      MSISDN and the amount against the configured limits before delegating to
      WaveSenegalProvider, with a DI seam for testing.

Docs

New env vars documented in .env.example (MTN_CURRENCY, MTN batch-poll
tuning, ORANGE_GUINEA_CALLBACK_*, the WAVE_* block).


Testing

  • 39 new unit tests across baseProvider, MTN requestPayment / batch
    payout, Orange Guinea middleware + routes, and Wave Senegal routing.
  • All new tests pass together with the adjacent existing suites
    (fallbackRouter, networkOutageFallbackService, appConfig, Orange
    Madagascar callback middleware + routes, mtnPayoutConfirmation
    integration) — 13 suites / 144 tests green.
  • npx tsc --noEmit: no new errors.
  • eslint / prettier: clean on all changed files.
  • Pre-existing unrelated suite failures on main (mtnUganda.test.ts and
    moov.test.ts missing mock modules, the Pact mtnProvider.test.ts ESM
    parse error, kycUpload.test.ts, config/database.test.ts) are untouched
    and out of scope here.

🤖 Generated with Claude Code

Addresses four Mobile Money <-> Stellar Bridge issues:

sublime247#1795 Fix token renewal scheduler in bridge authorization module
  - baseProvider.cacheToken() no longer poisons the cache when a token
    endpoint omits/garbles expires_in (was NaN -> isTokenValid() always
    false -> re-auth on every call). Added normalizeExpiresInSeconds()
    and a finite-check in isTokenValid().
  - Added a real proactive renewal scheduler (scheduleTokenRenewal /
    cancelTokenRenewal / destroy): unref'd timer fires `leeway` before
    expiry, replaces any prior timer, no-ops after destroy(), retries
    once on failure. Uses #private fields to avoid clashing with
    subclasses that already declare `private destroyed`.

sublime247#1796 Integrate MTN Cameroon Mobile Money API
  - requestPayment: currency is now MTN_CURRENCY (default XAF for
    Cameroon) instead of hard-coded EUR; the collection POST now sends
    the Authorization: Bearer header (was unauthenticated) and the
    required X-Reference-Id UUID, which is returned to the caller for
    status polling; X-Target-Environment comes from config.
  - sendBatchPayout: implemented status polling + (phone, amount)
    fallback matching that the existing test suite expected.
  - Wired scheduleTokenRenewal into getAccessToken; stopped ignoring
    MTN_BASE_URL.
  - Fixed the pre-existing mtn.sendBatchPayout.test.ts (undefined `env`).

sublime247#1797 Integrate Orange Guinea payment gateway
  - appConfig: added providers.orangeGuinea.callbackSecret and
    callbackSignatureHeader. Their absence made the wired-up
    /api/orange-guinea/callback route throw
    "cannot find configuration param" on every callback.
  - Added the missing middleware + route test coverage (mirrors Orange
    Madagascar).

sublime247#1798 Implement Wave Senegal payment routing
  - wave_senegal is now a first-class routable provider: enum + limits
    (config/providers.ts), config block (appConfig.ts), health-check
    entry, KnownProvider + fallback chain (networkOutageFallbackService).
  - New waveSenegalRouting.ts: isWaveSenegalRoute() (explicit
    wave_senegal key or a +221 MSISDN) plus routeWaveSenegalPayment /
    routeWaveSenegalPayout that validate the MSISDN and amount against
    the configured limits before delegating.

Docs: new env vars added to .env.example.

Tests: 39 new unit tests across baseProvider, MTN requestPayment/batch,
Orange Guinea middleware + routes, and Wave Senegal routing; all green
with the adjacent existing suites. No new tsc/eslint/prettier issues.
(Pre-existing unrelated suite failures -- mtnUganda, moov, pact, kyc --
are broken on main too and untouched here.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@devsimze Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@sublime247
sublime247 merged commit d964898 into sublime247:main Aug 27, 2026
13 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants