### Using PlanRepository to Retrieve Plans Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Shows how to resolve the PlanRepository from the service container and use its methods to get a single plan by name or all configured plans. ```php use Maartenpaauw\Filament\Cashier\ConfigPlanRepository; use Maartenpaauw\Filament\Cashier\Plan; // Resolve from the container (binding is registered automatically) $repository = app("Maartenpaauw\Filament\Cashier\PlanRepository"); // or directly: $repository = app(ConfigPlanRepository::class); // Retrieve a single plan by its config key $plan = $repository->get(name: 'default'); // Returns a Plan instance with all values from config/cashier.php // Retrieve all configured plans $plans = $repository->all(); // Returns: ['default' => Plan, 'basic' => Plan, ...] foreach ($plans as $name => $plan) { echo "{$name}: {$plan->productId} / {$plan->priceId}\n"; // default: prod_0JkO18GJx0Vtux / price_Fxp5y8x0qjrm2jjk2nzuMpSF // basic: prod_jad5TBR8eIwywy / price_ruVAAh5dwyhwbtWIVQn0lUvc } // Type defaults to the array key if not explicitly configured $basic = $repository->get('basic'); echo $basic->type; // "basic" (inherited from key) var_dump($basic->trialDays); // bool(false) (no trial configured) var_dump($basic->allowPromotionCodes); // bool(false) ``` -------------------------------- ### Install Filament Cashier Billing Provider Source: https://github.com/maartenpaauw/filament-cashier-billing-provider/blob/main/README.md Install the package using Composer. Ensure your `Billable` model matches your Filament tenant model. ```bash composer require maartenpaauw/filament-cashier-billing-provider ``` -------------------------------- ### Configure Cashier Plans Source: https://github.com/maartenpaauw/filament-cashier-billing-provider/blob/main/README.md Add your subscription plans to the `cashier.php` configuration file. This example shows a default plan with trial days and promotion code support. ```php 'plans' => [ 'default' => [ 'product_id' => ENV('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRODUCT_ID'), 'price_id' => ENV('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRICE_ID'), 'type' => 'default', // Optional, by default it uses the array key as type. 'trial_days' => 14, // Optional 'has_generic_trial' => true, // Optional, only `trial_days` OR `has_generic_trial` can be used. 'allow_promotion_codes' => true, // Optional 'collect_tax_ids' => true, // Optional 'metered_price' => true, // Optional ], ], ``` -------------------------------- ### Constructing and Validating a Plan Object Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Demonstrates successful construction of a Plan object with various parameters and shows examples of validation errors that throw InvalidArgumentException. ```php use Maartenpaauw\Filament\Cashier\Plan; use InvalidArgumentException; // Successful construction $plan = new Plan( type: 'default', productId: 'prod_0JkO18GJx0Vtux', priceId: 'price_Fxp5y8x0qjrm2jjk2nzuMpSF', trialDays: 14, hasGenericTrial: false, allowPromotionCodes: true, collectTaxIds: true, isMeteredPrice: false, ); echo $plan->type; // "default" echo $plan->productId; // "prod_0JkO18GJx0Vtux" echo $plan->priceId; // "price_Fxp5y8x0qjrm2jjk2nzuMpSF" echo $plan->trialDays; // 14 var_dump($plan->hasGenericTrial); // bool(false) var_dump($plan->allowPromotionCodes); // bool(true) // Validation errors thrown at construction: // Empty type, productId, or priceId → InvalidArgumentException // trial_days + has_generic_trial both set → InvalidArgumentException // trial_days < 0 → InvalidArgumentException try { new Plan(type: '', productId: 'prod_x', priceId: 'price_x', trialDays: false, hasGenericTrial: false, allowPromotionCodes: false, collectTaxIds: false, isMeteredPrice: false); } catch (InvalidArgumentException $e) { echo $e->getMessage(); // "Type cannot be empty." } try { new Plan(type: 'default', productId: 'prod_x', priceId: 'price_x', trialDays: 14, hasGenericTrial: true, allowPromotionCodes: false, collectTaxIds: false, isMeteredPrice: false); } catch (InvalidArgumentException $e) { echo $e->getMessage(); // 'Only "trial days" or "has generic trial" can be used.' } ``` -------------------------------- ### Resolve Current Filament Tenant as Billable Model Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Use TenantRepository to get the current Filament tenant as a Cashier Billable model. Ensure the tenant model matches Cashier::$customerModel and uses the Billable trait, otherwise a LogicException will be thrown. ```php use Maartenpaauw\Filament\Cashier\TenantRepository; use LogicException; // Resolve the current Filament tenant as a Billable model $tenant = TenantRepository::make()->current(); // Returns: Model & Billable (e.g. App\Models\Team) // Typical usage in a custom action or service $tenant = TenantRepository::make()->current(); if (! $tenant->hasStripeId()) { $tenant->createAsStripeCustomer(); } // Redirect to the Stripe Billing Portal return $tenant->redirectToBillingPortal(returnUrl: 'https://myapp.com/dashboard'); // Error cases: // LogicException: "Filament tenant does not match the Cashier customer model" // → Occurs when config('cashier.model') !== get_class(Filament::getTenant()) // LogicException: "Tenant model does not use Cashier Billable trait" // → Occurs when the tenant model lacks `use Billable;` ``` -------------------------------- ### Using Backed Enums for Plan Names Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt PHP string-backed enums can be used for plan names to provide type safety and prevent typos. This example shows how to define an enum and use it with the BillingProvider and for generating middleware. ```php // app/Enums/Plan.php enum Plan: string { case Basic = 'basic'; case Advanced = 'advanced'; case Premium = 'premium'; } // Use in BillingProvider — restrict panel to Advanced or Premium subscribers use Maartenpaauw\Filament\Cashier\Stripe\BillingProvider; ->tenantBillingProvider(new BillingProvider(plans: [Plan::Advanced, Plan::Premium])) ->requiresTenantSubscription() // Generates middleware: // RedirectIfUserNotSubscribed:advanced,premium // Use in middleware string generation use Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed; $middleware = RedirectIfUserNotSubscribed::plan(plans: Plan::Advanced); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:advanced" $middleware = RedirectIfUserNotSubscribed::plan(plans: Plan::cases()); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic,advanced,premium" ``` -------------------------------- ### Add Stripe Product ID to Cashier Config Source: https://github.com/maartenpaauw/filament-cashier-billing-provider/blob/main/UPGRADING.md Starting from v2, the `product_id` configuration is required in `cashier.php` to manage multiple plans and restrict tenant access based on subscriptions. This change updates the middleware to use `->subscribedToProduct(...)`. ```diff 'plans' => [ 'default' => [ + 'product_id' => ENV('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRODUCT_ID'), 'price_id' => ENV('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRICE_ID'), ], ], ``` -------------------------------- ### Upgrade Configuration for Multi-Plan Support Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Version 2 of the package requires the 'product_id' to be configured for each plan in config/cashier.php to enable multi-plan support with product-level access control. ```diff // config/cashier.php 'plans' => [ 'default' => [ + 'product_id' => env('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRODUCT_ID'), 'price_id' => env('CASHIER_STRIPE_SUBSCRIPTION_DEFAULT_PRICE_ID'), ], ], ``` -------------------------------- ### Run Tests Source: https://github.com/maartenpaauw/filament-cashier-billing-provider/blob/main/README.md Execute the package tests using Composer. ```bash composer test ``` -------------------------------- ### Configure Subscription Plans in `cashier.php` Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Define subscription plans in `config/cashier.php` under the `plans` key. Each plan maps a name to Stripe product/price IDs and optional billing configurations. ```php // config/cashier.php return [ 'model' => \App\Models\Team::class, 'plans' => [ // Minimal plan — only product_id and price_id are required 'basic' => [ 'product_id' => env('STRIPE_BASIC_PRODUCT_ID'), // e.g. prod_jad5TBR8eIwywy 'price_id' => env('STRIPE_BASIC_PRICE_ID'), // e.g. price_ruVAAh5dwyhwbtWIVQn0lUvc ], // Full-featured plan with all optional fields 'default' => [ 'product_id' => env('STRIPE_DEFAULT_PRODUCT_ID'), // prod_0JkO18GJx0Vtux 'price_id' => env('STRIPE_DEFAULT_PRICE_ID'), // price_Fxp5y8x0qjrm2jjk2nzuMpSF 'type' => 'primary', // Optional: overrides the array key as subscription type 'trial_days' => 14, // Optional: fixed trial period (mutually exclusive with has_generic_trial) // 'has_generic_trial' => true, // Optional: use Cashier's generic trial instead (cannot combine with trial_days) 'allow_promotion_codes' => true, // Optional: show promotion code input in Stripe Checkout 'collect_tax_ids' => true, // Optional: collect tax IDs during checkout 'metered_price' => true, // Optional: use metered billing (price added via meteredPrice()) ], ], ]; ``` -------------------------------- ### Register Billing Provider in Filament Source: https://github.com/maartenpaauw/filament-cashier-billing-provider/blob/main/README.md Add the `tenantBillingProvider` and `requiresTenantSubscription` to your `AdminPanelProvider`. Requiring a tenant subscription is optional. ```php use Maartenpaauw\Filament\Cashier\Stripe\BillingProvider; // ... public function panel(Panel $panel): Panel { return $panel // ... ->tenantBillingProvider(new BillingProvider('default')) ->requiresTenantSubscription() // ... } ``` -------------------------------- ### Generate Subscription Enforcement Middleware String Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Use the static `plan()` helper from `RedirectIfUserNotSubscribed` to generate the fully-qualified Laravel middleware string for subscription enforcement. This method supports various plan definitions, including single strings, comma-separated lists, and backed enums. ```php use Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed; use App\Enums\Plan; // --- Static helper: generate a middleware string --- // Default plan RedirectIfUserNotSubscribed::plan(); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:default" // Single string plan RedirectIfUserNotSubscribed::plan(plans: 'basic'); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic" // Multiple string plans (comma-separated) RedirectIfUserNotSubscribed::plan(plans: 'basic,advanced,premium'); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic,advanced,premium" // Backed enum RedirectIfUserNotSubscribed::plan(plans: Plan::Premium); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:premium" // Array of backed enums RedirectIfUserNotSubscribed::plan(plans: Plan::cases()); // → "Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic,advanced,premium" // --- Middleware handle() logic (executed automatically by Laravel) --- // For each plan in $plans: // 1. If plan->hasGenericTrial && tenant->onGenericTrial() → allow through // 2. If tenant->subscribedToProduct(productId, type) → allow through // If no plan passes → initiate Stripe Checkout for the first plan: // - Metered prices: added via ->meteredPrice(), prices array is empty // - trial_days set: ->trialDays(N) applied // - allow_promotion_codes: ->allowPromotionCodes() applied // - collect_tax_ids: ->collectTaxIds() applied // - Checkout success_url and cancel_url both point to Dashboard::getUrl() ``` -------------------------------- ### Service Container Binding for PlanRepository Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt The FilamentCashierServiceProvider automatically binds PlanRepository::class to ConfigPlanRepository::class. This allows for dependency injection of the plan repository throughout the package and in user code. ```php // Binding registered automatically — no manual registration needed. // Equivalent to what the provider does: $this->app->bind( \Maartenpaauw\Filament\Cashier\PlanRepository::class, \Maartenpaauw\Filament\Cashier\ConfigPlanRepository::class ); // Resolve anywhere via the container: $repo = app(\Maartenpaauw\Filament\Cashier\PlanRepository::class); $plan = $repo->get('default'); // Or type-hint in a constructor: class MyBillingService { public function __construct( private \Maartenpaauw\Filament\Cashier\PlanRepository $plans, ) {} public function getPlan(string $name): \Maartenpaauw\Filament\Cashier\Plan { return $this->plans->get($name); } } ``` -------------------------------- ### Register BillingProvider in Filament Panel Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Register the BillingProvider in your Filament panel configuration to manage tenant subscriptions. It supports single or multiple plans specified by strings or enums. Ensure you have `requiresTenantSubscription()` enabled to enforce subscription access. ```php use Filament\Panel; use Filament\PanelProvider; use Maartenpaauw\Filament\Cashier\Stripe\BillingProvider; use App\Enums\Plan; // Your string-backed enum class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { return $panel ->id('admin') ->path('admin') ->tenant(\App\Models\Team::class) // --- Single plan by string key --- ->tenantBillingProvider(new BillingProvider('default')) // --- Single plan by string-backed enum --- // ->tenantBillingProvider(new BillingProvider(Plan::Premium)) // --- Multiple plans: access granted if subscribed to ANY --- // ->tenantBillingProvider(new BillingProvider(['basic', 'advanced', 'premium'])) // ->tenantBillingProvider(new BillingProvider(Plan::cases())) // --- Require active subscription to access the panel --- ->requiresTenantSubscription() // ... other panel configuration ; } } ``` -------------------------------- ### Configure Tenant Model for Cashier Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Ensure your Filament tenant model uses Laravel Cashier's `Billable` trait and is set as the Cashier customer model in `config/cashier.php`. ```php // app/Models/Team.php use Laravel\Cashier\Billable; use Illuminate\Database\Eloquent\Model; class Team extends Model { use Billable; } ``` ```php // config/cashier.php return [ 'model' => \App\Models\Team::class, // ... ]; ``` -------------------------------- ### Inspect BillingProvider Middleware String Source: https://context7.com/maartenpaauw/filament-cashier-billing-provider/llms.txt Inspect the middleware string generated by BillingProvider, which is used for enforcing subscription access. This is useful for testing and understanding the middleware's configuration. ```php // Inspect the generated middleware string (useful for testing) $provider = new BillingProvider('basic'); echo $provider->getSubscribedMiddleware(); // Output: Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic $provider = new BillingProvider(Plan::cases()); // [Basic, Advanced, Premium] echo $provider->getSubscribedMiddleware(); // Output: Maartenpaauw\Filament\Cashier\Stripe\RedirectIfUserNotSubscribed:basic,advanced,premium ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.