# Source: https://polar.sh/docs/integrate/sdk/adapters/laravel.md

# Source: https://polar.sh/docs/guides/laravel.md

> ## Documentation Index
> Fetch the complete documentation index at: https://polar.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate Polar with Laravel

> In this guide, we'll show you how to integrate Polar with Laravel.

<img src="https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=7f57da3dcc77d6f8e0f8b2e760f0aeea" data-og-width="1920" width="1920" data-og-height="1080" height="1080" data-path="assets/guides/laravel/hero.jpeg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=280&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=5c8af67a7a1f3e1ed28e24fd633057fb 280w, https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=560&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=991feb7c5298a76f861c23fe8143583b 560w, https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=840&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=2a542a3866463e546ed8f3d295fcf843 840w, https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=1100&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=4f7bc6092412d56d1ae3bccdfeebdfcf 1100w, https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=1650&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=9b627520015006b566123b43379d83c0 1650w, https://mintcdn.com/polar/0Af3hN6-oIM4IHT3/assets/guides/laravel/hero.jpeg?w=2500&fit=max&auto=format&n=0Af3hN6-oIM4IHT3&q=85&s=36e5b661f7df187fac6bb1b04d18fa88 2500w" />

Consider following this guide while using the Polar Sandbox Environment. This will allow you to test your integration without affecting your production data.

## Polar Laravel Example App

We've created a simple example Laravel application that you can use as a reference.

