malico/laravel-mesomb
A Laravel integration for the MeSomb payment API, offering straightforward configuration, service bindings, and convenient helpers for initiating mobile money payments and handling transactions in Laravel applications.
Read this first: release 1.6 is the final release of this package. It has been superseded by
hachther/laravel-mesomb, which is maintained by the MeSomb team directly. New projects should start on the successor package; existing projects should treat 1.6 as a frozen dependency and budget a migration. Everything below applies equally well to both packages since the underlying MeSomb HTTP API and the general shape of the wrapper are the same.
Install and configure
composer require <vendor>/laravel-mesomb (pin to 1.6.* — no further releases are coming).php artisan vendor:publish --tag=mesomb-config (tag name varies; check the service provider's publishes() call)..env:
MESOMB_SANDBOX=true for development, false in productionphp artisan config:clear (a stale config:cache is the single most common "my keys aren't being picked up" cause).Where to look first
config/mesomb.php — the canonical list of options, base URI, and credential names. Match your .env keys against this file rather than the README, which may lag behind.app()->make().README + CHANGELOG — 1.6 is the terminal entry; everything above it is your effective spec.tests/ directory (if present) — the fastest way to see real request/response payload shapes.First use case
The archetypal first integration is a collection: request a payment from a customer's mobile money wallet (MTN MoMo / Orange Money) and reconcile the result. A minimal flow:
$response = Mesomb::makeCollect(
amount: 1000,
service: 'MTN', // or 'ORANGE'
payer: '2376XXXXXXXX', // MSISDN with country code
nonce: $payment->reference // your idempotency key
);
Then persist the returned transaction reference/id and immediately decide: do you trust the synchronous response, or do you poll getStatus() / wait for a webhook? For anything involving money, treat the synchronous response as acceptance, not settlement — see Gotchas.
Wrap the package behind your own interface. This is the pattern that makes the 1.6 EOL announcement a non-event. Define a small contract in your app (PaymentGateway with collect(), disburse(), status()), bind the package-backed implementation in a service provider, and never reference the facade outside that one class. When you move to hachther/laravel-mesomb, you rewrite one adapter and nothing else.
Persist a local transaction record before you call out. Table with reference (your own UUID), provider_id, amount, currency, service, status, raw_response. Write the row first, then call the API, then update. This gives you idempotency, an audit trail, and a queue of "pending" transactions to reconcile after a crash or timeout.
Use your own nonce/reference for idempotency. Pass a stable per-attempt identifier rather than letting the package or the API generate one. If a request times out, you can safely re-query status by your reference instead of re-issuing a charge.
Reconcile asynchronously. Production pattern: controller creates the intent, dispatches a job, the job calls the gateway, then a scheduled command sweeps pending transactions older than N minutes and calls status() on each. This covers the very common case where MeSomb accepted the request but the customer never entered their PIN.
Webhooks (if the package exposes a handler). Point the callback route at a controller that: (1) locates the local transaction by reference, (2) re-fetches status from the API rather than trusting the payload body, (3) updates state inside a DB transaction, (4) returns 200 fast and does the rest on a queue. Always make webhook handling idempotent — providers retry.
Multi-environment config. Keep sandbox and live credentials in separate .env blocks and drive them via config('mesomb.sandbox') / a services entry. Never branch on app()->environment() inside business logic; branch on config so tests can flip it.
Testing. Http::fake() around the client is the cheapest approach: assert the outgoing payload shape and stub the response. Combined with the adapter interface, you can also bind a FakePaymentGateway for feature tests and keep the suite fully offline. Do not hit the sandbox in CI.
Migration path to the successor. Both packages wrap the same API, so the mapping is mostly mechanical: install hachther/laravel-mesomb, re-publish its config, copy credentials across, re-point your adapter, run your existing tests against Http::fake() stubs updated to the new namespace. Do it behind a feature flag so you can flip back per-environment.
EOL is the headline gotcha. 1.6 receives no security patches, no PHP/Laravel compatibility updates, and no bug fixes. Pin the version in composer.json, add a README/ticket noting the migration, and do not build new features on it. If you're on Laravel 12+ or PHP 8.4+, verify installability before committing — a dead package will not be updated for the next framework major.
Amounts are integers in minor-ish units. XAF has no decimal subdivision, but sending 10.00 or a float will produce confusing validation errors or silently wrong charges. Always pass int, and validate at the edge ($request->validate(['amount' => 'required|integer|min:100'])).
Phone numbers must be MSISDN with country code, typically 2376XXXXXXXX for Cameroon, no +, no spaces. A number that works in the sandbox with one format may fail live. Normalize in a single helper.
Never blindly retry a collection. A timeout does not mean failure — it means unknown. Retrying with a fresh nonce can double-charge the customer. Retry only status lookups; for charges, reconcile by reference.
Set explicit HTTP timeouts. Package defaults are often generous or absent. Wrap calls in ->timeout(...), and make sure your queue worker's --timeout exceeds it, otherwise the job is killed mid-request and you get an orphaned charge with no local record.
Error handling is inconsistent between transport and business failures. A network error throws; a declined payment often returns a 200 with a non-success status/code in the body. Handle both: catch transport exceptions and inspect the response object. Don't rely on HTTP status alone.
The synchronous response is not settlement. Treat SUCCESS on the initial call as "request accepted." Only your status check or webhook flips the transaction to paid.
Webhook payloads may not be signed in packages of this vintage. If there's no signature verification, the safe pattern is verify-by-refetch: on callback, call status() for that reference and use that answer. Also make sure the callback route is POST, excluded from CSRF, and not behind auth middleware.
Local webhook development requires a public URL (ngrok, expose, or Laravel Valet's share command). Document it in the team README — this trips up every new developer. Fall back to the status-sweep command while developing.
Sensitive data in logs. The package may log full request/response payloads including keys and phone numbers at debug level. Check config/logging.php in production and consider a redacting tap or setting the package's log channel to a null driver.
Config caching bites. After any .env or config/mesomb.php change: php artisan config:clear. In Docker, verify the env vars actually reach the container — env() calls inside package config files return null once config is cached.
Extension points worth knowing: the base URI is usually configurable (useful for pointing at a mock server in tests), the underlying Guzzle/HTTP client can be swapped via container binding if the package resolves it through the container, and the facade is typically a thin proxy — so Mesomb::shouldReceive(...) mocks work, but prefer the adapter interface over facade mocking to keep tests resilient.
Watch the fork, not this repo. Set a reminder to check hachther/laravel-mesomb's releases before your next major upgrade cycle; that's where fixes for anything you hit here will land.
How can I help you explore Laravel packages today?