PHPackages                             confetticode/laravel-polar - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. confetticode/laravel-polar

ActiveLibrary[Payment Processing](/categories/payments)

confetticode/laravel-polar
==========================

Integrate Laravel applications with Polar.sh

v0.3.0(12mo ago)074MITPHPPHP ^8.2CI passing

Since Jul 1Pushed 12mo agoCompare

[ Source](https://github.com/confetticode/laravel-polar)[ Packagist](https://packagist.org/packages/confetticode/laravel-polar)[ Docs](https://github.com/confetticode/laravel-polar)[ GitHub Sponsors](https://github.com/danestves)[ Fund](https://polar.sh/danestves)[ RSS](/packages/confetticode-laravel-polar/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (11)Versions (8)Used By (0)

Laravel Polar
=============

[](#laravel-polar)

[![Latest Version on Packagist](https://camo.githubusercontent.com/90b99915321b0c21dad1218ad3acffdf65c56d67ff408da2aed67d4a9e160792/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f636f6e6665747469636f64652f6c61726176656c2d706f6c61722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/confetticode/laravel-polar)[![GitHub Tests Action Status](https://camo.githubusercontent.com/14aaed073e7527b6d2563ab2ce7c2f621c3c06ba78976e955c36e153775fc0a7/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f636f6e6665747469636f64652f6c61726176656c2d706f6c61722f746573742e796d6c3f6272616e63683d6d61696e266c6162656c3d74657374267374796c653d666c61742d737175617265)](https://github.com/confetticode/laravel-polar/actions?query=workflow%3Atest+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/9c9af6b129ca9b682eb0cb86bca1e16212173c63f8e237b4c1489fc50f26d130/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f636f6e6665747469636f64652f6c61726176656c2d706f6c61722f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/confetticode/laravel-polar/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/dc109308daca335ffc41eea3dc5872350a8584782f134ee2dd4bcd9c346b4843/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f636f6e6665747469636f64652f6c61726176656c2d706f6c61722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/confetticode/laravel-polar)

⚠️ Warning
----------

[](#️-warning)

This package is NOT an official Polar project. I first copied code from [danestves/laravel-polar](https://github.com/danestves/laravel-polar) but will modify the way I expect. Changes follow semantic versioning and are noted in [CHANGELOG.md](./CHANGELOG.md) file. For the official one, please visit [Polar for Laravel](https://docs.polar.sh/integrate/sdk/adapters/laravel).

Requirements
------------

[](#requirements)

Laravel `12.x`

Installation
------------

[](#installation)

**Step 1:** Install the package via composer

```
composer require confetticode/laravel-polar
```

**Step 2:** Run the `polar:install` command

```
php artisan polar:install
```

This will publish the config, migrations and views, and ask to run the migrations.

Or publish and run the migrations individually:

```
php artisan vendor:publish --tag="polar-migrations"
php artisan vendor:publish --tag="polar-config"
php artisan vendor:publish --tag="polar-views"
php artisan migrate
```

**Step 3:** Set polar api url, access token and webhook secret

Create a new token in the Polar Dashboard &gt; Settings &gt; Developers

> E.g. [https://polar.sh/dashboard/org\_slug/settings](https://polar.sh/dashboard/org_slug/settings)

Create a new webhook secret in the Polar Dashboard &gt; Settings &gt; Webhooks

> E.g. [https://polar.sh/dashboard/org\_slug/settings/Webhooks](https://polar.sh/dashboard/org_slug/settings/Webhooks)

Then, set them in the .env file.

```
POLAR_API_URL=https://api.polar.sh
POLAR_ACCESS_TOKEN=""
POLAR_WEBHOOK_SECRET=""
```

> For sandbox, use  and

Usage
-----

[](#usage)

### Billable Trait

[](#billable-trait)

Let’s make sure everything’s ready for your customers to checkout smoothly. 🛒

First, we’ll need to set up a model to handle billing—don’t worry, it’s super simple! In most cases, this will be your app’s User model. Just add the Billable trait to your model like this (you’ll import it from the package first, of course):

```
use ConfettiCode\LaravelPolar\Billable;

class User extends Authenticatable
{
    use Billable;
}
```

Now the user model will have access to the methods provided by the package. You can make any model billable by adding the trait to it, not just the User model.

### Polar Script

[](#polar-script)

Polar includes a JavaScript script that you can use to initialize the [Polar Embedded Checkout](https://docs.polar.sh/features/checkout/embed). If you going to use this functionality, you can use the `@polarEmbedScript` directive to include the script in your views inside the `` tag.

```

    ...

    @polarEmbedScript

```

### Webhooks

[](#webhooks)

This package includes a webhook handler that will handle the webhooks from Polar.

#### Webhooks &amp; CSRF Protection

[](#webhooks--csrf-protection)

Incoming webhooks should not be affected by [CSRF protection](https://laravel.com/docs/csrf). To prevent this, add your webhook path to the except list:

```
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'polar/*',
    ]);
})
```

### Commands

[](#commands)

This package includes a list of commands that you can use to retrieve information about your Polar account.

CommandDescription`php artisan polar:products`List all available products with their ids`php artisan polar:products --id=123`List a specific product by id`php artisan polar:products --id=123 --id=321`List a specific products by ids### Checkouts

[](#checkouts)

#### Single Payments

[](#single-payments)

To create a checkout to show only a single payment, pass a single items to the array of products when creating the checkout.

```
use Illuminate\Http\Request;

Route::get('/subscribe', function (Request $request) {
    return $request->user()->checkout(['product_id_123']);
});
```

If you want to show multiple products that the user can choose from, you can pass an array of product ids to the `checkout` method.

```
use Illuminate\Http\Request;

Route::get('/subscribe', function (Request $request) {
    return $request->user()->checkout(['product_id_123', 'product_id_456']);
});
```

This could be useful if you want to offer monthly, yearly, and lifetime plans for example.

Note

If you are requesting the checkout a lot of times we recommend you to cache the URL returned by the `checkout` method.

#### Custom Price

[](#custom-price)

You can override the price of a product using the `charge` method.

```
use Illuminate\Http\Request;

Route::get('/subscribe', function (Request $request) {
    return $request->user()->charge(1000, ['product_id_123']);
});
```

#### Embedded Checkout

[](#embedded-checkout)

Instead of redirecting the user you can create the checkout link, pass it to the page and use our blade component:

```
use Illuminate\Http\Request;

Route::get('/billing', function (Request $request) {
    $checkout = $request->user()->checkout(['product_id_123']);

    return view('billing', ['checkout' => $checkout]);
});
```

Now we can use the button like this:

```

```

The component accepts the normal props that a link element accepts. You can change the theme of the embedded checkout by using the following prop:

```

```

It defaults to light theme, so you only need to pass the prop if you want to change it.

##### Inertia

[](#inertia)

For projects usin Inertia you can render the button adding `data-polar-checkout` to the link in the following way:

`button.vue`

```

  Buy now

```

```
// button.{jsx,tsx}

export function Button() {
  return (
    Buy now
  );
}
```

At the end is just a normal link but ysin an special attribute for the script to render the embedded checkout.

Note

Remember that you can use the theme attribute too to change the color system in the checkout

### Prefill Customer Information

[](#prefill-customer-information)

You can override the user data using the following methods in your models provided by the `Billable` trait.

```
public function polarNameField(): string; // default: 'name'
public function polarEmailField(): string; // default: 'email'

public function polarName(): ?string; // default: $model->name (depends on polarNameField)
public function polarEmail(): ?string; // default: $model->email (depends on polarEmailField)
```

### Redirects After Purchase

[](#redirects-after-purchase)

You can redirect the user to a custom page after the purchase using the `withSuccessUrl` method:

```
$request->user()->checkout('variant-id')
    ->withSuccessUrl(url('/success'));
```

You can also add the `checkout_id={CHECKOUT_ID}` query parameter to the URL to retrieve the checkout session id:

```
$request->user()->checkout('variant-id')
    ->withSuccessUrl(url('/success?checkout_id={CHECKOUT_ID}'));
```

### Custom metadata and customer metadata

[](#custom-metadata-and-customer-metadata)

You can add custom metadata to the checkout session using the `withMetadata` method:

```
$request->user()->checkout('variant-id')
    ->withMetadata(['key' => 'value']);
```

You can also add customer metadata to the checkout session using the `withCustomerMetadata` method:

```
$request->user()->checkout('variant-id')
    ->withCustomerMetadata(['key' => 'value']);
```

These will then be available in the relevant webhooks for you.

#### Reserved Keywords

[](#reserved-keywords)

When working with custom data, this library has a few reserved terms.

- `billable_id`
- `billable_type`
- `subscription_type`

Using any of these will result in an exception being thrown.

### Customers

[](#customers)

#### Customer Portal

[](#customer-portal)

Customers can update their personal information (e.g., name, email address) by accessing their [self-service customer portal](https://docs.polar.sh/features/customer-portal). To redirect customers to this portal, call the `redirectToCustomerPortal()` method on your billable model (e.g., the User model).

```
use Illuminate\Http\Request;

Route::get('/customer-portal', function (Request $request) {
    return $request->user()->redirectToCustomerPortal();
});
```

Optionally, you can obtain the signed customer portal URL directly:

```
$url = $user->customerPortalUrl();
```

### Orders

[](#orders)

#### Retrieving Orders

[](#retrieving-orders)

You can retrieve orders by using the `orders` relationship on the billable model:

```

    @foreach ($user->orders as $order)
        {{ $order->ordered_at->toFormattedDateString() }}
        {{ $order->polar_id }}
        {{ $order->amount }}
        {{ $order->tax_amount }}
        {{ $order->refunded_amount }}
        {{ $order->refunded_tax_amount }}
        {{ $order->currency }}

    @endforeach

```

#### Check order status

[](#check-order-status)

You can check the status of an order by using the `status` attribute:

```
$order->status;
```

Or you can use some of the helper methods offers by the `Order` model:

```
$order->paid();
```

Aside from that, you can run two other checks: refunded, and partially refunded. If the order is refunded, you can utilize the refunded\_at timestamp:

```
@if ($order->refunded())
    Order {{ $order->polar_id }} was refunded on {{ $order->refunded_at->toFormattedDateString() }}
@endif
```

You may also see if an order was for a certain product:

```
if ($order->hasProduct('product_id_123')) {
    // ...
}
```

Furthermore, you can check if a consumer has purchased a specific product:

```
if ($user->hasPurchasedProduct('product_id_123')) {
    // ...
}
```

### Subscriptions

[](#subscriptions)

#### Creating Subscriptions

[](#creating-subscriptions)

Starting a subscription is simple. For this, we require our product's variant id. Copy the product id and start a new subscription checkout using your billable model:

```
use Illuminate\Http\Request;

Route::get('/subscribe', function (Request $request) {
    return $request->user()->subscribe('product_id_123');
});
```

When a customer completes their checkout, the incoming `SubscriptionCreated` event webhook connects it to your billable model in the database. You may then get the subscription from your billable model:

```
$subscription = $user->subscription();
```

#### Checking Subscription Status

[](#checking-subscription-status)

Once a consumer has subscribed to your services, you can use a variety of methods to check on the status of their subscription. The most basic example is to check if a customer has a valid subscription.

```
if ($user->subscribed()) {
    // ...
}
```

You can utilize this in a variety of locations in your app, such as middleware, rules, and so on, to provide services. To determine whether an individual subscription is valid, you can use the `valid` method:

```
if ($user->subscription()->valid()) {
    // ...
}
```

This method, like the subscribed method, returns true if your membership is active, on trial, past due, or cancelled during its grace period.

You may also check if a subscription is for a certain product:

```
if ($user->subscription()->hasProduct('product_id_123')) {
    // ...
}
```

If you wish to check if a subscription is on a specific product while being valid, you can use:

```
if ($user->subscribedToProduct('product_id_123')) {
    // ...
}
```

Alternatively, if you use different [subscription types](#multiple-subscriptions), you can pass a type as an additional parameter:

```
if ($user->subscribed('swimming')) {
    // ...
}

if ($user->subscribedToProduct('product_id_123', 'swimming')) {
    // ...
}
```

#### Cancelled Status

[](#cancelled-status)

To see if a user has cancelled their subscription, you can use the cancelled method:

```
if ($user->subscription()->cancelled()) {
    // ...
}
```

When they are in their grace period, you can utilize the `onGracePeriod` check.

```
if ($user->subscription()->onGracePeriod()) {
    // ...
}
```

#### Past Due Status

[](#past-due-status)

If a recurring payment fails, the subscription will become past due. This indicates that the subscription is still valid, but your customer's payments will be retried in two weeks.

```
if ($user->subscription()->pastDue()) {
    // ...
}
```

#### Subscription Scopes

[](#subscription-scopes)

There are several subscription scopes available for querying subscriptions in specific states:

```
// Get all active subscriptions...
$subscriptions = Subscription::query()->active()->get();

// Get all of the cancelled subscriptions for a specific user...
$subscriptions = $user->subscriptions()->cancelled()->get();
```

Here's all available scopes:

```
Subscription::query()->incomplete();
Subscription::query()->incompleteExpired();
Subscription::query()->onTrial();
Subscription::query()->active();
Subscription::query()->pastDue();
Subscription::query()->unpaid();
Subscription::query()->cancelled();
```

#### Changing Plans

[](#changing-plans)

When a consumer is on a monthly plan, they may desire to upgrade to a better plan, alter their payments to an annual plan, or drop to a lower-cost plan. In these cases, you can allow them to swap plans by giving a different product id to the `swap` method:

```
use App\Models\User;

$user = User::find(1);

$user->subscription()->swap('product_id_123');
```

This will change the customer's subscription plan, however billing will not occur until the next payment cycle. If you want to immediately invoice the customer, you can use the `swapAndInvoice` method instead.

```
$user = User::find(1);

$user->subscription()->swapAndInvoice('product_id_123');
```

#### Multiple Subscriptions

[](#multiple-subscriptions)

In certain situations, you may wish to allow your consumer to subscribe to numerous subscription kinds. For example, a gym may provide a swimming and weight lifting subscription. You can let your customers subscribe to one or both.

To handle the various subscriptions, you can offer a type of subscription as the second argument when creating a new one:

```
$user = User::find(1);

$checkout = $user->subscribe('product_id_123', 'swimming');
```

You can now always refer to this specific subscription type by passing the type argument when getting it:

```
$user = User::find(1);

// Retrieve the swimming subscription type...
$subscription = $user->subscription('swimming');

// Swap plans for the gym subscription type...
$user->subscription('gym')->swap('product_id_123');

// Cancel the swimming subscription...
$user->subscription('swimming')->cancel();
```

#### Cancelling Subscriptions

[](#cancelling-subscriptions)

To cancel a subscription, call the `cancel` method.

```
$user = User::find(1);

$user->subscription()->cancel();
```

This will cause your subscription to be cancelled. If you cancel your subscription in the middle of the cycle, it will enter a grace period, and the ends\_at column will be updated. The customer will continue to have access to the services offered for the duration of the period. You may check the grace period by calling the `onGracePeriod` method:

```
if ($user->subscription()->onGracePeriod()) {
    // ...
}
```

Polar does not offer immediate cancellation. To resume a subscription while it is still in its grace period, use the resume method.

```
$user->subscription()->resume();
```

When a cancelled subscription approaches the end of its grace period, it becomes expired and cannot be resumed.

#### Subscription Trials

[](#subscription-trials)

Note

Coming soon.

### Handling Webhooks

[](#handling-webhooks)

Polar can send webhooks to your app, allowing you to react. By default, this package handles the majority of the work for you. If you have properly configured webhooks, it will listen for incoming events and update your database accordingly. We recommend activating all event kinds so you may easily upgrade in the future.

#### Supported Events

[](#supported-events)

Currently, we support a few events by default. They have their own handlers defined in the `config/polar.php` file.

- `customer.updated`
- `order.created`
- `order.updated`
- `subscription.created`
- `subscription.updated`
- `subscription.active`
- `subscription.canceled`
- `subscription.revoked`

You can modify however you want. Please be carefully before changing these default behavior or subscribed methods don't work as you expect.

#### Additional Events

[](#additional-events)

E.g, if you want to handle more events like `checkout.created` and `checkout.updated`. First, you have to create a class like this.

```
