PHPackages                             emeroid/laravel-billing-core - 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. emeroid/laravel-billing-core

ActiveLaravel-package[Payment Processing](/categories/payments)

emeroid/laravel-billing-core
============================

A robust, driver-based, multi-gateway billing package for Laravel. Supports Paystack, PayPal, plan swapping, and dunning.

v1.2.2(3mo ago)011MITPHPPHP ^8.1

Since Nov 11Pushed 3mo agoCompare

[ Source](https://github.com/emeroid/laravel-billing-core)[ Packagist](https://packagist.org/packages/emeroid/laravel-billing-core)[ Docs](https://github.com/emeroid/laravel-billing-core)[ GitHub Sponsors](https://github.com/emeroid)[ Patreon](https://www.patreon.com/emeroid)[ RSS](/packages/emeroid-laravel-billing-core/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (7)Versions (6)Used By (0)

Laravel Billing Core
====================

[](#laravel-billing-core)

**Laravel Billing Core** is a robust, driver-based, multi-gateway billing package for Laravel. It provides a simple, fluent API to manage **one-time payments**, **subscriptions**, **plan swapping**, and **dunning logic** for your SaaS application.

> Stop rebuilding billing logic for every project — this package is the plug-and-play foundation you need.

---

🚀 Features
----------

[](#-features)

- **Driver-Based:** Switch gateways just by changing your `.env`.
- **Multi-Gateway:** Out-of-the-box support for **Paystack** and **PayPal**.
- **Subscription Management:** A complete subscription lifecycle (`subscribe`, `cancel`, `swapPlan`).
- **Grace Periods:** Automatically handles “cancel on end of billing period”.
- **Plan Swapping:** Fluent API to upgrade or downgrade users.
- **Dunning:** Listens for `invoice.payment_failed` webhooks to set `past_due` status.
- **Event-Based:** Fires events like `SubscriptionStarted`, `SubscriptionCancelled`, etc.
- **Billable Trait:** A powerful interface you can add to your `User` model.

---

🧩 Installation
--------------

[](#-installation)

```
composer require emeroid/laravel-billing-core
```

### 1. Configuration

[](#1-configuration)

#### Publish the Config File

[](#publish-the-config-file)

```
php artisan vendor:publish --provider="Emeroid\Billing\BillingServiceProvider" --tag="billing-config"
```

> This creates `config/billing.php`.

#### Publish the Migrations

[](#publish-the-migrations)

```
php artisan vendor:publish --provider="Emeroid\Billing\BillingServiceProvider" --tag="billing-migrations"
```

> This adds the **plans**, **subscriptions**, and **transactions** tables.

#### Run the Migrations

[](#run-the-migrations)

```
php artisan migrate
```

#### Update Your `.env` File

[](#update-your-env-file)

```
# --- BILLING CORE ---
BILLING_DEFAULT_DRIVER=paystack
BILLING_MODEL=\App\Models\User

# --- PAYSTACK ---
PAYSTACK_PUBLIC_KEY=pk_...
PAYSTACK_SECRET_KEY=sk_...

# --- PAYPAL ---
PAYPAL_CLIENT_ID=...
PAYPAL_SECRET=...
PAYPAL_MODE=sandbox
PAYPAL_WEBHOOK_ID=WH-...
```

#### Add the `Billable` Trait

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

```
// app/Models/User.php

use Emeroid\Billing\Traits\Billable;
use Illuminate{...};

class User extends Authenticatable
{
    use Billable, HasFactory, Notifiable;
    // ...
}
```

---

💳 Usage
-------

[](#-usage)

### 2. Creating Plans

[](#2-creating-plans)

Before creating subscriptions, define your plans in the `plans` table — typically via a seeder:

```
// database/seeders/PlanSeeder.php

use Emeroid\Billing\Models\Plan;
use Illuminate\Database\Seeder;

class PlanSeeder extends Seeder
{
    public function run(): void
    {
        Plan::create([
            'name' => 'Pro Plan',
            'slug' => 'pro-plan',
            'amount' => 500000, // 5000 NGN (in kobo)
            'interval' => 'monthly',
            'paystack_plan_id' => 'PL_abc123',
            'paypal_plan_id' => 'P-xyz456',
        ]);

        Plan::create([
            'name' => 'Business Plan',
            'slug' => 'business-plan',
            'amount' => 1000000, // 10000 NGN
            'interval' => 'monthly',
            'paystack_plan_id' => 'PL_def789',
            'paypal_plan_id' => 'P-ghi123',
        ]);
    }
}
```

---

### 3. One-Time Payments

[](#3-one-time-payments)

```
use Emeroid\Billing\Facades\Billing;
use Illuminate\Http\Request;

class PaymentController
{
    public function startPayment(Request $request)
    {
        $user = $request->user();
        $amountInKobo = 50000; // 5000 NGN

        try {
            $payment = Billing::purchase($amountInKobo, $user->email, [
                'user_id' => $user->id,
                'currency' => 'NGN',
            ]);

            return redirect()->away($payment['authorization_url']);

        } catch (\Emeroid\Billing\Exceptions\PaymentInitializationFailedException $e) {
            return back()->with('error', $e->getMessage());
        }
    }
}
```

---

### 4. Subscriptions

[](#4-subscriptions)

```
use Emeroid\Billing\Facades\Billing;
use Emeroid\Billing\Models\Plan;
use Illuminate\Http\Request;

class SubscriptionController
{
    public function startSubscription(Request $request)
    {
        $user = $request->user();
        $plan = Plan::where('slug', 'pro-plan')->firstOrFail();

        $gatewayPlanId = config('billing.default') === 'paypal'
            ? $plan->paypal_plan_id
            : $plan->paystack_plan_id;

        try {
            $subscription = Billing::subscribe(
                $gatewayPlanId,
                $user->email,
                [
                    'amount' => $plan->amount,
                    'user_id' => $user->id,
                    'currency' => 'NGN',
                ]
            );

            return redirect()->away($subscription['authorization_url']);

        } catch (\Emeroid\Billing\Exceptions\PaymentInitializationFailedException $e) {
            return back()->with('error', $e->getMessage());
        }
    }
}
```

---

### 5. Handling Callbacks

[](#5-handling-callbacks)

After payment, users are redirected to your site. The built-in `CallbackController` handles:

- Verifying the transaction via `Billing::verifyTransaction(...)`
- Creating the Subscription record
- Firing events (`TransactionSuccessful`, `SubscriptionStarted`)
- Redirecting to success/failure URLs (defined in `config/billing.php`)

All of this happens **automatically**.

---

### 6. Handling Webhooks

[](#6-handling-webhooks)

Add these URLs to your gateway dashboards:

```
https://your-app.com/billing-webhooks/paystack
https://your-app.com/billing-webhooks/paypal

```

The package automatically handles:

- `charge.success` → verifies payments
- `subscription.create` → creates subscriptions
- `subscription.disable` → triggers `SubscriptionCancelled`
- Dunning events:

    - `invoice.payment_failed` (Paystack)
    - `BILLING.SUBSCRIPTION.PAYMENT.FAILED` (PayPal)

---

### 7. The `Billable` Trait API

[](#7-the-billable-trait-api)

```
$user = auth()->user();

// STATUS CHECKS
$user->isSubscribed();
$user->onGracePeriod();
$user->hasActiveSubscription();
$user->isSubscribedTo('pro-plan');
$user->isPastDue();

// MANAGEMENT
$subscription = $user->getSubscription('SUB_abc');

$user->cancelSubscription($subscription->gateway_subscription_id);
$user->swapPlan($subscription->gateway_subscription_id, 'business-plan');
$user->syncSubscription($subscription->gateway_subscription_id);
```

---

### 8. Events

[](#8-events)

Listen for billing events in your `EventServiceProvider`:

```
// app/Providers/EventServiceProvider.php

use Emeroid\Billing\Events\{
    TransactionSuccessful,
    SubscriptionStarted,
    SubscriptionCancelled,
    SubscriptionPlanSwapped,
    SubscriptionPaymentFailed
};

protected $listen = [
    TransactionSuccessful::class => [
        'App\Listeners\GrantAccessToProduct',
        'App\Listeners\SendInvoiceEmail',
    ],
    SubscriptionStarted::class => [
        'App\Listeners\ActivateProFeatures',
    ],
    SubscriptionCancelled::class => [
        'App\Listeners\RevokeProFeaturesAtPeriodEnd',
    ],
    SubscriptionPlanSwapped::class => [
        'App\Listeners\HandlePlanSwap',
    ],
    SubscriptionPaymentFailed::class => [
        'App\Listeners\SendDunningEmail',
    ],
];
```

---

**Emeroid Billing Package Customization**
=========================================

[](#emeroid-billing-package-customization)

This document explains how to configure the Emeroid Billing package to correctly identify the billable entity when the customer’s payment gateway email does not directly match the billable model’s email field.

In many SaaS applications, a subscription belongs to a **Team** or **Organization**, even though the **User** making the payment uses their personal email.

---

**1. Custom Billable Lookup Strategy**
--------------------------------------

[](#1-custom-billable-lookup-strategy)

By default, the `AbstractDriver` attempts to locate the Billable Model (e.g., `Team`) by matching the email directly on that model.

If your Billable Model does **not** store the customer’s email, you must implement a **custom lookup strategy**.

### **Implementation: The `BillableLookup` Class**

[](#implementation-the-billablelookup-class)

Create a lookup class that identifies the Billable Model (`Team`) through an associated `User`.

This method must have the exact signature:

```
public static function findByEmail(string $email): ?Model
```

#### **Example: `app/Lookups/BillableLookup.php`**

[](#example-applookupsbillablelookupphp)

```
