Skip to main content

Overview

The Billable Interface allows any Eloquent model to accept payments, manage subscriptions, and interact with the billing system. Whether billing users, teams, organizations, or any custom entity - simply implement the interface and use the trait.
Any model can be billable: User, Team, Company, Organization, or even custom models like Client or Agency.

Quick Setup

Step 1: Implement the Interface

use Eufaturo\Billing\Billable;
use Eufaturo\Billing\BillableInterface;
use Illuminate\Database\Eloquent\Model;

class Company extends Model implements BillableInterface
{
    use Billable;

    // Implement required methods
    public function getBillableId(): string
    {
        return (string) $this->id;
    }

    public function getBillableName(): string
    {
        return $this->name;
    }

    public function getBillableEmail(): string
    {
        return $this->email;
    }
}

Step 2: Configure the Resolver

Tell Billing which entity to use:
// In AppServiceProvider.php
use Eufaturo\Billing\Billing;

public function boot(): void
{
    Billing::resolveBillableUsing(fn() => auth()->user()->activeCompany);
}
That’s it! Your model is now billable.

The Billable Trait

The Billable trait provides all billing functionality:
use Eufaturo\Billing\Billable;

class Company extends Model implements BillableInterface
{
    use Billable;
}

What It Provides

1. Relationships

Access all billing data through relationships:
$company = Company::find(1);

// Get all subscriptions
$company->subscriptions; // Collection<Subscription>

// Get all orders (one-time payments)
$company->orders; // Collection<Order>

// Get all transactions
$company->transactions; // Collection<Transaction>

// Get gateway records (Stripe IDs, etc.)
$company->gatewayRecords; // Collection<GatewayRecord>

2. Subscription Methods

Check subscription status:
// Check if subscribed to any plan
if ($company->isSubscribed()) {
    // Customer has an active subscription
}

// Check if subscribed to specific plan
$proPlan = Billing::plans()->find('pro-monthly');
if ($company->isSubscribed($proPlan)) {
    // Customer is on Pro plan
}

// Get active subscription
$subscription = $company->subscription();

// Get subscription for specific plan
$proSubscription = $company->subscription($proPlan);

3. Trial Methods

Check trial status:
// Check if on trial for any plan
if ($company->isTrialing()) {
    // Customer is in trial period
}

// Check if on trial for specific plan
if ($company->isTrialing($proPlan)) {
    // Customer is on Pro trial
}

Required Methods

Implement these three methods to fulfill the interface:

getBillableId()

Unique identifier sent to payment gateways:
public function getBillableId(): string
{
    return (string) $this->id;
}
Always cast to string! Payment gateways expect string IDs.
Examples:
// Simple ID
return (string) $this->id;

// UUID
return (string) $this->uuid;

// Prefixed
return 'company_' . $this->id;

// Custom logic
return $this->external_id ?? (string) $this->id;

getBillableName()

Customer name for invoices and gateways:
public function getBillableName(): string
{
    return $this->name;
}
Examples:
// Company name
return $this->name;

// Full name for users
return $this->first_name . ' ' . $this->last_name;

// With fallback
return $this->company_name ?? $this->name;

// Organization name
return $this->organization_name;

getBillableEmail()

Email for invoices, receipts, and notifications:
public function getBillableEmail(): string
{
    return $this->email;
}
Examples:
// Direct email
return $this->email;

// Billing-specific email
return $this->billing_email ?? $this->email;

// Owner's email
return $this->owner->email;

// Admin email for teams
return $this->admin_email;

Common Billable Models

User-Based Billing

Individual users subscribe:
use Eufaturo\Billing\Billable;
use Eufaturo\Billing\BillableInterface;

class User extends Authenticatable implements BillableInterface
{
    use Billable;

    public function getBillableId(): string
    {
        return (string) $this->id;
    }

    public function getBillableName(): string
    {
        return $this->name;
    }

    public function getBillableEmail(): string
    {
        return $this->email;
    }
}
// Configure resolver
Billing::resolveBillableUsing(fn() => auth()->user());

Team-Based Billing

Teams/organizations subscribe:
class Team extends Model implements BillableInterface
{
    use Billable;

    public function getBillableId(): string
    {
        return (string) $this->id;
    }

    public function getBillableName(): string
    {
        return $this->name;
    }

