akyroslabs / laravel-polar
Polar.sh integration for Laravel — subscriptions, checkout, customer portal, webhooks, plan limits, usage billing, and benefits. Polar as Merchant of Record.
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
README
Polar.sh integration for Laravel — subscriptions, checkout, customer portal, webhooks, plan limits, usage billing, benefits, and more. Polar as Merchant of Record.
No tax headaches. No invoice logic. Polar handles it all.
Installation
composer require akyroslabs/laravel-polar
Publish config and migrations:
php artisan vendor:publish --tag=polar-config php artisan vendor:publish --tag=polar-migrations php artisan migrate
Configuration
Add to your .env:
POLAR_API_KEY=polar_key_... POLAR_WEBHOOK_SECRET=whsec_... POLAR_SANDBOX=false POLAR_BILLABLE_MODEL=App\Models\Tenant
Configure plans in config/polar.php:
'plans' => [ 'free' => [ 'product_id' => null, 'limits' => ['servers' => 2, 'users' => 1], 'features' => ['basic_monitoring'], ], 'starter' => [ 'product_id' => 'polar-product-id', 'limits' => ['servers' => 10, 'users' => 3], 'features' => ['*'], ], 'pro' => [ 'product_id' => 'polar-product-id', 'limits' => ['servers' => 50, 'users' => 10], 'features' => ['*'], ], ],
Setup
Add the Billable trait to your model:
use AkyrosLabs\Polar\Billable; class Tenant extends Model { use Billable; }
Register the webhook URL in your Polar dashboard:
https://your-app.com/polar/webhook
Checkout
// Redirect to Polar checkout return $tenant->checkout('price_id') ->successUrl('/dashboard?upgraded=1') ->redirect(); // Subscribe to a plan return $tenant->subscribe('price_id') ->successUrl('/dashboard') ->redirect(); // One-time charge return $tenant->charge(4999, 'product_id') ->redirect(); // Just get the URL $url = $tenant->checkout('price_id')->url();
Subscription Management
// Check status $tenant->subscribed(); // Has active subscription? $tenant->subscribed('default', $productId); // On specific product? $tenant->onTrial(); // In trial? $tenant->onGracePeriod(); // Canceled but still active? // Get subscription $sub = $tenant->subscription(); $sub->active(); // Active? $sub->canceled(); // Canceled? $sub->valid(); // Active, trial, or grace period? $sub->ended(); // Fully ended? $sub->pastDue(); // Payment past due? // Actions $sub->swap('new_price_id'); // Change plan $sub->cancel(); // Cancel at period end $sub->resume(); // Resume during grace period
Customer Portal
return redirect($tenant->customerPortalUrl()); // Or shorthand return $tenant->redirectToCustomerPortal();
Plan Limits & Features
$tenant->planName(); // "pro" $tenant->planLimit('servers'); // 50 $tenant->planLimit('users'); // 10 $tenant->hasFeature('attack_mode'); // true $tenant->onFreePlan(); // false $tenant->exceedsLimit('servers', 48); // false $tenant->exceedsLimit('servers', 50); // true $tenant->planLimits(); // ['servers' => 50, 'users' => 10] $tenant->planFeatures(); // ['*']
Usage-Based Billing
// Track single event $tenant->ingestUsageEvent('api_call', ['endpoint' => '/servers']); // Batch events $tenant->ingestUsageEvents([ ['name' => 'api_call', 'metadata' => ['endpoint' => '/alerts']], ['name' => 'api_call', 'metadata' => ['endpoint' => '/servers']], ]); // Get meters $tenant->listCustomerMeters();
Benefits
$tenant->listBenefits(); $tenant->getBenefit('benefit_id'); $tenant->listBenefitGrants('benefit_id');
Orders
$tenant->orders; // All orders $tenant->hasPurchasedProduct('product_id'); // Check purchase
Webhooks
The package automatically processes these Polar events:
| Event | Action |
|---|---|
subscription.created |
Creates subscription + customer record |
subscription.updated |
Syncs status, product, period end |
subscription.active |
Sets status to active |
subscription.canceled |
Sets status to canceled |
subscription.revoked |
Sets status to revoked |
order.created |
Creates order record |
order.updated |
Syncs order data |
customer.created/updated/deleted |
Syncs customer records |
checkout.created/updated |
Fires events |
benefit_grant.created/updated/revoked |
Fires events |
Custom Event Listeners
use AkyrosLabs\Polar\Events\SubscriptionCreated; class HandleNewSubscription { public function handle(SubscriptionCreated $event): void { $tenant = $event->billable; // Send welcome email, provision resources, etc. } }
17 events available: WebhookReceived, WebhookHandled, SubscriptionCreated, SubscriptionUpdated, SubscriptionActive, SubscriptionCanceled, SubscriptionRevoked, OrderCreated, OrderUpdated, CustomerCreated, CustomerUpdated, CustomerDeleted, CheckoutCreated, CheckoutUpdated, BenefitGrantCreated, BenefitGrantUpdated, BenefitGrantRevoked
Middleware
// In bootstrap/app.php — auto-registered as 'subscribed' Route::middleware('subscribed')->group(function () { // Requires any active subscription }); Route::middleware('subscribed:pro')->group(function () { // Requires specific plan });
Blade Directives
@subscribed <p>You have an active subscription.</p> @endsubscribed @onPlan('pro') <p>Pro features here.</p> @endonPlan @onTrial <p>Your trial ends soon.</p> @endonTrial @feature('attack_mode') <button>Activate Attack Mode</button> @endfeature
Artisan Commands
# Sync all subscriptions with Polar php artisan polar:sync # List all products php artisan polar:products
Facade
use AkyrosLabs\Polar\Facades\Polar; $products = Polar::listProducts(); $subscription = Polar::getSubscription($id); $session = Polar::createCustomerSession($customerId);
Database Tables
| Table | Purpose |
|---|---|
polar_customers |
Billable ↔ Polar customer mapping, generic trial |
polar_subscriptions |
Active subscriptions with status, product, period |
polar_orders |
Orders with amounts, tax, refund tracking |
All tables use polymorphic billable relationships — works with any model.
Sandbox Mode
Set POLAR_SANDBOX=true to use sandbox-api.polar.sh for testing.
Requirements
- PHP 8.2+
- Laravel 11 or 12
License
MIT License
Built by Akyros Labs LLC — hello@akyroslabs.com