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;
}
}
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
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