PHPackages                             valentin-morice/laravel-billing-repository - 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. valentin-morice/laravel-billing-repository

ActiveLibrary

valentin-morice/laravel-billing-repository
==========================================

Config-as-code for billing providers (Stripe, Paddle, etc.) in Laravel. Define products, prices, tax rates, webhooks, and billing settings in versioned files, then plan/apply with artisan commands.

60[1 PRs](https://github.com/valentin-morice/laravel-billing-repository/pulls)PHPCI passing

Since Jan 19Pushed 2mo agoCompare

[ Source](https://github.com/valentin-morice/laravel-billing-repository)[ Packagist](https://packagist.org/packages/valentin-morice/laravel-billing-repository)[ RSS](/packages/valentin-morice-laravel-billing-repository/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)DependenciesVersions (3)Used By (0)

Laravel Billing Repository
==========================

[](#laravel-billing-repository)

[![Latest Version on Packagist](https://camo.githubusercontent.com/db948e13a9e39b0d8cbc1ba586d209eece888f3aacaa19e533afafdf5272a70c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f76616c656e74696e2d6d6f726963652f6c61726176656c2d62696c6c696e672d7265706f7369746f72792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/valentin-morice/laravel-billing-repository)[![GitHub Tests Action Status](https://camo.githubusercontent.com/6d01594f3fee75d9c708d912c9c9b76b52abdc2f63bde944293f3c0a74f3d54e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f76616c656e74696e2d6d6f726963652f6c61726176656c2d62696c6c696e672d7265706f7369746f72792f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/valentin-morice/laravel-billing-repository/actions?query=workflow%3Aci+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/6c1ee5ac24f8573821b94afcd25a9936dd6b132a7721daaf3edc750f4b3663c0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f76616c656e74696e2d6d6f726963652f6c61726176656c2d62696c6c696e672d7265706f7369746f72792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/valentin-morice/laravel-billing-repository)

**Version-controlled billing configuration with type-safe access to provider IDs.**

Define products and prices in PHP config files, deploy them to your billing provider (Stripe), and access provider IDs safely throughout your codebase—no more hardcoded strings or magic values.

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

[](#why-this-package)

**The Problem:** Managing billing products/prices requires:

- Hardcoding provider IDs (`price_1ABC...`) scattered across your codebase
- Manual syncing between provider dashboard and application
- No version control or audit trail for billing changes
- Slow API calls every time you need a price ID

**The Solution:** This package provides:

- **Config-as-code**: Define products/prices in versioned PHP files
- **DB caching**: Fast local lookups without API calls
- **Type-safe facade**: Access provider IDs using constant keys
- **Auto-generated constants**: IDE autocomplete and refactor-safe code
- **Deploy workflow**: Plan and apply billing changes like infrastructure-as-code

Quick Example
-------------

[](#quick-example)

**1. Define your products in `config/billing.php`:**

```
'products' => [
    'premium' => [
        'name' => 'Premium Plan',
        'prices' => [
            'monthly' => [
                'amount' => 999,
                'currency' => 'eur',
                'recurring' => ['interval' => 'month'],
            ],
            'yearly' => [
                'amount' => 9900,
                'currency' => 'eur',
                'recurring' => ['interval' => 'year'],
            ],
        ],
    ],
],
```

**2. Deploy to Stripe and cache locally:**

```
php artisan billing:deploy
```

This automatically:

- Creates/updates products and prices in Stripe
- Caches them in your local database
- **Generates type-safe enums for your products and prices**

**3. Use type-safe accessors in your code:**

```
use App\Enums\Billing\{ProductKey, PriceKey};
use ValentinMorice\LaravelBillingRepository\Facades\BillingRepository;

// Option 1: Direct facade access with string keys
$priceId = BillingRepository::priceId('premium', 'monthly');

// Option 2: Type-safe enums (auto-generated after deploy)
$priceId = BillingRepository::priceId(
    ProductKey::Premium,    // 'premium'
    PriceKey::Monthly       // 'monthly'
);

// Use in your application
$user->checkout($priceId, ['quantity' => 1]);
```

**Before vs After:**

```
// ❌ Before: Hardcoded magic strings
$checkout = $user->checkout('price_1ABCxyz123', ['quantity' => 1]);

// ✅ After: Type-safe, refactor-friendly
$checkout = $user->checkout(
    BillingRepository::priceId(ProductKey::Premium, PriceKey::Monthly),
    ['quantity' => 1]
);
```

How It Works
------------

[](#how-it-works)

### 1. Database Cache Layer

[](#1-database-cache-layer)

The package stores products and prices locally in `billing_products` and `billing_prices` tables:

- **Fast lookups**: No API calls needed to get provider IDs
- **Reference data**: Use as relationships in your models (subscriptions, invoices, etc.)
- **Audit trail**: Track what's deployed and when

### 2. Config-as-Code Workflow

[](#2-config-as-code-workflow)

Define your billing structure in `config/billing.php` using simple arrays:

```
return [
    'provider' => env('BILLING_PROVIDER', 'stripe'),
    'api_key' => env('BILLING_API_KEY'),

    'products' => [
        'basic' => [
            'name' => 'Basic Plan',
            'description' => 'Perfect for individuals',
            'prices' => [
                'monthly' => [
                    'amount' => 499,
                    'currency' => 'eur',
                    'recurring' => ['interval' => 'month'],
                ],
            ],
        ],
        'premium' => [
            'name' => 'Premium Plan',
            'description' => 'For power users',
            'prices' => [
                'monthly' => [
                    'amount' => 999,
                    'currency' => 'eur',
                    'recurring' => ['interval' => 'month'],
                ],
                'yearly' => [
                    'amount' => 9900,
                    'currency' => 'eur',
                    'recurring' => ['interval' => 'year'],
                ],
            ],
        ],
    ],
];
```

### 3. Two-Way Sync Commands

[](#3-two-way-sync-commands)

#### `billing:import` - Pull from provider

[](#billingimport---pull-from-provider)

Import existing products/prices from your billing provider into the database:

```
# Import to database only (default)
php artisan billing:import --db-only

# Import and generate config file from provider
php artisan billing:import --generate-config
```

Use `--generate-config` when starting with existing Stripe products, or to sync your config with provider changes.

#### `billing:deploy` - Push to provider

[](#billingdeploy---push-to-provider)

Deploy config changes to your billing provider and sync to database:

```
# Preview changes (dry-run)
php artisan billing:deploy --dry-run

# Deploy changes
php artisan billing:deploy

# CI/Non-interactive: Auto-archive for immutable field changes
php artisan billing:deploy --archive-all

# CI/Non-interactive: Auto-duplicate for immutable field changes
php artisan billing:deploy --duplicate-all
```

The command shows a plan of changes (create/update/archive) before applying them.

**Handling Immutable Fields:**

Stripe prices have immutable fields (amount, currency, recurring interval) that cannot be updated once created. When you modify these fields in your config, the deploy command detects this and prompts you to choose a strategy:

- **Archive**: Archives the old price in Stripe and creates a new price with the same key. Existing subscriptions using the old price will continue to work, but new subscriptions will use the new price.
- **Duplicate**: Keeps the old price active and creates a new price with an incremented key (e.g., `monthly` → `monthly_1`). Useful when you need both prices to remain available.

In CI environments, use `--archive-all` or `--duplicate-all` to skip interactive prompts.

### 4. Auto-Generated Type-Safe Enums

[](#4-auto-generated-type-safe-enums)

After running `billing:deploy` or `billing:import`, the package automatically generates backed enum files in your application:

**app/Enums/Billing/ProductKey.php** (auto-generated):

```
namespace App\Enums\Billing;

enum ProductKey: string
{
    case Basic = 'basic';
    case Premium = 'premium';
}
```

**app/Enums/Billing/PriceKey.php** (auto-generated):

```
namespace App\Enums\Billing;

enum PriceKey: string
{
    case Monthly = 'monthly';
    case Yearly = 'yearly';
}
```

**Configurable Location:**

The enum path and namespace are configurable in `config/billing.php`:

```
'enums' => [
    'path' => app_path('Enums/Billing'),
    'namespace' => 'App\\Enums\\Billing',
],
```

**Benefits:**

- **IDE Autocomplete**: Your editor suggests available products/prices
- **Refactor-Safe**: Rename keys without breaking your codebase
- **Type Safety**: Catch typos at development time, not runtime
- **Self-Documenting**: See all available products/prices in one place

**Enum Case Naming:**

- Product keys: Converted to PascalCase (e.g., `'premium'` → `Premium`)
- Price types: Converted to PascalCase (e.g., `'monthly'` → `Monthly`)
- Handles special cases: Separators trigger new words (e.g., `'pro-plan'` → `ProPlan`)
- Avoids collisions: Reserved PHP keywords get `Case` suffix

### 5. Type-Safe Facade

[](#5-type-safe-facade)

Access provider IDs throughout your codebase:

```
use App\Enums\Billing\{ProductKey, PriceKey};
use ValentinMorice\LaravelBillingRepository\Facades\BillingRepository;

// Get a specific price ID (with enums)
$priceId = BillingRepository::priceId(ProductKey::Premium, PriceKey::Monthly);
// Returns: "price_1ABC..." (Stripe price ID)

// Or use string keys directly
$priceId = BillingRepository::priceId('premium', 'monthly');

// Get a product ID
$productId = BillingRepository::productId(ProductKey::Premium);
// Returns: "prod_XYZ..." (Stripe product ID)

// Use in your application
$user->checkout($priceId, ['quantity' => 1]);
$user->subscription('default')->swap($priceId);
```

**Error Handling:**

```
use ValentinMorice\LaravelBillingRepository\Exceptions\Models\{
    ProductNotFoundException,
    PriceNotFoundException
};

try {
    $priceId = BillingRepository::priceId('nonexistent', 'monthly');
} catch (ProductNotFoundException $e) {
    // Product not found or inactive
} catch (PriceNotFoundException $e) {
    // Price not found or inactive for this product
}
```

**Additional Facade Methods:**

```
// Access config-level operations
$productDef = BillingRepository::config()->product('premium');
$priceDef = BillingRepository::config()->price('premium', 'monthly');

// Access database resources
$product = BillingRepository::resource()->product('premium'); // BillingProduct model
$prices = BillingRepository::resource()->prices('premium');   // Collection of BillingPrice
```

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

[](#installation)

```
composer require valentin-morice/laravel-billing-repository
```

Publish config and run migrations:

```
php artisan vendor:publish --provider="ValentinMorice\LaravelBillingRepository\LaravelBillingRepositoryServiceProvider"
php artisan migrate
```

Set your billing provider credentials in `.env`:

```
BILLING_PROVIDER=stripe
BILLING_API_KEY=sk_test_...
```

Typical Workflows
-----------------

[](#typical-workflows)

### Starting Fresh

[](#starting-fresh)

1. Define products in `config/billing.php`
2. Run `php artisan billing:deploy` to create them in Stripe
3. Enums are auto-generated in `app/Enums/Billing/`
4. Use `BillingRepository::priceId(ProductKey::Premium, PriceKey::Monthly)` in your code

### Migrating Existing Stripe Setup

[](#migrating-existing-stripe-setup)

1. Run `php artisan billing:import --generate-config` to pull existing products
2. Review and version-control the generated config
3. Enums are auto-generated from imported data
4. Use enums to replace hardcoded IDs throughout your codebase

### Making Changes

[](#making-changes)

1. Update `config/billing.php`
2. Run `php artisan billing:deploy --dry-run` to preview
3. Run `php artisan billing:deploy` to apply
4. Enums are automatically regenerated

### CI/CD Deployment

[](#cicd-deployment)

For automated deployments, use flags to skip interactive prompts:

```
# Auto-archive old prices when immutable fields change
php artisan billing:deploy --archive-all

# Auto-duplicate prices when immutable fields change
php artisan billing:deploy --duplicate-all
```

Real-World Example
------------------

[](#real-world-example)

```
// config/billing.php
'products' => [
    'starter' => [
        'name' => 'Starter Plan',
        'prices' => [
            'monthly' => [
                'amount' => 999,
                'currency' => 'eur',
                'recurring' => ['interval' => 'month'],
            ],
        ],
    ],
    'pro' => [
        'name' => 'Pro Plan',
        'prices' => [
            'monthly' => [
                'amount' => 2999,
                'currency' => 'eur',
                'recurring' => ['interval' => 'month'],
            ],
            'yearly' => [
                'amount' => 29900,
                'currency' => 'eur',
                'recurring' => ['interval' => 'year'],
            ],
        ],
    ],
],
```

After `php artisan billing:deploy`, use everywhere:

```
// Checkout controller
public function checkout(Request $request)
{
    $plan = $request->input('plan');     // 'starter' or 'pro'
    $interval = $request->input('interval'); // 'monthly' or 'yearly'

    $priceId = BillingRepository::priceId($plan, $interval);

    return $request->user()->checkout($priceId, [
        'success_url' => route('checkout.success'),
        'cancel_url' => route('checkout.cancel'),
    ]);
}

// Subscription management
public function upgrade(User $user)
{
    $user->subscription('default')->swap(
        BillingRepository::priceId(ProductKey::Pro, PriceKey::Yearly)
    );
}

// Pricing page
public function pricing()
{
    return view('pricing', [
        'starterMonthly' => BillingRepository::priceId(ProductKey::Starter, PriceKey::Monthly),
        'proMonthly' => BillingRepository::priceId(ProductKey::Pro, PriceKey::Monthly),
        'proYearly' => BillingRepository::priceId(ProductKey::Pro, PriceKey::Yearly),
    ]);
}
```

Provider Support
----------------

[](#provider-support)

Currently supported:

- **Stripe** (ships with package)

The package uses a provider adapter pattern—adding new providers (Paddle, PayPal) requires implementing the `ProviderAdapter` interface.

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

[](#requirements)

- PHP ^8.3
- Laravel ^11.31 or ^12.0

Testing
-------

[](#testing)

```
composer test
composer test-coverage
composer format
composer analyse
```

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

21

—

LowBetter than 19% of packages

Maintenance57

Moderate activity, may be stable

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity15

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/5b2f03a5a84b7c0e6c54756b6cfcb56ccf6e5a6359203185782f604eeeb08170?d=identicon)[valentin-morice](/maintainers/valentin-morice)

---

Top Contributors

[![valentin-morice](https://avatars.githubusercontent.com/u/100000204?v=4)](https://github.com/valentin-morice "valentin-morice (56 commits)")

### Embed Badge

![Health badge](/badges/valentin-morice-laravel-billing-repository/health.svg)

```
[![Health](https://phpackages.com/badges/valentin-morice-laravel-billing-repository/health.svg)](https://phpackages.com/packages/valentin-morice-laravel-billing-repository)
```

PHPackages © 2026

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