Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions with-php-laravel/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# https://docs.polar.sh/integrate/oat
POLAR_ACCESS_TOKEN="polar_oat_..."
# https://docs.polar.sh/integrate/webhooks/endpoints#setup-webhooks
POLAR_WEBHOOK_SECRET="polar_whs_..."
# Polar server mode - production or sandbox
POLAR_MODE="sandbox"
# client url - this is the URL the customer would be led to if they purchase something.
POLAR_SUCCESS_URL="http://localhost:8000"
9 changes: 9 additions & 0 deletions with-php-laravel/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/vendor/
/.env
/bootstrap/cache/*
/storage/
/public/storage
/node_modules
.DS_Store
.idea
.vscode
29 changes: 29 additions & 0 deletions with-php-laravel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
![](../logo.svg)

# Getting started with Polar and Laravel

## Clone the repository

```bash
npx degit polarsource/examples/with-php-laravel ./with-php-laravel
```

## How to use

1. Run the command below to copy the `.env.example` file:

```bash
cp .env.example .env
```

2. Run the command below to install project dependencies:

```bash
composer install
```

3. Run the Laravel application using the following command:

```bash
php artisan serve
```
137 changes: 137 additions & 0 deletions with-php-laravel/app/Http/Controllers/PolarController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use StandardWebhooks\Webhook;
use StandardWebhooks\Exception\WebhookVerificationException;
use GuzzleHttp\Client;

class PolarController extends \Illuminate\Routing\Controller
{
private $accessToken;
private $webhookSecret;
private $server;
private $baseUrl;

public function __construct()
{
$this->accessToken = env('POLAR_ACCESS_TOKEN');
$this->webhookSecret = env('POLAR_WEBHOOK_SECRET');
$this->server = env('POLAR_MODE', 'production');
$this->baseUrl = $this->server === 'sandbox' ? 'https://sandbox-api.polar.sh' : 'https://api.polar.sh';
}

private function getClient()
{
return new Client([
'headers' => [
'Authorization' => 'Bearer ' . $this->accessToken,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
]
]);
}

public function index()
{
if (!$this->accessToken) {
return response("Missing POLAR_ACCESS_TOKEN", 401);
}

try {
$response = $this->getClient()->get($this->baseUrl . '/v1/products/?is_archived=false');
$data = json_decode($response->getBody()->getContents());
$items = $data->items ?? [];

$productLinks = array_map(function($product) {
return "<div><a target='_blank' href='/checkout?products={$product->id}'>{$product->name}</a></div>";
}, $items);

$html = "<html><body>
<form action='/portal' method='get'>
<input type='email' name='email' placeholder='Email' required />
<button type='submit'>Open Customer Portal</button>
</form>
" . implode('', $productLinks) . "
</body></html>";

return response($html, 200, ['Content-Type' => 'text/html']);
} catch (\Exception $e) {
return response("Error: " . $e->getMessage(), 500);
}
}

public function checkout(Request $request)
{
$productIds = $request->query('products');
if (!$productIds) {
return response('Missing products parameter', 400);
}

try {
$response = $this->getClient()->post($this->baseUrl . '/v1/checkouts/', [
'json' => [
'products' => is_array($productIds) ? $productIds : [$productIds],
'success_url' => env('POLAR_SUCCESS_URL', $request->getSchemeAndHttpHost() . '/')
]
]);

$data = json_decode($response->getBody()->getContents());
return redirect($data->url);
} catch (\Exception $e) {
return response("Error: " . $e->getMessage(), 500);
}
}

public function portal(Request $request)
{
$email = $request->query('email');
if (!$email) {
return response('Missing email parameter', 400);
}

try {
$response = $this->getClient()->get($this->baseUrl . '/v1/customers/?email=' . urlencode($email));
$data = json_decode($response->getBody()->getContents());
$items = $data->items ?? [];

if (empty($items)) {
return response('Customer not found', 404);
}

$customer = $items[0];

$sessionResponse = $this->getClient()->post($this->baseUrl . '/v1/customer-sessions/', [
'json' => [
'customer_id' => $customer->id
]
]);

$sessionData = json_decode($sessionResponse->getBody()->getContents());
return redirect($sessionData->customer_portal_url);
} catch (\Exception $e) {
return response("Error: " . $e->getMessage(), 500);
}
}

public function webhooks(Request $request)
{
$payload = $request->getContent();
$headers = [
'webhook-id' => $request->header('webhook-id'),
'webhook-timestamp' => $request->header('webhook-timestamp'),
'webhook-signature' => $request->header('webhook-signature'),
];

$base64Secret = base64_encode($this->webhookSecret);
$webhook = new Webhook($base64Secret);

try {
$webhook->verify($payload, $headers);
return response()->json(json_decode($payload));
} catch (WebhookVerificationException $e) {
return response($e->getMessage(), 403);
}
}
}
21 changes: 21 additions & 0 deletions with-php-laravel/artisan
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php

define('LARAVEL_START', microtime(true));

// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';

// Bootstrap Laravel and handle the command...
$app = require_once __DIR__.'/bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);

$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);

$kernel->terminate($input, $status);

exit($status);
28 changes: 28 additions & 0 deletions with-php-laravel/bootstrap/app.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
config([
'app.key' => 'php example polar',
'app.env' => 'local',
'session.driver' => 'file',
'cache.default' => 'file',
'database.default' => 'sqlite',
'database.connections.sqlite.database' => ':memory:',
]);

$middleware->validateCsrfTokens(except: [
'/polar/webhooks'
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
22 changes: 22 additions & 0 deletions with-php-laravel/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "polarsource/example-laravel",
"description": "Polar with Laravel Example",
"type": "project",
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"polar-sh/sdk": "^0.1.0",
"standard-webhooks/standard-webhooks": "dev-main as 1.0.0",
"vlucas/phpdotenv": "^5.6"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
}
}
Loading