PHPackages                             moffhub/billing - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. moffhub/billing

ActiveLibrary[Payment Processing](/categories/payments)

moffhub/billing
===============

Feature-based billing for Laravel with African payment provider support (M-Pesa, Paystack, Flutterwave, Pesapal). Plans, feature gating, usage metering, subscriptions, and invoicing.

v0.0.3(2mo ago)04201MITPHPPHP ^8.4|^8.5CI failing

Since Mar 21Pushed 2mo agoCompare

[ Source](https://github.com/Moffhub-Solutions/billing)[ Packagist](https://packagist.org/packages/moffhub/billing)[ Docs](https://github.com/moffhub/billing)[ RSS](/packages/moffhub-billing/feed)WikiDiscussions develop Synced 3w ago

READMEChangelog (3)Dependencies (18)Versions (4)Used By (0)

Moffhub Billing
===============

[](#moffhub-billing)

Feature-based subscription billing for Laravel with first-class African payment provider support.

Define plans, gate features, track usage, accept payments via PayOrchestra (orchestration backbone with smart routing, failover, and reconciliation) or directly through M-Pesa, Airtel Money, KCB, Equity, Co-op Bank, Stanbic, NCBA, IntaSend, Paystack, Flutterwave, or Pesapal — all through one package.

---

Why This Package?
-----------------

[](#why-this-package)

Laravel Cashier is Stripe-only. Spark is closed-source. Neither handles M-Pesa or feature-based gating.

This package gives you:

- **Feature gating** — gate routes by feature slug, not plan name. Plans are just bundles of features.
- **Usage metering** — track and enforce limits on metered features (API calls, OCR scans, entries/month)
- **Provider-agnostic payments** — PayOrchestra orchestration backbone, plus 12 standalone drivers: M-Pesa, Airtel Money, KCB BUNI, Equity Jenga, Co-op Bank, Stanbic, NCBA, IntaSend, Paystack, Flutterwave, Pesapal, or manual/cash — 13 providers behind one interface
- **Subscription lifecycle** — trials, renewals, cancellation, pause/resume, plan upgrades with proration
- **Invoicing** — auto-generated invoices with line items, tax calculation, sequential numbering
- **Full REST API** — 32 endpoints for managing plans, subscriptions, usage, payments, and invoices
- **Event-driven** — every state change fires a Laravel event for integration with SMS, email, webhooks

Quick Start
-----------

[](#quick-start)

### 1. Install

[](#1-install)

```
composer require moffhub/billing
```

### 2. Publish &amp; Migrate

[](#2-publish--migrate)

```
php artisan vendor:publish --tag=billing-config
php artisan vendor:publish --tag=billing-migrations
php artisan migrate
```

### 3. Add the Billable Trait

[](#3-add-the-billable-trait)

Add `Billable` to whichever model represents your paying entity — User, Team, Company, Organization:

```
use Moffhub\Billing\Traits\Billable;

class Company extends Model
{
    use Billable;
}
```

### 4. Seed Plans &amp; Features

[](#4-seed-plans--features)

```
php artisan billing:sync-plans --seed
```

Or create them programmatically:

```
use Moffhub\Billing\Models\Plan;
use Moffhub\Billing\Models\Feature;

// Register features
Feature::create([
    'slug' => 'ocr_scanning',
    'name' => 'OCR Scanning',
    'type' => 'metered',       // boolean | metered | consumable
    'is_addon' => true,
    'addon_price' => 2000,     // KES 20.00 (in cents)
]);

// Create a plan
Plan::create([
    'ulid' => Str::ulid()->toBase32(),
    'name' => 'Standard',
    'slug' => 'standard',
    'base_price' => 750000,     // KES 7,500.00
    'billing_cycle' => 'monthly',
    'trial_days' => 14,
    'features' => ['gatebook', 'incidents', 'shifts', 'analytics_reports'],
    'limits' => [
        'max_posts' => 10,
        'max_guards' => 25,
        'ocr_scanning' => 100,
    ],
]);
```

### 5. Subscribe

[](#5-subscribe)

```
// Subscribe with a 14-day trial
$company->subscribe('standard')
    ->trialDays(14)
    ->provider('mpesa')
    ->create();

// Check subscription status
$company->subscribed();          // true
$company->onPlan('standard');    // true
$company->onTrial();             // true
```

### 6. Gate Features

[](#6-gate-features)

**Middleware** — protect routes by feature or plan:

```
// Require a specific feature (AND logic)
Route::middleware(['feature:ocr_scanning'])->group(function () {
    Route::post('ocr/scan', ScanController::class);
});

// Require one of several plans (OR logic)
Route::middleware(['plan:professional,enterprise'])->group(function () {
    Route::get('analytics/advanced', AnalyticsController::class);
});

// Combine feature access + usage limit enforcement
Route::middleware(['feature:ocr_scanning', 'usage:ocr_scanning'])->group(function () {
    Route::post('ocr/scan', ScanController::class);
});
```

**Blade** — show/hide UI elements:

```
@feature('shifts')
    Shift Management
@else
    Upgrade to unlock Shifts
@endfeature

@plan('professional')
    PRO
@endplan
```

**In code** — check programmatically:

```
if ($company->hasFeature('ocr_scanning')) {
    // perform scan
}

$remaining = $company->remainingQuota('ocr_scanning'); // 58
$percentage = $company->usagePercentage('ocr_scanning'); // 0.42
```

### 7. Track Usage

[](#7-track-usage)

```
use Moffhub\Billing\Services\UsageService;

$usage = app(UsageService::class);

// Record a usage event
$usage->record($company, 'ocr_scanning');

// Record with quantity and deduplication
$usage->record($company, 'ocr_scanning', quantity: 5, transactionId: 'scan-abc-123');

// Check limits
$usage->isWithinLimit($company, 'ocr_scanning'); // true/false
$usage->getUsage($company, 'ocr_scanning');      // 42
```

Usage alerts fire automatically at configurable thresholds (80%, 90%, 100%).

### 8. Accept Payments

[](#8-accept-payments)

```
use Moffhub\Billing\PaymentManager;

$manager = app(PaymentManager::class);

// Charge via the default provider
$result = $manager->driver()->charge(750000, 'KES', [
    'phone' => '254712345678',    // for M-Pesa STK Push
]);

// Or specify a provider
$result = $manager->driver('paystack')->charge(750000, 'KES', [
    'email' => 'customer@example.com',
]);

// Collect via KCB BUNI (M-Pesa, Airtel, T-Kash, VOOMA, or bank)
$result = $manager->driver('kcb')->charge(50000, 'KES', [
    'phone' => '0712345678',
    'payment_channel' => 'mpesa',  // mpesa, airtel, tkash, vooma, bank
    'reference' => 'INV-001',
]);

// Collect via Airtel Money
$result = $manager->driver('airtel')->charge(50000, 'KES', [
    'phone' => '0733123456',
    'reference' => 'INV-002',
]);

// Transfer via Co-op Bank PesaLink (to any Kenyan bank)
$result = $manager->driver('coopbank')->charge(100000, 'KES', [
    'destination_account' => '0011547896523',
    'bank_code' => '01',           // KCB
    'transfer_type' => 'pesalink',
]);

// Route through PayOrchestra — channel hint picks the right connector
$result = $company->chargeVia('mpesa', 750000, 'KES', [
    'phone' => '254712345678',
    'reference' => 'INV-001',
]);

$result = $company->chargeVia('card', 750000, 'KES', [
    'email' => 'customer@example.com',
    'reference' => 'INV-001',
]);

// PayOrchestra hosted checkout — redirect the payer
$session = $company->hostedPayment(750000, 'KES', [
    'description' => 'Invoice #INV-001',
    'success_url' => 'https://app.example.com/payment/success',
    'cancel_url' => 'https://app.example.com/payment/cancel',
]);

return redirect($session['session_url']);
```

### 9. Generate Invoices

[](#9-generate-invoices)

```
use Moffhub\Billing\Models\Invoice;

// Via API
POST /api/billing/invoices
{
    "items": [
        {"description": "Standard Plan - March 2026", "unit_price": 750000},
        {"description": "OCR Add-on - 42 scans",     "unit_price": 2100, "feature_slug": "ocr_scanning"}
    ],
    "tax_rate": 16.0
}

// Tax is calculated automatically (VAT 16% default for Kenya)
```

---

API
---

[](#api)

The package ships with a full REST API. All endpoints are documented in [docs/API.md](docs/API.md).

ResourceEndpointsDescriptionPlans5List, show, create, update, deleteFeatures6List, list add-ons, show, create, update, deleteSubscriptions8Subscribe, current, change plan, cancel, pause, resumeAdd-ons3List, enable, disableUsage3Summary, detail, recordPayments4List, initiate, show, refundInvoices6List, create, show, send, void, mark-paidWebhooks12M-Pesa, Paystack, Flutterwave, Pesapal, Airtel, KCB, Jenga, Co-op, Stanbic, NCBA, IntaSend, PayOrchestra callbacksRoutes are configurable:

```
// config/billing.php
'routes' => [
    'enabled' => true,
    'prefix' => 'api/billing',
    'middleware' => ['api', 'auth:sanctum'],
    'rate_limit' => 60,
],
```

---

Payment Providers
-----------------

[](#payment-providers)

ProviderDriverPayment Methods**PayOrchestra** ⭐`payorchestra`Multi-channel via backbone — routes to M-Pesa, cards, bank transfers, etc. with smart routing, failover, reconciliation, hosted checkout**M-Pesa**`mpesa`STK Push, C2B, B2C**Airtel Money**`airtel`C2B collections, B2C disbursements**KCB BUNI**`kcb`M-Pesa, Airtel, T-Kash, VOOMA, bank (multi-channel)**Equity Jenga**`jenga`Cards, mobile money, bank transfers**Co-op Bank**`coopbank`PesaLink (any bank), internal transfers, balance queries**Stanbic Bank**`stanbic`STK Push, mobile money, bank transfers**NCBA**`ncba`PesaLink, IPN Push**IntaSend**`intasend`M-Pesa STK Push, cards, bank, PesaLink**Paystack**`paystack`Cards, bank, mobile money**Flutterwave**`flutterwave`Cards, M-Pesa, MTN MoMo, bank**Pesapal**`pesapal`Cards, M-Pesa, Airtel Money**Manual**`manual`Cash, bank transfer, chequeProviders without direct APIs (Telkom T-Kash, Family Bank, DTB) can be accessed through KCB BUNI, Pesapal, or Flutterwave. See [docs/PROVIDERS.md](docs/PROVIDERS.md) for full integration guides.

All providers implement `PaymentProviderInterface`:

```
interface PaymentProviderInterface
{
    public function charge(int $amount, string $currency, array $options = []): array;
    public function refund(string $providerPaymentId, ?int $amount = null, array $options = []): array;
    public function getPaymentStatus(string $providerPaymentId): string;
    public function verifyWebhook(Request $request): bool;
    public function parseWebhook(Request $request): array;
    public function isConfigured(): bool;
    public function getName(): string;
}
```

Add your own provider:

```
class MyProvider extends BasePaymentProvider
{
    public function charge(int $amount, string $currency, array $options = []): array
    {
        // Your implementation
        return ['success' => true, 'provider_payment_id' => '...', 'status' => 'completed', ...];
    }

    public function getName(): string { return 'my-provider'; }
    public function isConfigured(): bool { return true; }
}
```

---

Events
------

[](#events)

Every state change fires a Laravel event so you can integrate with your SMS, email, analytics, or approval workflows.

EventFired WhenPayload`SubscriptionCreated`New subscription`$subscription``SubscriptionCancelled`Subscription cancelled`$subscription`, `$immediately``SubscriptionRenewed`Subscription renewed`$subscription``PlanChanged`Plan upgrade/downgrade`$subscription`, `$oldPlan`, `$newPlan``PaymentReceived`Successful payment`$payment``PaymentFailed`Failed payment`$payment`, `$reason``UsageLimitApproaching`Usage hits 80/90/100%`$billable`, `$featureSlug`, `$percentage``FeatureAccessDenied`Unauthorized feature access`$billable`, `$featureSlug`, `$reason`Example listener:

```
// In EventServiceProvider
protected $listen = [
    PaymentReceived::class => [SendPaymentReceiptSms::class],
    UsageLimitApproaching::class => [SendUsageWarningNotification::class],
];
```

---

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag=billing-config
```

Key settings:

```
return [
    'currency' => 'KES',                    // Default currency (ISO 4217)
    'default_provider' => 'mpesa',           // Default payment provider
    'billable_model' => App\Models\Company::class,

    'tax' => [
        'enabled' => true,
        'default_rate' => 16.0,              // Kenya VAT
    ],

    'features' => [
        'cache_ttl' => 300,                  // Feature access cache (seconds)
    ],

    'usage' => [
        'alert_thresholds' => [80, 90, 100], // Fire events at these %
        'allow_overage' => false,            // Hard-stop or allow over-limit
    ],

    'subscriptions' => [
        'grace_period_days' => 7,            // Days before cancelling past-due
        'dunning_schedule' => [1, 3, 7],     // Retry failed charges on these days
        'allow_pause' => true,
    ],

    'providers' => [
        'payorchestra' => [
            'base_url' => env('PAYORCHESTRA_URL', 'https://backbone.payorchestra.com'),
            'api_key' => env('PAYORCHESTRA_API_KEY'),
            'org_id' => env('PAYORCHESTRA_ORG_ID'),
            'webhook_secret' => env('PAYORCHESTRA_WEBHOOK_SECRET'),
        ],
        'mpesa' => [
            'consumer_key' => env('MPESA_CONSUMER_KEY'),
            'consumer_secret' => env('MPESA_CONSUMER_SECRET'),
            'shortcode' => env('MPESA_SHORTCODE'),
            // ...
        ],
        // airtel, kcb, jenga, coopbank, stanbic, ncba, intasend,
        // paystack, flutterwave, pesapal, manual
    ],
];
```

---

Artisan Commands
----------------

[](#artisan-commands)

```
# Seed default plans and features
php artisan billing:sync-plans --seed

# List current plans and features
php artisan billing:sync-plans

# Health check — providers, subscriptions, config
php artisan billing:health
```

---

Architecture
------------

[](#architecture)

See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full code map, entity relationships, data flow diagrams, and event map.

Key design decisions:

- **Polymorphic billable** — `Billable` trait works on any Eloquent model via `morphMany`. Your billing entity can be User, Team, Company, or Organization.
- **Feature slugs over plan names** — gate access by feature (`hasFeature('shifts')`) not plan (`onPlan('professional')`). This means plan restructuring doesn't break your code.
- **JSON features/limits on Plan** — features are stored as a JSON array on the plan, limits as a JSON object. No pivot tables, no joins. Fast reads, easy to reason about.
- **Usage deduplication** — `transaction_id` on usage events prevents double-counting (idempotent recording).
- **Provider Manager pattern** — `PaymentManager` extends Laravel's `Manager` class. Same driver pattern as `Mail`, `Queue`, `Cache`. Add providers without changing core code.
- **Event-driven integration** — the package fires events, never imports other packages. SMS, approvals, and USSD integration happen via listeners in your app.

---

Testing
-------

[](#testing)

```
cd packages/moffhub/billing
composer install
vendor/bin/phpunit
```

**716 tests, 1,840 assertions** covering:

- Plan CRUD, feature checking, limits
- Subscription lifecycle (create, trial, cancel, pause/resume)
- Feature gating via Billable trait
- Usage recording, deduplication, limit enforcement
- All 13 payment providers (charge, refund, status, webhooks) including PayOrchestra orchestration
- Invoice generation, tax calculation, proration
- Coupon and promotion code logic
- Admin bypass, encryption, event dispatching

---

Ecosystem
---------

[](#ecosystem)

This package is part of the Moffhub Laravel package suite:

PackagePurposeIntegration[moffhub/billing](.)Subscriptions, feature gating, paymentsCore[moffhub/maker-checker](../maker-checker)Approval workflowsGate high-value billing actions (refunds, plan changes)[moffhub/sms-handler](../sms-handler)Multi-provider SMSSend payment receipts, usage warnings, invoice reminders[moffhub/ussd](../ussd)USSD menus"Check balance", "Upgrade plan" via USSDThe packages are fully decoupled — billing fires events, your app wires them to SMS, approvals, or USSD as needed.

---

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 12.0+
- A database (MySQL, PostgreSQL, SQLite)

---

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance86

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

3

Last Release

88d ago

PHP version history (2 changes)v0.0.1PHP ^8.3|^8.4|^8.5

v0.0.3PHP ^8.4|^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/6b5c3549bf8f8bab163b760fe2023fe357a3c85ed366e052a735ff5c50f84518?d=identicon)[Moffrough](/maintainers/Moffrough)

---

Top Contributors

[![MOFFROUGH](https://avatars.githubusercontent.com/u/20206597?v=4)](https://github.com/MOFFROUGH "MOFFROUGH (26 commits)")

---

Tags

laravelbillinglaravel-packagepaymentssubscriptioninvoicingsaasrecurring paymentsmpesakenyafeature-flagspaystackflutterwaveafricapesapalmetered billingusage billingfeature-gating

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/moffhub-billing/health.svg)

```
[![Health](https://phpackages.com/badges/moffhub-billing/health.svg)](https://phpackages.com/packages/moffhub-billing)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M137](/packages/laravel-cashier)[laravel/cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

267880.7k3](/packages/laravel-cashier-paddle)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[musahmusah/laravel-multipayment-gateways

A Laravel Package that makes implementation of multiple payment Gateways endpoints and webhooks seamless

882.2k1](/packages/musahmusah-laravel-multipayment-gateways)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