    public function getBillableEmail(): string
    {
        // Use owner's email
        return $this->owner->email;
    }

    // Additional relationship
    public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}
// Configure resolver
Billing::resolveBillableUsing(fn() => auth()->user()->currentTeam);

Multi-Tenant (Company) Billing

class Company extends Model implements BillableInterface
{
    use Billable;

    public function getBillableId(): string
    {
        return (string) $this->id;
    }

    public function getBillableName(): string
    {
        return $this->company_name;
    }

    public function getBillableEmail(): string
    {
        return $this->billing_email ?? $this->admin_email;
    }
}
// Configure resolver with subdomain
Billing::resolveBillableUsing(function () {
    $subdomain = request()->getHost();
    return Company::where('subdomain', $subdomain)->firstOrFail();
});

Using the Billable Model

Creating Subscriptions

use Eufaturo\Billing\Billing;

$company = Company::find(1);
$plan = Billing::plans()->find('pro-monthly');

$subscription = Billing::subscriptions()->create(
    billable: $company,
    plan: $plan,
    type: SubscriptionType::PaymentGatewayManaged,
);

Checking Subscription Status

$company = Company::find(1);

// Is subscribed?
if ($company->isSubscribed()) {
    echo "Active subscriber";
}

// Get subscription
$subscription = $company->subscription();
if ($subscription) {
    echo "Plan: {$subscription->plan->name}";
    echo "Status: {$subscription->status->value}";
}

// Check specific plan
$proPlan = Billing::plans()->find('pro-monthly');
if ($company->isSubscribed($proPlan)) {
    echo "On Pro plan";
}

Accessing Subscriptions

$company = Company::find(1);

// All subscriptions (including cancelled)
$allSubscriptions = $company->subscriptions;

// Only active subscriptions
$active = $company->subscriptions()
    ->where('status', SubscriptionStatus::Active)
    ->get();

// Latest subscription
$latest = $company->subscriptions()
    ->latest()
    ->first();

Trial Checks

$company = Company::find(1);

// Is trialing?
if ($company->isTrialing()) {
    $subscription = $company->subscription();
    echo "Trial ends: {$subscription->trial_ends_at->format('M d, Y')}";
}

// Check trial for specific plan
if ($company->isTrialing($proPlan)) {
    echo "On Pro trial";
}

Accessing Orders

$company = Company::find(1);

// All orders
$orders = $company->orders;

// Recent orders
$recent = $company->orders()
    ->latest()
    ->take(10)
    ->get();

// Pending orders
$pending = $company->orders()
    ->where('status', 'pending')
    ->get();

Accessing Transactions

$company = Company::find(1);

// All transactions
$transactions = $company->transactions;

// Successful payments
$payments = $company->transactions()
    ->where('status', 'succeeded')
    ->get();

// Calculate total revenue
$total = $company->transactions()
    ->where('status', 'succeeded')
    ->sum('total');

Advanced Patterns

Dynamic Billable Resolution

Choose billable entity based on context:
// AppServiceProvider
Billing::resolveBillableUsing(function () {
    $user = auth()->user();

    // If user has active team context, bill the team
    if ($user->currentTeam) {
        return $user->currentTeam;
    }

    // Otherwise, bill the user directly
    return $user;
});

Fallback Pattern

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

Request-Based Resolution

Billing::resolveBillableUsing(function () {
    // Get from route parameter
    $teamId = request()->route('team');
    return Team::findOrFail($teamId);
});

Subdomain-Based Resolution

Billing::resolveBillableUsing(function () {
    $subdomain = explode('.', request()->getHost())[0];
    return Company::where('subdomain', $subdomain)->firstOrFail();
});

Gateway Records

The billable entity can have multiple gateway records:
$company = Company::find(1);

// All gateway records
$records = $company->gatewayRecords;

// Get Stripe customer ID
$stripeGateway = Billing::gateways()->get('stripe');
$stripeRecord = $company->gatewayRecords()
    ->where('payment_gateway_id', $stripeGateway->id)
    ->first();

if ($stripeRecord) {
    echo "Stripe ID: {$stripeRecord->external_id}";
}

Multiple Billable Models

You can have multiple billable models in the same app:
// User.php
class User extends Model implements BillableInterface
{
    use Billable;
    // ... implement methods
}

