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

Billing Laravel Package

atannex/billing

Atannex Billing is a Laravel/PHP package that provides billing support for your application. It gives you a reusable foundation for billing-related workflows, reducing the need to build and maintain custom billing logic from scratch. Composer-installable for Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. composer require atannex/billing (PHP 8.2+, Laravel 11 or 12 only — this will not install on L10 or earlier, so check composer.json constraints before planning an upgrade).
  2. Publish and run the package's config and migrations (config/billing.php, plus tables for payments, refunds, idempotency keys, webhooks and the audit ledger). Do this before touching any gateway credentials — the migrations define the state machine's persistence layer and the webhook dedupe store, and nothing works correctly without them.
  3. Add the Billable trait to your User (or tenant) model and the Payable trait to whatever represents the thing being paid for — an Order, Invoice, Subscription charge, etc. These traits are where the fluent entry points and refund helpers live.
  4. Set gateway credentials per driver in .env (Stripe, PayPal Orders v2, Flutterwave, MTN, Orange). Keep them in env, not in the published config, so config:cache in deploy stays safe.
  5. First use case: a single Stripe Checkout charge on a test order. Reach for Checkout (hosted redirect) before PaymentIntents — it exercises the whole path (initiate → redirect → verified webhook → state transition → status route) with the least code and no PCI surface.

Where to look first, in order:

  • config/billing.php — the gateway/currency/method/operator routing table and failover settings. This file is the mental model of the whole package; read it top to bottom before writing a controller.
  • The package's registered routes — specifically the JSON status route you'll poll from the frontend.
  • php artisan billing:gateways — confirms which drivers are configured and reachable before you debug your own code.
  • The Billable/Payable trait source — the actual method names and return types matter more than the README examples.

Implementation Patterns

Keep gateway selection out of your controllers. The selling point here is routing by currency, method and phone operator. Encode that policy once in the config routing table (e.g. XAF + mobile money + MTN prefix → MTN driver) and let the package resolve it. Controllers should express intent (charge this payable for this amount in this currency via this method), never if ($currency === 'XAF').

Treat the fluent call as "initiate", not "complete". Every flow converges on the same shape: initiate → customer redirect or USSD prompt → webhook → guarded transition → reconciliation. Write your controller to start the payment and redirect/poll; write a separate webhook handler and let the package's verified, deduplicated webhook processing drive the state. Never set a status column yourself — the state machine is guarded and amount-verified, so direct writes will either be rejected or silently diverge from the ledger.

Async flows (MTN Request to Pay, Orange Cameroon USSD push) need the polling route. Return the JSON status URL to the frontend and poll it rather than inventing your own status endpoint. Pair it with billing:reconcile on a short schedule (every minute or two) so payments that never produce a webhook still settle; without the scheduler entry, async payments sit pending forever and it looks like a package bug when it's a missing cron.

Use idempotency keys on anything retryable. Generate a stable key per logical attempt (e.g. order:123:attempt:1) and reuse it across retries and across failover. Failover to a secondary gateway is only safe if the key travels with the request; otherwise a retry after a timeout can double-charge.

Refunds: model partial refunds as data, not as a special case. Full and partial refunds both go through the Payable/Billable helpers; store the refund record and let the ledger be your source of truth for "how much has been returned" rather than a column you maintain by hand.

Testing with Billing::fake(). Use it for your happy-path feature tests and to assert state transitions and refunds. It will not exercise real operator-prefix routing or failover, so test routing separately by asserting your config resolves to the expected driver for each (currency, method, phone) tuple — those tests are cheap and catch the highest-consequence misconfiguration.

Operational commands to wire into your scheduler/deploy:

  • billing:reconcile — frequent (async settlement).
  • billing:prune — daily, with deliberate retention.
  • billing:gateways — in your deploy smoke check or a health endpoint.
  • billing:mtn-sandbox-user — local/CI setup for MTN sandbox testing.
  • billing:payment — ad-hoc inspection when a support ticket arrives.

Use the audit ledger for support, not your own logs. When a customer says "I paid and it didn't go through", the ledger plus the payment record should answer it; build any internal admin view on top of those rather than on application log lines.

Gotchas and Tips

  • Minor units will bite you. Stripe and PayPal use cents; XAF/XOF have no decimal subdivision. Amount verification is built in — which means a wrong unit assumption surfaces as a rejected transition, not a silent overcharge. Test one XAF and one USD payment end-to-end early. Same caution applies to partial refunds and rounding: the sum of refunds must never exceed the captured amount.
  • Normalize phone numbers before routing. Operator detection relies on Cameroonian prefixes, so pass E.164 (+2376XXXXXXXX) with spaces and dashes stripped. A user-entered 6 7X XX XX XX will not match the prefix table and you'll get a "no gateway" resolution error that looks like a config problem.
  • Failover is a safety net, not a feature. Enable it deliberately and log when it triggers — a silent failover will hide a dead gateway for weeks. Also confirm failover is not attempted for operations that already reached the primary gateway (see idempotency above).
  • Webhook dedupe lives in your database. If the migration didn't run or queue workers are down, you get duplicate processing and confusing state-machine rejections on the second delivery. Check the webhook table first when debugging "it fired twice".
  • billing:prune deletes real records. Set retention longer than your gateway's dispute/chargeback window before enabling it in production. Pruning idempotency keys too aggressively reopens the double-charge risk.
  • Don't write status columns directly. The state machine is guarded by design; bypassing it desynchronizes the ledger from your models and produces bugs that are painful to reconstruct later.
  • config:cache and gateway credentials. If you cache config in deploy, credentials must be resolved from env at runtime or read after cache clear — a stale cached config after rotating a key is a classic 3am failure.
  • Laravel 11/12 only, no prior version, no upgrade path. This is the first release, so budget for API surface you'll want to wrap in your own thin service layer. Pin the version (^1.0) and keep your integration code behind one adapter class so a 1.1 with signature changes touches one file.
  • Extension points to prefer over forking: a custom gateway driver implementing the package contract, and event listeners on the package's payment/refund events for anything you'd otherwise bolt onto the ledger. Forking a payments package is the option of last resort — the state machine and webhook verification are exactly the parts you don't want to maintain yourself.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource