PHPackages                             smartguycodes/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. smartguycodes/billing

ActiveLibrary

smartguycodes/billing
=====================

A configurable Laravel billing package for SaaS businesses in Kenya. Handles M-Pesa STK Push, subscriptions, dunning, and renewal reminders.

20Blade

Since Apr 6Pushed 2d agoCompare

[ Source](https://github.com/SmartGuyCodes/smart-billing)[ Packagist](https://packagist.org/packages/smartguycodes/billing)[ RSS](/packages/smartguycodes-billing/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Smart Billing
=============

[](#smart-billing)

**Drop-in Laravel billing for the Kenyan SaaS market.**

Zero plumbing. Define your plans, configure M-Pesa, and your billing loop is sorted — subscriptions, STK Push, callbacks, dunning, renewal reminders, and an admin UI all included.

---

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

[](#architecture)

```
    smart-billing/
        ├── src/
        │   ├── BillingServiceProvider.php      # Auto-discovery, publishes, registers
        │   ├── Concerns/Billable.php           # Trait for your User model
        │   ├── Contracts/PaymentDriver.php     # Interface for all payment drivers
        │   ├── Drivers/MpesaDriver.php         # Full Daraja API — STK Push, C2B, Refunds
        │   ├── Facades/Billing.php
        │   ├── Services/
        │   │   ├── BillingManager.php          # Extensible driver manager
        │   │   ├── PaymentService.php          # Orchestrates initiate → callback → renew
        │   │   ├── SubscriptionService.php     # subscribe, cancel, resume, changePlan
        │   │   └── DunningService.php          # Retry logic, suspension, cancellation
        │   ├── Models/                         # 5 Eloquent models with scopes & helpers
        │   ├── Http/Controllers/               # Admin UI + REST API + Webhook handlers
        │   ├── Events/                         # 8 events for your listeners
        │   ├── Notifications/RenewalReminder
        │   ├── Console/Commands/               # billing:install/renewals/dunning/reminders
        │   └── Support/PaymentResult.php       # Typed result value object
        ├── config/billing.php                  # All configurable — driver, dunning, reminders
        ├── database/migrations/                # 6 tables created in one migration
        ├── resources/views/admin/              # Dark-themed admin UI — dashboard, all CRUD
        └── routes/web.php + api.php
```

- **Drivers**: M-Pesa (Daraja) out of the box, with a simple interface to add Stripe, Flutterwave, etc.
- **Transactions**: Every payment attempt is stored with a unique reference, status, and metadata
- **Subscriptions**: Plan-based billing with trial periods, grace periods, and dunning support
- **Admin UI**: View transactions, manage plans, and monitor subscription statuses
- **Scheduler**: Daily commands to process renewals, send reminders, and handle dunning

---

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

[](#requirements)

PHP^8.2Laravel^11 | ^12M-PesaDaraja API credentials---

Installation
------------

[](#installation)

```
composer require smartguycodes/billing
```

```
php artisan billing:install
```

This publishes the config, runs migrations, and optionally seeds sample plans.

---

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

[](#configuration)

### .env

[](#env)

```
    # Core
    BILLING_DRIVER=mpesa
    BILLING_CURRENCY=KES
    BILLING_CURRENCY_SYMBOL=KSh

    # M-Pesa (Daraja)
    MPESA_ENV=sandbox                   # sandbox | production
    MPESA_CONSUMER_KEY=your_key
    MPESA_CONSUMER_SECRET=your_secret
    MPESA_SHORTCODE=174379
    MPESA_PASSKEY=your_passkey
    MPESA_TYPE=paybill                  # paybill | till
    MPESA_CALLBACK_URL=https://yourapp.com/api/billing/webhooks/mpesa/callback
    MPESA_TIMEOUT_URL=https://yourapp.com/api/billing/webhooks/mpesa/timeout

    # Admin UI
    BILLING_ADMIN_PREFIX=billing-admin

    # Invoice
    BILLING_INVOICE_PREFIX=INV
    BILLING_COMPANY_NAME="My SaaS Co"
    BILLING_REF_PREFIX=TXN
```

### Billable Model

[](#billable-model)

Add the `Billable` trait to your `User` model:

```
    use SmartGuyCodes\Billing\Concerns\Billable;

    class User extends Authenticatable
    {
        use Billable;
    }
```

---

Usage
-----

[](#usage)

### Initiate an M-Pesa STK Push

[](#initiate-an-m-pesa-stk-push)

```
    use SmartGuyCodes\Billing\Facades\Billing;

    $result = $user->charge([
        'amount'           => 999,
        'account_number'   => '0712345678',   // Customer phone
        'account_type'     => 'mobile',
        'transaction_type' => 'income',
        'description'      => 'Starter Plan - March 2025',
    ]);

    if ($result->isPending()) {
        // STK Push sent — wait for callback
        $checkoutId = $result->checkoutRequestId;
    }
```

### Subscribe to a Plan

[](#subscribe-to-a-plan)

```
    // By slug
    $plan = BillingPlan::where('slug', 'pro')->first();
    $subscription = $user->subscribeTo($plan);

    // With trial
    $subscription = $user->subscribeTo($plan, ['trial_days' => 14]);
```

### Check Subscription Status

[](#check-subscription-status)

```
    $user->isSubscribed();           // bool
    $user->onTrial();                // bool
    $user->subscribedTo('pro');      // bool

    $sub = $user->activeSubscription();
    $sub->daysUntilRenewal();        // int
    $sub->isActive();                // bool
    $sub->onGracePeriod();           // bool
```

### Cancel &amp; Resume

[](#cancel--resume)

```
    $user->cancelSubscription();                    // At period end
    $user->cancelSubscription(immediately: true);   // Right now

    // Resume within grace period
    $sub->resume();
```

### Verify a Transaction

[](#verify-a-transaction)

```
    $transaction = BillingTransaction::where('reference_no', 'TXN-...')->first();
    $result = app(\SmartGuyCodes\Billing\Services\PaymentService::class)->verify($transaction);
```

---

Transaction Data Model
----------------------

[](#transaction-data-model)

Every transaction stores the full API User Layer:

FieldDescription`reference_no`System-generated — `TXN-250101120000-ABCD``invoice_number``INV-2025-00042``client_no`Your identifier for the customer`account_number`Mobile number, bank account, or card number`account_type``mobile` | `bank` | `card``transaction_type``income` | `expense``amount`Float`currency`e.g. `KES``status``pending` → `completed` | `failed` | `refunded``driver``mpesa` | `stripe` | `flutterwave``gateway_ref`M-Pesa receipt number or Stripe charge ID---

M-Pesa Webhook URLs
-------------------

[](#m-pesa-webhook-urls)

Register these in the Safaricom Daraja portal:

TypeURLSTK Callback`https://yourapp.com/api/billing/webhooks/mpesa/callback`STK Timeout`https://yourapp.com/api/billing/webhooks/mpesa/timeout`C2B Validation`https://yourapp.com/api/billing/webhooks/mpesa/validation`C2B Confirmation`https://yourapp.com/api/billing/webhooks/mpesa/confirmation`---

REST API
--------

[](#rest-api)

All routes are prefixed `/api/billing` and require `auth:sanctum`.

```
    POST   /api/billing/pay
    GET    /api/billing/transactions/{ref}
    POST   /api/billing/transactions/{ref}/verify

    GET    /api/billing/plans
    POST   /api/billing/subscribe
    GET    /api/billing/subscription
    DELETE /api/billing/subscription
    POST   /api/billing/subscription/resume

    GET    /api/billing/invoices
    GET    /api/billing/invoices/{id}
```

### Example: Initiate Payment

[](#example-initiate-payment)

```
    POST /api/billing/pay
    Authorization: Bearer {token}
    Content-Type: application/json

    {
    "amount": 999,
    "account_number": "0712345678",
    "account_type": "mobile",
    "transaction_type": "income",
    "description": "Pro Plan Renewal"
    }
```

Response:

```
    {
    "success": true,
    "status": "pending",
    "reference": "TXN-250401120000-XKQZ",
    "checkout_request_id": "ws_CO_...",
    "message": "Enter your M-Pesa PIN to complete the payment."
    }
```

---

Admin Interface
---------------

[](#admin-interface)

Visit `/{BILLING_ADMIN_PREFIX}` (default: `/billing-admin`).

The middleware `billing.admin` gates access. By default it checks `$user->is_admin`. Override by publishing the middleware:

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

Or define a gate in your `AuthServiceProvider`:

```
    Gate::define('billing-admin', fn($user) => $user->role === 'superadmin');
```

---

Scheduler
---------

[](#scheduler)

Add to `routes/console.php` (Laravel 11+):

```
    use Illuminate\Support\Facades\Schedule;

    Schedule::command('billing:renewals')->dailyAt('00:05');
    Schedule::command('billing:dunning')->dailyAt('02:00');
    Schedule::command('billing:reminders')->dailyAt('08:00');
```

CommandPurpose`billing:renewals`Charge subscriptions due today`billing:dunning`Retry failed payments per dunning config`billing:reminders`Send renewal reminder notifications`billing:install`One-time setup wizard---

Adding a Custom Driver
----------------------

[](#adding-a-custom-driver)

```
    // In a ServiceProvider
    app('billing')->extend('mpesa', function ($app) {
        return new MpesaDriver(config('billing.drivers.mpesa'));
    });
```

Your driver must implement `SmartGuyCodes\Billing\Contracts\PaymentDriver`:

```
    interface PaymentDriver
    {
        public function initiate(array $payload): PaymentResult;
        public function verify(string $reference): PaymentResult;
        public function handleCallback(array $payload): PaymentResult;
        public function refund(string $reference, float $amount): PaymentResult;
        public function driverName(): string;
        public function validateConfig(): void;
    }
```

---

Events
------

[](#events)

Listen to these events in your application:

```
    use SmartGuyCodes\Billing\Events\PaymentCompleted;

    Event::listen(PaymentCompleted::class, function ($event) {
        // $event->transaction — BillingTransaction
        // $event->result      — PaymentResult
        Log::info("Payment received: {$event->transaction->reference_no}");
    });
```

EventFired When`PaymentInitiated`STK Push sent`PaymentCompleted`Callback confirms success`PaymentFailed`Callback confirms failure`SubscriptionCreated`User subscribes to a plan`SubscriptionRenewed`Subscription period renewed`SubscriptionCancelled`Subscription cancelled`SubscriptionSuspended`Dunning suspension`DunningAttempted`Retry payment attempted---

Plans Config (Optional)
-----------------------

[](#plans-config-optional)

If you prefer config-driven plans over database plans, set `BILLING_PLANS_SOURCE=config` and define in `config/billing.php`:

```
'plans' => [
    [
        'name'       => 'Starter',
        'slug'       => 'starter',
        'price'      => 999,
        'interval'   => 'monthly',
        'trial_days' => 14,
        'features'   => ['Up to 5 users', '10GB storage'],
    ],
],
```

---

Usage
-----

[](#usage-1)

- Integrate billing features into your Laravel app using provided facades, models, and API endpoints.
- Access the billing dashboard at `/billing` (if enabled).

Contributing
------------

[](#contributing)

Contributions are welcome! Please open issues or submit pull requests to help improve the package.

License
-------

[](#license)

This package is open-sourced software licensed under the MIT License.

###  Health Score

21

—

LowBetter than 19% of packages

Maintenance65

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/7263e219e9a709c1e52c9d0b3fdc4c15d78e479c17316aaa6a287dd7aeebadd3?d=identicon)[SmartGuyCodes](/maintainers/SmartGuyCodes)

---

Top Contributors

[![SmartGuyCodes](https://avatars.githubusercontent.com/u/74773182?v=4)](https://github.com/SmartGuyCodes "SmartGuyCodes (25 commits)")

### Embed Badge

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

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

PHPackages © 2026

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