[View Code on GitHub](https://github.com/polarsource/polar-laravel)

## Setting up environment variables

### Polar API Key

To authenticate with Polar, you need to create an access token, and supply it to Laravel using a `POLAR_API_KEY` environment variable.

You can create an organization access token from your organization settings.

## Fetching Polar Products for display

### Creating the Products Controller

Go ahead and add the following entry in your `routes/web.php` file:

```php  theme={null}
// routes/web.php
Route::get('/products', [ProductsController::class, 'handle']);
```

Next up, create the `ProductsController` class in the `app/Http/Controllers` directory:

```php  theme={null}
// app/Http/Controllers/ProductsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class ProductsController extends Controller
{
    public function handle(Request $request)
    {
        // Change from sandbox-api.polar.sh -> api.polar.sh when ready to go live
        // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET
        $data = Http::get('https://sandbox-api.polar.sh/v1/products', [
            'is_archived' => false,
        ]);

        $products = $data->json();

        return view('products', ['products' => $products['items']]);
    }
}
```

## Displaying Products

Finally, create the `products` view in the `resources/views` directory:

```php  theme={null}
// resources/views/products.blade.php
@foreach ($products as $product)
    <div>
        <h3>{{ $product['name'] }}</h3>
        <a href="/checkout?priceId={{ $product['prices'][0]['id'] }}">Buy</a>
    </div>
@endforeach
```

Notice that we create a link to `/checkout` with a query parameter `priceId`. This is the ID of the price that the user will be charged for when they click the "Buy" button. We will configure this route in the next section.

That's it for the products page. You can now display the products to your users, and they will be able to buy them. Let's now create the checkout endpoint.

## Generating Polar Checkout Sessions

This endpoint will be responsible for creating a new checkout session, redirecting the user to the Polar Checkout page & redirect back to a configured confirmation page.

Go ahead and create a new entry in your `routes/web.php` file:

```php  theme={null}
// routes/web.php
Route::get('/checkout', [CheckoutController::class, 'handle']);
```

Next, create the `CheckoutController` class in the `app/Http/Controllers` directory:

```php  theme={null}
// app/Http/Controllers/CheckoutController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class CheckoutController extends Controller
{
    public function handle(Request $request)
    {
        $productPriceId = $request->query('priceId', '');
        // Polar will replace {CHECKOUT_ID} with the actual checkout ID upon a confirmed checkout
        $confirmationUrl = $request->getSchemeAndHttpHost() . '/confirmation?checkout_id={CHECKOUT_ID}';

        // Change from sandbox-api.polar.sh -> api.polar.sh when ready to go live
        // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET
        $result = Http::withHeaders([
            'Authorization' => 'Bearer ' . env('POLAR_API_KEY'),
            'Content-Type' => 'application/json',
        ])->post('https://sandbox-api.polar.sh/v1/checkouts/custom/', [
            'product_price_id' => $productPriceId,
            'success_url' => $confirmationUrl,
            'payment_processor' => 'stripe',
        ]);

        $data = $result->json();

        $checkoutUrl = $data['url'];

        return redirect($checkoutUrl);
    }
}
```

We can now easily create a checkout session & redirect there by creating a link to `/checkout?priceId={priceId}`. Just like we did when displaying the products above.

Upon Checkout success, the user will be redirected to the confirmation page.

## Creating the Confirmation Page

Create a new entry in your `routes/web.php` file:

```php  theme={null}
// routes/web.php
Route::get('/confirmation', [ConfirmationController::class, 'handle']);
```

Next, create the `ConfirmationController` class in the `app/Http/Controllers` directory:

```php  theme={null}
// app/Http/Controllers/ConfirmationController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class ConfirmationController extends Controller
{
    public function handle(Request $request)
    {
        // Change from sandbox-api.polar.sh -> api.polar.sh when ready to go live
        // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET
        $data = Http::withHeaders([
            'Authorization' => 'Bearer ' . env('POLAR_API_KEY'),
            'Content-Type' => 'application/json',
        ])->get('https://sandbox-api.polar.sh/v1/checkouts/custom/' . $request->query('checkout_id'));

        $checkout = $data->json();

        Log::info(json_encode($checkout, JSON_PRETTY_PRINT));

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

The checkout is not considered "successful" yet however. It's initially marked as `confirmed` until you've received a webhook event `checkout.updated` with a status set to `succeeded`. We'll cover this in the next section.

## Handling Polar Webhooks

Polar can send you events about various things happening in your organization. This is very useful for keeping your database in sync with Polar checkouts, orders, subscriptions, etc.

Configuring a webhook is simple. Head over to your organization's settings page and click on the "Add Endpoint" button to create a new webhook.

### Tunneling webhook events to your local development environment

If you're developing locally, you can use a tool like [ngrok](https://ngrok.com/) to tunnel webhook events to your local development environment. This will allow you to test your webhook handlers without deploying them to a live server.

Run the following command to start an ngrok tunnel:

```bash Terminal theme={null}
ngrok http 3000
```

### Add Webhook Endpoint

1. Point the Webhook to `your-app.com/api/webhook/polar`. This must be an absolute URL which Polar can reach. If you use ngrok, the URL will look something like this: `https://<your-ngrok-id>.ngrok-free.app/api/webhook/polar`.
2. Select which events you want to be notified about. You can read more about the available events in the [Events section](/api-reference#webhooks).
3. Generate a secret key to sign the requests. This will allow you to verify that the requests are truly coming from Polar.
4. Add the secret key to your environment variables.

```bash Terminal theme={null}
# .env
POLAR_API_KEY="polar_oat..."
POLAR_WEBHOOK_SECRET="..."
```

### Setting up the Webhook handler

First, we need to install the standard-webhooks package to properly decode the incoming webhook payloads.

```bash Terminal theme={null}
composer require standard-webhooks/standard-webhooks:dev-main
```

Go and add a `routes/api.php` file and add the following entry:

```php  theme={null}
// routes/api.php
Route::webhooks('/webhook/polar');
```

Make sure that it is included in the Bootstrap file.

```php  theme={null}
// bootstrap/app.php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();
```

We will use Spatie's Webhook Client to handle the webhook events. It will automatically verify the signature of the requests, and dispatch the payload to a job queue for processing.

```bash Terminal theme={null}
composer require spatie/laravel-webhook-client
```

Let's publish the config:

```bash Terminal theme={null}
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-config"
```

This will create a new file called webhook-client.php in the config folder.

We need to adjust it to properly verify the signature of the requests.

```php  theme={null}
// config/webhook-client.php
<?php
return [
    'configs' => [
        [
            /*
             * This package supports multiple webhook receiving endpoints. If you only have
             * one endpoint receiving webhooks, you can use 'default'.
             */
            'name' => 'default',

            /*
             * We expect that every webhook call will be signed using a secret. This secret
             * is used to verify that the payload has not been tampered with.
             */
            'signing_secret' => env('POLAR_WEBHOOK_SECRET'),

            /*
             * The name of the header containing the signature.
             */
            'signature_header_name' => 'webhook-signature',

            /*
             *  This class will verify that the content of the signature header is valid.
             *
             * It should implement \Spatie\WebhookClient\SignatureValidator\SignatureValidator
             */
            // 'signature_validator' => \Spatie\WebhookClient\SignatureValidator\DefaultSignatureValidator::class,
            'signature_validator' => App\Handler\PolarSignature::class,

            /*
             * This class determines if the webhook call should be stored and processed.
             */
            'webhook_profile' => \Spatie\WebhookClient\WebhookProfile\ProcessEverythingWebhookProfile::class,

            /*
             * This class determines the response on a valid webhook call.
             */
            'webhook_response' => \Spatie\WebhookClient\WebhookResponse\DefaultRespondsTo::class,

            /*
             * The classname of the model to be used to store webhook calls. The class should
             * be equal or extend Spatie\WebhookClient\Models\WebhookCall.
             */
            'webhook_model' => \Spatie\WebhookClient\Models\WebhookCall::class,

            /*
             * In this array, you can pass the headers that should be stored on
             * the webhook call model when a webhook comes in.
             *
             * To store all headers, set this value to `*`.
             */
            'store_headers' => [],

            /*
             * The class name of the job that will process the webhook request.
             *
             * This should be set to a class that extends \Spatie\WebhookClient\Jobs\ProcessWebhookJob.
             */
            'process_webhook_job' => App\Handler\ProcessWebhook::class,
        ],
    ],

    /*
     * The integer amount of days after which models should be deleted.
     *
     * 7 deletes all records after 1 week. Set to null if no models should be deleted.
     */
    'delete_after_days' => 30,
];
```

### Preparing the database

By default, all webhook calls get saved into the database. So, we need to publish the migration that will hold the records. So run:

```bash Terminal theme={null}
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations"
```

This will create a new migration file in the “database/migration” folder.

Then run `php artisan migrate` to run the migration.

### Setting up the queue system

Before we set up our job handler — let’s set up our queue system

Go to your “.env” file and set the QUEUE\_CONNECTION=database — you can decide to use other connections like redis.

Let’s create our jobs table by running php artisan queue:table and then run the migration using php artisan migrate.

### Create the Handlers

The next thing we do is to create a folder named Handler inside the app folder. Then inside this app/Handler, create two files which are

* PolarSignature.php
* ProcessWebhook.php

Inside app/Handler/PolarSignature.php, what we want to do is to validate that the request came from Polar. Add the code to that file.

```php  theme={null}
// app/Handler/PolarSignature.php
<?php

namespace App\Handler;

use Illuminate\Http\Request;
use Spatie\WebhookClient\Exceptions\WebhookFailed;
use Spatie\WebhookClient\WebhookConfig;
use Spatie\WebhookClient\SignatureValidator\SignatureValidator;

class PolarSignature implements SignatureValidator
{
    public function isValid(Request $request, WebhookConfig $config): bool
    {
        $signingSecret = base64_encode($config->signingSecret);
        $wh = new \StandardWebhooks\Webhook($signingSecret);

        return boolval( $wh->verify($request->getContent(), array(
            "webhook-id" => $request->header("webhook-id"),
            "webhook-signature" => $request->header("webhook-signature"),
            "webhook-timestamp" => $request->header("webhook-timestamp"),
        )));
    }
}
```

Great. So the other file app/Handler/ProcessWebhook.php extends the ProcessWebhookJob class which holds the WebhookCall variables containing each job’s detail.

```php  theme={null}
// app/Handler/ProcessWebhook.php
<?php

namespace App\Handler;

use Illuminate\Support\Facades\Log;
use Spatie\WebhookClient\Jobs\ProcessWebhookJob;

class ProcessWebhook extends ProcessWebhookJob
{
    public function handle()
    {
        $decoded = json_decode($this->webhookCall, true);
        $data = $decoded['payload'];

        switch ($data['type']) {
            case "checkout.created":
                // Handle the checkout created event
                break;
            case "checkout.updated":
                // Handle the checkout updated event
                break;
            case "subscription.created":
                // Handle the subscription created event
                break;
            case "subscription.updated":
                // Handle the subscription updated event
                break;
            case "subscription.active":
                // Handle the subscription active event
                break;
            case "subscription.revoked":
                // Handle the subscription revoked event
                break;
            case "subscription.canceled":
                // Handle the subscription canceled event
                break;
            default:
                // Handle unknown event
                Log::info($data['type']);
                break;
        }

        //Acknowledge you received the response
        http_response_code(200);
    }
}
```

Our application is ready to receive webhook requests.

Don’t forget to run `php artisan queue:listen` to process the jobs.

### Tips

If you're keeping track of active and inactive subscriptions in your database, make sure to handle the `subscription.active` and `subscription.revoked` events accordingly.

The cancellation of a subscription is handled by the `subscription.canceled`
event. The user has probably canceled their subscription before the end of the
billing period. Do not revoke any kind of access immediately, but rather wait
until the end of the billing period or when you receive the
`subscription.revoked` event.

## Notifying the client about the event

If you're building a real-time application, you might want to notify the client about the event. On the confirmation-page, you can listen for the `checkout.updated` event and update the UI accordingly when it reaches the succeeded status.

## Polar Laravel Example App

We've created a simple example Laravel application that you can use as a reference

[View Code on GitHub](https://github.com/polarsource/polar-laravel)

If you have issues or need support, feel free to join [our Discord](https://discord.gg/Pnhfz3UThd).