@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
You can check the status of an order by using the `status` attribute:
```php
$order->status;
```
Or you can use some of the helper methods offers by the `Order` model:
```php
$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:
```blade
@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:
```php
if ($order->hasProduct('product_id_123')) {
// ...
}
```
Or for an specific price:
```php
if ($order->hasPrice('price_id_123')) {
// ...
}
```
Furthermore, you can check if a consumer has purchased a specific product:
```php
if ($user->hasPurchasedProduct('product_id_123')) {
// ...
}
```
Or for an specific price:
```php
if ($user->hasPurchasedPrice('price_id_123')) {
// ...
}
```
### 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:
```php
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:
```php
$subscription = $user->subscription();
```
#### 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.
```php
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:
```php
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:
```php
if ($user->subscription()->hasProduct('product_id_123')) {
// ...
}
```
Or for a certain price:
```php
if ($user->subscription()->hasPrice('price_id_123')) {
// ...
}
```
If you wish to check if a subscription is on a specific price while being valid, you can use:
```php
if ($user->subscribedToPrice('price_id_123')) {
// ...
}
```
Alternatively, if you use different [subscription types](#multiple-subscriptions), you can pass a type as an additional parameter:
```php
if ($user->subscribed('swimming')) {
// ...
}
if ($user->subscribedToPrice('price_id_123', 'swimming')) {
// ...
}
```
#### Cancelled Status
To see if a user has cancelled their subscription, you can use the cancelled method:
```php
if ($user->subscription()->cancelled()) {
// ...
}
```
When they are in their grace period, you can utilize the `onGracePeriod` check.
```php
if ($user->subscription()->onGracePeriod()) {
// ...
}
```
#### 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.
```php
if ($user->subscription()->pastDue()) {
// ...
}
```
#### Subscription Scopes
There are several subscription scopes available for querying subscriptions in specific states:
```php
// 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:
```php
Subscription::query()->incomplete();
Subscription::query()->incompleteExpired();
Subscription::query()->onTrial();
Subscription::query()->active();
Subscription::query()->pastDue();
Subscription::query()->unpaid();
Subscription::query()->cancelled();
```
#### 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:
```php
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.
```php
$user = User::find(1);
$user->subscription()->swapAndInvoice('product_id_123');
```
#### 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:
```php
$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:
```php
$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
To cancel a subscription, call the `cancel` method.
```php
$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:
```php
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.
```php
$user->subscription()->resume();
```
When a cancelled subscription approaches the end of its grace period, it becomes expired and cannot be resumed.
#### Subscription Trials
> \[!NOTE]
> Coming soon.
### 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.
#### Webhook Events
* `Danestves\LaravelPolar\Events\BenefitGrantCreated`
* `Danestves\LaravelPolar\Events\BenefitGrantUpdated`
* `Danestves\LaravelPolar\Events\BenefitGrantRevoked`
* `Danestves\LaravelPolar\Events\OrderCreated`
* `Danestves\LaravelPolar\Events\OrderRefunded`
* `Danestves\LaravelPolar\Events\SubscriptionActive`
* `Danestves\LaravelPolar\Events\SubscriptionCanceled`
* `Danestves\LaravelPolar\Events\SubscriptionCreated`
* `Danestves\LaravelPolar\Events\SubscriptionRevoked`
* `Danestves\LaravelPolar\Events\SubscriptionUpdated`
Each of these events has a billable `$model` object and an event `$payload`. The subscription events also include the `$subscription` object. These can be accessed via the public properties.
If you wish to respond to these events, you must establish listeners for them. For example, you may wish to react when a subscription is updated.
```php
payload['type'] === 'subscription.updated') {
// Handle the incoming event...
}
}
}
```
The [Polar documentation](https://docs.polar.sh/integrate/webhooks/events) includes an example payload.
Laravel v11 and up will automatically discover the listener. If you're using Laravel v10 or lower, you should configure it in your app's `EventServiceProvider`:
```php
[
PolarEventListener::class,
],
];
}
```
## Roadmap
* [ ] Add support for trials
Polar itself doesn't support trials, but we can manage them by ourselves.
## Testing
```bash
composer test
```
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## Security Vulnerabilities
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
## Credits
* [laravel/cashier (Stripe)](https://github.com/laravel/cashier-stripe)
* [laravel/cashier (Paddle)](https://github.com/laravel/cashier-paddle)
* [lemonsqueezy/laravel](https://github.com/lmsqueezy/laravel)
* [All Contributors](../../contributors)
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
# NextJS
Source: https://docs.polar.sh/integrate/sdk/adapters/nextjs
Payments and Checkouts made dead simple with NextJS
```bash
pnpm install @polar-sh/nextjs zod
```
## Checkout
Create a Checkout handler which takes care of redirections.
```typescript
// checkout/route.ts
import { Checkout } from "@polar-sh/nextjs";
export const GET = Checkout({
accessToken: process.env.POLAR_ACCESS_TOKEN,
successUrl: process.env.SUCCESS_URL,
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
### Query Params
Pass query params to this route.
* products `?products=123`
* customerId (optional) `?products=123&customerId=xxx`
* customerExternalId (optional) `?products=123&customerExternalId=xxx`
* customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
* customerName (optional) `?products=123&customerName=Jane`
* metadata (optional) `URL-Encoded JSON string`
## Customer Portal
Create a customer portal where your customer can view orders and subscriptions.
```typescript
// portal/route.ts
import { CustomerPortal } from "@polar-sh/nextjs";
export const GET = CustomerPortal({
accessToken: process.env.POLAR_ACCESS_TOKEN,
getCustomerId: (req: NextRequest) => "", // Function to resolve a Polar Customer ID
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
## Webhooks
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
```typescript
// api/webhook/polar/route.ts
import { Webhooks } from "@polar-sh/nextjs";
export const POST = Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onPayload: async (payload) => {
// Handle the payload
// No need to return an acknowledge response
},
});
```
### Payload Handlers
The Webhook handler also supports granular handlers for easy integration.
* onCheckoutCreated: (payload) =>
* onCheckoutUpdated: (payload) =>
* onOrderCreated: (payload) =>
* onSubscriptionCreated: (payload) =>
* onSubscriptionUpdated: (payload) =>
* onSubscriptionActive: (payload) =>
* onSubscriptionCanceled: (payload) =>
* onSubscriptionRevoked: (payload) =>
* onProductCreated: (payload) =>
* onProductUpdated: (payload) =>
* onOrganizationUpdated: (payload) =>
* onBenefitCreated: (payload) =>
* onBenefitUpdated: (payload) =>
* onBenefitGrantCreated: (payload) =>
* onBenefitGrantUpdated: (payload) =>
* onBenefitGrantRevoked: (payload) =>
* onCustomerCreated: (payload) =>
* onCustomerUpdated: (payload) =>
* onCustomerDeleted: (payload) =>
* onCustomerStateChanged: (payload) =>
# Nuxt
Source: https://docs.polar.sh/integrate/sdk/adapters/nuxt
Payments and Checkouts made dead simple with Nuxt
## Installation
Choose your preferred package manager to install the module:
`pnpm add @polar-sh/nuxt`
### Register the module
Add the module to your `nuxt.config.ts`:
```typescript
export default defineNuxtConfig({
modules: ["@polar-sh/nuxt"],
});
```
## Checkout
Create a Checkout handler which takes care of redirections.
```typescript
// server/routes/api/checkout.post.ts
export default defineEventHandler((event) => {
const {
private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer },
} = useRuntimeConfig();
const checkoutHandler = Checkout({
accessToken: polarAccessToken,
successUrl: polarCheckoutSuccessUrl,
server: polarServer as "sandbox" | "production",
});
return checkoutHandler(event);
});
```
### Query Params
Pass query params to this route.
* products `?products=123`
* customerId (optional) `?products=123&customerId=xxx`
* customerExternalId (optional) `?products=123&customerExternalId=xxx`
* customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
* customerName (optional) `?products=123&customerName=Jane`
* metadata (optional) `URL-Encoded JSON string`
## Customer Portal
Create a customer portal where your customer can view orders and subscriptions.
```typescript
// server/routes/api/portal.get.ts
export default defineEventHandler((event) => {
const {
private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer },
} = useRuntimeConfig();
const customerPortalHandler = CustomerPortal({
accessToken: polarAccessToken,
server: polarServer as "sandbox" | "production",
getCustomerId: (event) => {
// Use your own logic to get the customer ID - from a database, session, etc.
return Promise.resolve("9d89909b-216d-475e-8005-053dba7cff07");
},
});
return customerPortalHandler(event);
});
```
## Webhooks
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
```typescript
// server/routes/webhook/polar.post.ts
export default defineEventHandler((event) => {
const {
private: { polarWebhookSecret },
} = useRuntimeConfig();
const webhooksHandler = Webhooks({
webhookSecret: polarWebhookSecret,
onPayload: async (payload) => {
// Handle the payload
// No need to return an acknowledge response
},
});
return webhooksHandler(event);
});
```
### Payload Handlers
The Webhook handler also supports granular handlers for easy integration.
* onCheckoutCreated: (payload) =>
* onCheckoutUpdated: (payload) =>
* onOrderCreated: (payload) =>
* onSubscriptionCreated: (payload) =>
* onSubscriptionUpdated: (payload) =>
* onSubscriptionActive: (payload) =>
* onSubscriptionCanceled: (payload) =>
* onSubscriptionRevoked: (payload) =>
* onProductCreated: (payload) =>
* onProductUpdated: (payload) =>
* onOrganizationUpdated: (payload) =>
* onBenefitCreated: (payload) =>
* onBenefitUpdated: (payload) =>
* onBenefitGrantCreated: (payload) =>
* onBenefitGrantUpdated: (payload) =>
* onBenefitGrantRevoked: (payload) =>
* onCustomerCreated: (payload) =>
* onCustomerUpdated: (payload) =>
* onCustomerDeleted: (payload) =>
* onCustomerStateChanged: (payload) =>
# Remix
Source: https://docs.polar.sh/integrate/sdk/adapters/remix
Payments and Checkouts made dead simple with Remix
```bash
pnpm install @polar-sh/remix zod
```
## Checkout
Create a Checkout handler which takes care of redirections.
```typescript
import { Checkout } from "@polar-sh/remix";
export const loader = Checkout({
accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN
successUrl: process.env.SUCCESS_URL,
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
### Query Params
Pass query params to this route.
* products `?products=123`
* customerId (optional) `?products=123&customerId=xxx`
* customerExternalId (optional) `?products=123&customerExternalId=xxx`
* customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
* customerName (optional) `?products=123&customerName=Jane`
* metadata (optional) `URL-Encoded JSON string`
## Customer Portal
Create a customer portal where your customer can view orders and subscriptions.
```typescript
import { CustomerPortal } from "@polar-sh/remix";
export const loader = CustomerPortal({
accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN
getCustomerId: (event) => "", // Function to resolve a Polar Customer ID
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
## Webhooks
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
```typescript
import { Webhooks } from "@polar-sh/remix";
export const action = Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onPayload: async (payload) => /** Handle payload */,
})
```
### Payload Handlers
The Webhook handler also supports granular handlers for easy integration.
* onCheckoutCreated: (payload) =>
* onCheckoutUpdated: (payload) =>
* onOrderCreated: (payload) =>
* onSubscriptionCreated: (payload) =>
* onSubscriptionUpdated: (payload) =>
* onSubscriptionActive: (payload) =>
* onSubscriptionCanceled: (payload) =>
* onSubscriptionRevoked: (payload) =>
* onProductCreated: (payload) =>
* onProductUpdated: (payload) =>
* onOrganizationUpdated: (payload) =>
* onBenefitCreated: (payload) =>
* onBenefitUpdated: (payload) =>
* onBenefitGrantCreated: (payload) =>
* onBenefitGrantUpdated: (payload) =>
* onBenefitGrantRevoked: (payload) =>
* onCustomerCreated: (payload) =>
* onCustomerUpdated: (payload) =>
* onCustomerDeleted: (payload) =>
* onCustomerStateChanged: (payload) =>
# Sveltekit
Source: https://docs.polar.sh/integrate/sdk/adapters/sveltekit
Payments and Checkouts made dead simple with Sveltekit
```bash
pnpm install @polar-sh/sveltekit zod
```
## Checkout
Create a Checkout handler which takes care of redirections.
```typescript
// /api/checkout/+server.ts
import { Checkout } from "@polar-sh/sveltekit";
export const GET = Checkout({
accessToken: process.env.POLAR_ACCESS_TOKEN,
successUrl: process.env.SUCCESS_URL,
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
### Query Params
Pass query params to this route.
* products `?products=123`
* customerId (optional) `?products=123&customerId=xxx`
* customerExternalId (optional) `?products=123&customerExternalId=xxx`
* customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
* customerName (optional) `?products=123&customerName=Jane`
* metadata (optional) `URL-Encoded JSON string`
## Customer Portal
Create a customer portal where your customer can view orders and subscriptions.
```typescript
// /api/portal/+server.ts
import { CustomerPortal } from "@polar-sh/sveltekit";
export const GET = CustomerPortal({
accessToken: process.env.POLAR_ACCESS_TOKEN,
getCustomerId: (event) => "", // Function to resolve a Polar Customer ID
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
});
```
## Webhooks
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
```typescript
// api/webhook/polar/route.ts
import { Webhooks } from "@polar-sh/sveltekit";
export const POST = Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onPayload: async (payload) => {
// Handle the payload
},
});
```
### Payload Handlers
The Webhook handler also supports granular handlers for easy integration.
* onCheckoutCreated: (payload) =>
* onCheckoutUpdated: (payload) =>
* onOrderCreated: (payload) =>
* onSubscriptionCreated: (payload) =>
* onSubscriptionUpdated: (payload) =>
* onSubscriptionActive: (payload) =>
* onSubscriptionCanceled: (payload) =>
* onSubscriptionRevoked: (payload) =>
* onProductCreated: (payload) =>
* onProductUpdated: (payload) =>
* onOrganizationUpdated: (payload) =>
* onBenefitCreated: (payload) =>
* onBenefitUpdated: (payload) =>
* onBenefitGrantCreated: (payload) =>
* onBenefitGrantUpdated: (payload) =>
* onBenefitGrantRevoked: (payload) =>
* onCustomerCreated: (payload) =>
* onCustomerUpdated: (payload) =>
* onCustomerDeleted: (payload) =>
* onCustomerStateChanged: (payload) =>
# TanStack Start
Source: https://docs.polar.sh/integrate/sdk/adapters/tanstack-start
Payments and Checkouts made dead simple with TanStack Start
`pnpm install @polar-sh/tanstack-start zod`
## Checkout
Create a Checkout handler which takes care of redirections.
```typescript
// routes/api/checkout.ts
import { Checkout } from "@polar-sh/tanstack-start";
import { createAPIFileRoute } from "@tanstack/react-start/api";
export const APIRoute = createAPIFileRoute("/api/checkout")({
GET: Checkout({
accessToken: process.env.POLAR_ACCESS_TOKEN,
successUrl: process.env.SUCCESS_URL,
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
}),
});
```
### Query Params
Pass query params to this route.
* products `?products=123`
* customerId (optional) `?products=123&customerId=xxx`
* customerExternalId (optional) `?products=123&customerExternalId=xxx`
* customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com`
* customerName (optional) `?products=123&customerName=Jane`
* metadata (optional) `URL-Encoded JSON string`
## Customer Portal
Create a customer portal where your customer can view orders and subscriptions.
```typescript
// routes/api/portal.ts
import { CustomerPortal } from "@polar-sh/tanstack-start";
import { createAPIFileRoute } from "@tanstack/react-start/api";
import { getSupabaseServerClient } from "~/servers/supabase-server";
export const APIRoute = createAPIFileRoute("/api/portal")({
GET: CustomerPortal({
accessToken: process.env.POLAR_ACCESS_TOKEN,
getCustomerId: async (request: Request) => "", // Function to resolve a Polar Customer ID
server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise
}),
});
```
## Webhooks
A simple utility which resolves incoming webhook payloads by signing the webhook secret properly.
```typescript
// api/webhook/polar.ts
import { Webhooks } from "@polar-sh/tanstack-start";
import { createAPIFileRoute } from "@tanstack/react-start/api";
export const APIRoute = createAPIFileRoute("/api/webhook/polar")({
POST: Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onPayload: async (payload) => {
// Handle the payload
// No need to return an acknowledge response
},
}),
});
```
#### Payload Handlers
The Webhook handler also supports granular handlers for easy integration.
* onCheckoutCreated: (payload) =>
* onCheckoutUpdated: (payload) =>
* onOrderCreated: (payload) =>
* onOrderUpdated: (payload) =>
* onOrderPaid: (payload) =>
* onSubscriptionCreated: (payload) =>
* onSubscriptionUpdated: (payload) =>
* onSubscriptionActive: (payload) =>
* onSubscriptionCanceled: (payload) =>
* onSubscriptionRevoked: (payload) =>
* onProductCreated: (payload) =>
* onProductUpdated: (payload) =>
* onOrganizationUpdated: (payload) =>
* onBenefitCreated: (payload) =>
* onBenefitUpdated: (payload) =>
* onBenefitGrantCreated: (payload) =>
* onBenefitGrantUpdated: (payload) =>
* onBenefitGrantRevoked: (payload) =>
* onCustomerCreated: (payload) =>
* onCustomerUpdated: (payload) =>
* onCustomerDeleted: (payload) =>
* onCustomerStateChanged: (payload) =>
# Go SDK
Source: https://docs.polar.sh/integrate/sdk/golang
Documentation coming soon.
# PHP SDK
Source: https://docs.polar.sh/integrate/sdk/php
Documentation coming soon.
# Python SDK
Source: https://docs.polar.sh/integrate/sdk/python
Fully type-hinted and allows both synchronous and asynchronous usage, thanks to [HTTPX](https://www.python-httpx.org/).
Under the hood, schemas are validated by [Pydantic](https://docs.pydantic.dev/latest/).
### Quickstart
```bash
pip install polar-sdk
```
```python
# Synchronous Example
from polar_sdk import Polar
s = Polar(
access_token="