// Team.php
class Team extends Model implements BillableInterface
{
    use Billable;
    // ... implement methods
}

// Company.php
class Company extends Model implements BillableInterface
{
    use Billable;
    // ... implement methods
}
Then choose which one to use:
// Bill users directly
Billing::resolveBillableUsing(fn() => auth()->user());

// Or bill teams
Billing::resolveBillableUsing(fn() => auth()->user()->currentTeam);

// Or bill companies
Billing::resolveBillableUsing(fn() => auth()->user()->company);

Feature Gating with Billable

Use billable status for feature access:
// In your controller
public function store()
{
    $company = Billing::getBillable();

    if (!$company->isSubscribed()) {
        return redirect()->route('billing.plans')
            ->with('error', 'Please subscribe to create projects.');
    }

    // Check feature access
    $subscription = $company->subscription();
    if (!$subscription->hasFeature('advanced-analytics')) {
        abort(403, 'Upgrade to access advanced analytics.');
    }

    // Proceed with creating project...
}

Blade Directives (Custom)

You can create custom blade directives:
// AppServiceProvider
Blade::if('subscribed', function () {
    return Billing::getBillable()->isSubscribed();
});

Blade::if('trialing', function () {
    return Billing::getBillable()->isTrialing();
});
@subscribed
    <p>Thanks for being a subscriber!</p>
@else
    <a href="/pricing">Subscribe now</a>
@endsubscribed

@trialing
    <p>You're on a free trial. Upgrade to continue after trial ends.</p>
@endtrialing

Testing

Test Setup

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

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

        // Create test company
        $this->company = Company::factory()->create();

        // Set as billable
        Billing::resolveBillableUsing(fn() => $this->company);
    }
}

Testing Subscriptions

test('company can subscribe to a plan', function () {
    $plan = ProductPlanFactory::new()->create();

    $subscription = Billing::subscriptions()->create(
        billable: $this->company,
        plan: $plan,
        type: SubscriptionType::LocallyManaged,
    );

    expect($this->company->isSubscribed())->toBeTrue();
    expect($this->company->subscription())->toBe($subscription);
});

test('can check if subscribed to specific plan', function () {
    $proPlan = ProductPlanFactory::new()->create(['slug' => 'pro']);
    $basicPlan = ProductPlanFactory::new()->create(['slug' => 'basic']);

    Billing::subscriptions()->create(
        billable: $this->company,
        plan: $proPlan,
        type: SubscriptionType::LocallyManaged,
    );

    expect($this->company->isSubscribed($proPlan))->toBeTrue();
    expect($this->company->isSubscribed($basicPlan))->toBeFalse();
});

Common Patterns

Check Before Action

public function createProject()
{
    $company = Billing::getBillable();

    // Ensure subscribed
    if (!$company->isSubscribed()) {
        return redirect()->route('pricing');
    }

    // Check plan limits
    $subscription = $company->subscription();
    $projectCount = $company->projects()->count();
    $limit = $subscription->getFeatureLimit('projects');

    if ($projectCount >= $limit) {
        return back()->with('error', 'Project limit reached. Upgrade your plan.');
    }

    // Proceed...
}

Subscription Middleware

// App/Http/Middleware/EnsureSubscribed.php
public function handle($request, Closure $next)
{
    $billable = Billing::getBillable();

    if (!$billable->isSubscribed()) {
        return redirect()->route('pricing')
            ->with('error', 'Subscription required to access this feature.');
    }

    return $next($request);
}

Troubleshooting

”Billable resolver not configured”

Error:
RuntimeException: Billable resolver not configured.
Solution:
// Add to AppServiceProvider
Billing::resolveBillableUsing(fn() => auth()->user());

Wrong Billable Entity

Problem: Billing the wrong entity (user instead of team). Solution:
// Check your resolver
Billing::resolveBillableUsing(function () {
    // Make sure this returns the correct model
    return auth()->user()->currentTeam; // Not auth()->user()
});

Null Billable

Problem: Resolver returns null. Solution:
Billing::resolveBillableUsing(function () {
    $user = auth()->user();

    if (!$user) {
        throw new \Exception('User must be authenticated');
    }

    return $user->currentTeam ?? $user;
});

Next Steps


See Also