Skip to main content

Overview

The Billing facade provides a clean API for configuring billing behavior in your application. It manages billable entity resolution, route registration, and request-scoped memoization.
use Eufaturo\Billing\Billing;

Methods

resolveBillableUsing()

Configure the callback used to resolve the billable entity.
public static function resolveBillableUsing(callable $callback): void
Parameters:
  • $callback (callable): Function that returns a BillableInterface instance
Example:
Billing::resolveBillableUsing(function () {
    return auth()->user();
});

// With arrow function
Billing::resolveBillableUsing(fn() => auth()->user()->activeCompany);
// User-based billing
Billing::resolveBillableUsing(fn() => auth()->user());

// Company/Team-based billing
Billing::resolveBillableUsing(fn() => auth()->user()->activeCompany);

// Fallback pattern
Billing::resolveBillableUsing(function () {
    return auth()->user()?->activeCompany ?? auth()->user();
});

// Subdomain-based
Billing::resolveBillableUsing(function () {
    return Company::where('subdomain', request()->getHost())->firstOrFail();
});

getBillable()

Get the current billable entity.
public static function getBillable(): BillableInterface
Returns: BillableInterface Throws:
  • RuntimeException - If resolver not configured
  • RuntimeException - If resolver returns non-billable instance
Features:
  • ✅ Request-scoped memoization (resolver called only once per request)
  • ✅ Automatic caching using Laravel’s once() helper
  • ✅ Thread-safe and performant
Example:
$billable = Billing::getBillable();

// Access billable properties
$id = $billable->getBillableId();
$name = $billable->getBillableName();
$email = $billable->getBillableEmail();

// Check subscription
if ($billable->isSubscribed()) {
    // User has an active subscription
}
The getBillable() method is memoized - the resolver callback is only called once per request, and subsequent calls return the cached instance.

ignoreRoutes()

Disable automatic route registration.
public static function ignoreRoutes(): static
Returns: static - Returns instance for method chaining Example:
// In AppServiceProvider
public function boot(): void
{
    Billing::ignoreRoutes();
}
By default, routes are registered by UI packages (like billing-checkout-livewire). Use this method if you want complete control over routing.

resetBillableResolver()

Reset the billable resolver (primarily for testing).
public static function resetBillableResolver(): void
Example:
// In test setup
protected function setUp(): void
{
    parent::setUp();

    Billing::resetBillableResolver();
    Billing::resolveBillableUsing(fn() => $this->testUser);
}

Properties

$registersRoutes

Indicates whether routes will be registered.
public static bool $registersRoutes = true;
Example:
// Check if routes are registered
if (Billing::$registersRoutes) {
    // Routes will be registered
}

// Disable routes
Billing::ignoreRoutes();
expect(Billing::$registersRoutes)->toBeFalse();

Memoization

The getBillable() method uses Laravel’s once() helper for request-scoped memoization:
// First call - executes resolver
$billable1 = Billing::getBillable(); // Calls resolver

// Subsequent calls - returns cached instance
$billable2 = Billing::getBillable(); // Returns cache
$billable3 = Billing::getBillable(); // Returns cache

// All instances are identical
$billable1 === $billable2 === $billable3; // true
Benefits:
  • ✅ Resolver called only once per request
  • ✅ Eliminates redundant database queries
  • ✅ Improves performance in checkout flows
  • ✅ Cache cleared automatically between requests
Perfect for:
  • Checkout flows accessing billable multiple times
  • Multiple components/actions needing billable entity
  • Controllers, middleware, and Livewire components

Complete Example

app/Providers/AppServiceProvider.php
use Eufaturo\Billing\Billing;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Configure billable resolution
        Billing::resolveBillableUsing(function () {
            return request()->user()?->activeCompany ?? request()->user();
        });

        // Optional: Disable default routes
        if (config('billing.custom_routes')) {
            Billing::ignoreRoutes();
        }
    }
}
app/Http/Controllers/SubscriptionController.php
use Eufaturo\Billing\Billing;

class SubscriptionController extends Controller
{
    public function index()
    {
        $billable = Billing::getBillable();

        return view('subscriptions.index', [
            'subscriptions' => $billable->subscriptions,
            'activeSubscription' => $billable->subscription(),
        ]);
    }

    public function show($id)
    {
        $billable = Billing::getBillable();
        $subscription = $billable->subscriptions()->findOrFail($id);

        return view('subscriptions.show', compact('subscription'));
    }
}

Error Handling

use Eufaturo\Billing\Billing;
use RuntimeException;

try {
    $billable = Billing::getBillable();
} catch (RuntimeException $e) {
    if (str_contains($e->getMessage(), 'not configured')) {
        // Resolver not configured
        abort(500, 'Billing system not properly configured');
    }

    if (str_contains($e->getMessage(), 'BillableInterface')) {
        // Resolver returned invalid type
        abort(500, 'Invalid billable entity');
    }
}

Testing

use Eufaturo\Billing\Billing;
use Tests\TestCase;

class BillingTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // Reset and configure for testing
        Billing::resetBillableResolver();
        Billing::resolveBillableUsing(fn() => $this->createTestUser());
    }

    public function test_can_retrieve_billable()
    {
        $billable = Billing::getBillable();

        expect($billable)->toBeInstanceOf(BillableInterface::class);
    }

    public function test_billable_is_memoized()
    {
        $billable1 = Billing::getBillable();
        $billable2 = Billing::getBillable();

        // Should return same instance
        expect($billable1)->toBe($billable2);
    }

    private function createTestUser()
    {
        return User::factory()->create();
    }
}

See Also