moffhub/billing
Feature-based subscription billing for Laravel with African payment support: gate routes by feature, meter usage, manage subscriptions and invoices, and accept payments via M-Pesa, Airtel Money, Paystack, Flutterwave, and 10+ more through one unified API.
Architecture fit. The package follows conventional Laravel package anatomy — service provider, publishable config and migrations, an artisan seeder (billing:sync-plans --seed), and a trait-based Billable that attaches to any paying entity. That is the lowest-ceremony footprint available in this stack. The domain model (Plan → Feature → Subscription → Usage → Invoice → Payment) is correctly abstracted: gating on feature slugs rather than plan names means repackaging commercial tiers does not require code changes. Feature types (boolean | metered | consumable) cover entitlement, quota, and credit burn-down without workarounds. The PaymentProviderInterface (charge/refund/status/verifyWebhook) is a genuine seam, and PaymentManager::driver() mirrors Laravel's familiar Manager pattern — a team can extend or replace a provider without forking core billing logic. Events fire on every state change, which cleanly pushes notification fanout into the host app.
Integration feasibility. Four commands to install and migrate; gating is declarative via feature:, plan:, and usage: middleware plus @feature/@plan Blade directives, so retrofitting onto existing routes is mechanical rather than invasive. The 32 API endpoints are mounted under a configurable prefix/middleware/rate limit, so they can be scoped behind auth:sanctum rather than exposed by default. GET /payments/options driven by BILLING_ENABLED_PROVIDERS maps directly to a "choose how to pay" screen without per-provider UI plumbing.
Technical risk, ranked.
charge(int $amount, string $currency, array $options) exposes no documented idempotency key (usage recording has transactionId; payments do not advertise one). Blind retry is a double-charge vector.confirm_unverified defaults on). But M-Pesa, T-Kash, Co-op, and Pesapal are unsigned by design, so settlement liveness is coupled to each provider's status API availability.Key questions for the maintainer. Test coverage and CI matrix (Laravel/PHP versions)? Idempotency and retry contract on charge/refund? Proration algorithm on upgrade/downgrade — day-proration or credit-based? Invoice numbering: gapless and sequential under concurrency, and any tax-authority (e.g. eTIMS) export hooks? Behavior after confirm_backoff exhausts — dead-letter or silent drop? Semver and Laravel-major compatibility policy? Is PayOrchestra optional or load-bearing? Security disclosure channel and support commitment?
Stack fit. Compatible with any Laravel app on a supported major; the only structural decision is which model carries the Billable trait (User, Team, or Company). Make that call once — changing it later is a data migration. Adopt the minor-unit convention (integer cents) in your own services and API contracts on day one; mixed units is the canonical billing defect.
Migration path. Greenfield is clean: seed plans and features as deploy-time data, gate routes incrementally. If you already have subscribers on Cashier/Stripe or a homegrown system, do not treat this as a migration tool — there is no importer. Run it in parallel behind a flag for new sign-ups only, or backfill plans/features manually with dual-written status until reconciled. If cards are a material channel today, this is additive to Cashier, not a replacement — which means two reconciliation sources of truth. Scope that cost explicitly.
Compatibility checks before commit. Table-name collisions on plans/subscriptions/invoices are the most likely blocker; confirm a prefix strategy. Confirm PHP and Laravel versions, database (MySQL vs Postgres), and a working queue driver — the webhook confirmation path is queue-dependent. Confirm your auth guard maps cleanly onto the documented Sanctum middleware.
Sequencing — the critical path is commercial, not technical.
manual/bank-transfer driver only, one cohort. Human-settled and fully reversible — this is the kill switch.Maintenance. Fourteen drivers means fourteen integration surfaces that can silently drift when a provider changes an endpoint or signature scheme. Budget a standing 10–15% of one engineer for provider upkeep. Single-maintainer upstream implies fork risk: the PaymentProviderInterface seam makes a provider fork survivable; a fork of ledger or proration logic is not. Pin exact versions, review diffs, and disable automated dependency merges for this package. Keep your own invoice and subscription records of record if you need audit independence.
Support. No commercial support indicated. Establish an escalation path to the maintainer and an internal owner before go-live, or the package is unsupportable for revenue-critical paths. Customer-visible burden lands on you — "my M-Pesa payment didn't reflect" becomes your ticket queue. The async confirmation path improves correctness at the cost of settlement latency; write UI copy and support macros accordingly.
Scaling. Package code is thin; the constraints are provider rate limits, per-day caps, and your queue. Annual and enterprise invoices will fan out into multiple tranches against the 250k cap — more rows, more callbacks, more reconciliation. Size that before selling high-ticket plans. Usage metering writes on every metered action, so the usage table is a hot path: validate write amplification and indexing before high-volume features (API calls, OCR) go live. Webhook routes are public by design — put them behind edge rate limiting and off latency-sensitive pools.
Failure modes to design for.
confirm_unverified to false.charge().billing:sync-plans --seed can change live plans. Gate the command; never run it unattended in production.Ramp-up. One to two engineer-days to a sandbox slice; two to three weeks to production-grade (hardening, observability, reconciliation, runbooks); provider onboarding dominates the calendar. Learning curve is low for Laravel-fluent engineers — the non-obvious domain skill is reconciliation and dispute handling, not the API.
Adoption gate. Promote from "evaluate" to "adopt" only after: sandbox certification across at least two providers with different auth schemes (one signed, one unsigned — e.g. Paystack plus M-Pesa), a written reconciliation runbook, alerting on stuck payments, and a documented upstream support arrangement. Absent those, cap usage at non-revenue-critical gating and metering.
How can I help you explore Laravel packages today?