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