From 384a5a4f9264d47040283eaadb502b5d040d4fa1 Mon Sep 17 00:00:00 2001 From: Emil Widlund Date: Fri, 16 May 2025 17:27:12 +0200 Subject: [PATCH] docs: update polar docs (#2671) Add updated docs to new Polar plugin --- docs/content/docs/plugins/polar.mdx | 507 ++++++++++++++++++++++++---- 1 file changed, 444 insertions(+), 63 deletions(-) diff --git a/docs/content/docs/plugins/polar.mdx b/docs/content/docs/plugins/polar.mdx index 6569c25f44..7cef8fb5b3 100644 --- a/docs/content/docs/plugins/polar.mdx +++ b/docs/content/docs/plugins/polar.mdx @@ -11,78 +11,459 @@ description: Better Auth Plugin for Payment and Checkouts using Polar repo](https://github.com/polarsource/polar-adapters). +## Features + +- Checkout Integration +- Customer Portal +- Automatic Customer creation on signup +- Event Ingestion & Customer Meters for flexible Usage Based Billing +- Handle Polar Webhooks securely with signature verification +- Reference System to associate purchases with organizations + ## Installation - - - ### Install the plugin - First, install the plugin: +```bash +pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk +``` - ```package-install - @polar-sh/better-auth - ``` - - - ### Install the Polar SDK +## Preparation - Next, install the Polar SDK on your server: +Go to your Polar Organization Settings, and create an Organization Access Token. Add it to your environment. - ```package-install - @polar-sh/sdk - ``` - - - ### Add the plugin to your auth config +```bash +# .env +POLAR_ACCESS_TOKEN=... +``` - ```ts title="auth.ts" - import { betterAuth } from "better-auth"; - import { polar } from "@polar-sh/better-auth"; - import { Polar } from "@polar-sh/sdk"; +### Configuring BetterAuth Server - const client = new Polar({ - accessToken: process.env.POLAR_ACCESS_TOKEN, - // Use 'sandbox' if you're using the Polar Sandbox environment - // Remember that access tokens, products, etc. are completely separated between environments. - // Access tokens obtained in Production are for instance not usable in the Sandbox environment. - server: 'production' - }); +The Polar plugin comes with a handful additional plugins which adds functionality to your stack. - const auth = betterAuth({ - // ... Better Auth config - plugins: [ - polar({ - client, - // Enable automatic Polar Customer creation on signup - createCustomerOnSignUp: true, - // Enable customer portal - enableCustomerPortal: true, // Deployed under /portal for authenticated users - // Configure checkout - checkout: { - enabled: true, - products: [ - { - productId: "123-456-789", // ID of Product from Polar Dashboard - slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro - } - ], - successUrl: "/success?checkout_id={CHECKOUT_ID}" - }, - // Incoming Webhooks handler will be installed at /polar/webhooks - webhooks: { - secret: process.env.POLAR_WEBHOOK_SECRET, - onPayload: ..., - } +- Checkout - Enables a seamless checkout integration +- Portal - Makes it possible for your customers to manage their orders, subscriptions & granted benefits +- Usage - Simple extension for listing customer meters & ingesting events for Usage Based Billing +- Webhooks - Listen for relevant Polar webhooks + +```typescript +import { betterAuth } from "better-auth"; +import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth"; +import { Polar } from "@polar-sh/sdk"; + +const polarClient = new Polar({ + accessToken: process.env.POLAR_ACCESS_TOKEN, + // Use 'sandbox' if you're using the Polar Sandbox environment + // Remember that access tokens, products, etc. are completely separated between environments. + // Access tokens obtained in Production are for instance not usable in the Sandbox environment. + server: 'sandbox' +}); + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + client: polarClient, + createCustomerOnSignUp: true, + use: [ + checkout({ + products: [ + { + productId: "123-456-789", // ID of Product from Polar Dashboard + slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro + } + ], + successUrl: "/success?checkout_id={CHECKOUT_ID}", + authenticatedUsersOnly: true + }), + portal(), + usage(), + webhooks({ + secret: process.env.POLAR_WEBHOOK_SECRET, + onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes + onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) + ... // Over 25 granular webhook handlers + onPayload: (payload) => // Catch-all for all events }) - ] - }); - ``` - + ], + }) + ] +}); +``` - +### Configuring BetterAuth Client - - There is no client configuration required. You don't need to pass any - configuration options to the `createAuthClient` function. - +You will be using the BetterAuth Client to interact with the Polar functionalities. -For configuration options and more information, check out [Polar's Better Auth docs](https://docs.polar.sh/integrate/sdk/adapters/better-auth#configuration-options). +```typescript +import { createAuthClient } from "better-auth/react"; +import { polarClient } from "@polar-sh/better-auth"; +import { organizationClient } from "better-auth/client/plugins"; + +// This is all that is needed +// All Polar plugins, etc. should be attached to the server-side BetterAuth config +export const authClient = createAuthClient({ + plugins: [polarClient()], +}); +``` + +## Configuration Options + +```typescript +import { betterAuth } from "better-auth"; +import { + polar, + checkout, + portal, + usage, + webhooks, +} from "@polar-sh/better-auth"; +import { Polar } from "@polar-sh/sdk"; + +const polarClient = new Polar({ + accessToken: process.env.POLAR_ACCESS_TOKEN, + // Use 'sandbox' if you're using the Polar Sandbox environment + // Remember that access tokens, products, etc. are completely separated between environments. + // Access tokens obtained in Production are for instance not usable in the Sandbox environment. + server: "sandbox", +}); + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + client: polarClient, + createCustomerOnSignUp: true, + getCustomerCreateParams: ({ user, session }, request) => ({ + myCustomProperty: 123, + }), + use: [ + // This is where you add Polar plugins + ], + }), + ], +}); +``` + +### Required Options + +- `client`: Polar SDK client instance + +### Optional Options + +- `createCustomerOnSignUp`: Automatically create a Polar customer when a user signs up +- `getCustomerCreateParams`: Custom function to provide additional customer creation metadata + +### Customers + +When `createCustomerOnSignUp` is enabled, a new Polar Customer is automatically created when a new User is added in the Better-Auth Database. + +All new customers are created with an associated `externalId`, which is the ID of your User in the Database. This allows us to skip any Polar <-> User mapping in your Database. + +## Checkout Plugin + +To support checkouts in your app, simply pass the Checkout plugin to the use-property. + +```typescript +import { polar, checkout } from "@polar-sh/better-auth"; + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + ... + use: [ + checkout({ + // Optional field - will make it possible to pass a slug to checkout instead of Product ID + products: [ { productId: "123-456-789", slug: "pro" } ], + // Relative URL to return to when checkout is successfully completed + successUrl: "/success?checkout_id={CHECKOUT_ID}", + // Wheather you want to allow unauthenticated checkout sessions or not + authenticatedUsersOnly: true + }) + ], + }) + ] +}); +``` + +When checkouts are enabled, you're able to initialize Checkout Sessions using the checkout-method on the BetterAuth Client. This will redirect the user to the Product Checkout. + +```typescript +await authClient.checkout({ + // Any Polar Product ID can be passed here + products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], + // Or, if you setup "products" in the Checkout Config, you can pass the slug + slug: "pro", +}); +``` + +Checkouts will automatically carry the authenticated User as the customer to the checkout. Email-address will be "locked-in". + +If `authenticatedUsersOnly` is `false` - then it will be possible to trigger checkout sessions without any associated customer. + +### Organization Support + +This plugin supports the Organization plugin. If you pass the organization ID to the Checkout referenceId, you will be able to keep track of purchases made from organization members. + +```typescript +const organizationId = (await authClient.organization.list())?.data?.[0]?.id, + +await authClient.checkout({ + // Any Polar Product ID can be passed here + products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], + // Or, if you setup "products" in the Checkout Config, you can pass the slug + slug: 'my-pro-product', + // Reference ID will be saved as `referenceId` in the metadata of the checkout, order & subscription object + referenceId: organizationId +}); +``` + +## Portal Plugin + +A plugin which enables customer management of their purchases, orders and subscriptions. + +```typescript +import { polar, checkout, portal } from "@polar-sh/better-auth"; + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + ... + use: [ + checkout(...), + portal() + ], + }) + ] +}); +``` + +The portal-plugin gives the BetterAuth Client a set of customer management methods, scoped under `authClient.customer`. + +### Customer Portal Management + +The following method will redirect the user to the Polar Customer Portal, where they can see orders, purchases, subscriptions, benefits, etc. + +```typescript +await authClient.customer.portal(); +``` + +### Customer State + +The portal plugin also adds a convenient state-method for retrieving the general Customer State. + +```typescript +const { data: customerState } = await authClient.customer.state(); +``` + +The customer state object contains: + +- All the data about the customer. +- The list of their active subscriptions + - Note: This does not include subscriptions done by a parent organization. See the subscription list-method below for more information. +- The list of their granted benefits. +- The list of their active meters, with their current balance. + +Thus, with that single object, you have all the required information to check if you should provision access to your service or not. + +[You can learn more about the Polar Customer State in the Polar Docs](https://docs.polar.sh/integrate/customer-state). + +### Benefits, Orders & Subscriptions + +The portal plugin adds 3 convenient methods for listing benefits, orders & subscriptions relevant to the authenticated user/customer. + +[All of these methods use the Polar CustomerPortal APIs](https://docs.polar.sh/api-reference/customer-portal) + +#### Benefits + +This method only lists granted benefits for the authenticated user/customer. + +```typescript +const { data: benefits } = await authClient.customer.benefits.list({ + query: { + page: 1, + limit: 10, + }, +}); +``` + +#### Orders + +This method lists orders like purchases and subscription renewals for the authenticated user/customer. + +```typescript +const { data: orders } = await authClient.customer.orders.list({ + query: { + page: 1, + limit: 10, + productBillingType: "one_time", // or 'recurring' + }, +}); +``` + +#### Subscriptions + +This method lists the subscriptions associated with authenticated user/customer. + +```typescript +const { data: subscriptions } = await authClient.customer.orders.list({ + query: { + page: 1, + limit: 10, + active: true, + }, +}); +``` + +**Important** - Organization Support + +This will **not** return subscriptions made by a parent organization to the authenticated user. + +However, you can pass a `referenceId` to this method. This will return all subscriptions associated with that referenceId instead of subscriptions associated with the user. + +So in order to figure out if a user should have access, pass the user's organization ID to see if there is an active subscription for that organization. + +```typescript +const organizationId = (await authClient.organization.list())?.data?.[0]?.id, + +const { data: subscriptions } = await authClient.customer.orders.list({ + query: { + page: 1, + limit: 10, + active: true, + referenceId: organizationId + }, +}); + +const userShouldHaveAccess = subscriptions.some( + sub => // Your logic to check subscription product or whatever. +) +``` + +## Usage Plugin + +A simple plugin for Usage Based Billing. + +```typescript +import { polar, checkout, portal, usage } from "@polar-sh/better-auth"; + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + ... + use: [ + checkout(...), + portal(), + usage() + ], + }) + ] +}); +``` + +### Event Ingestion + +Polar's Usage Based Billing builds entirely on event ingestion. Ingest events from your application, create Meters to represent that usage, and add metered prices to Products to charge for it. + +[Learn more about Usage Based Billing in the Polar Docs.](https://docs.polar.sh/features/usage-based-billing/introduction) + +```typescript +const { data: ingested } = await authClient.usage.ingest({ + event: "file-uploads", + metadata: { + uploadedFiles: 12, + }, +}); +``` + +The authenticated user is automatically associated with the ingested event. + +### Customer Meters + +A simple method for listing the authenticated user's Usage Meters, or as we call them, Customer Meters. + +Customer Meter's contains all information about their consumtion on your defined meters. + +- Customer Information +- Meter Information +- Customer Meter Information + - Consumed Units + - Credited Units + - Balance + +```typescript +const { data: customerMeters } = await authClient.usage.meters.list({ + query: { + page: 1, + limit: 10, + }, +}); +``` + +## Webhooks Plugin + +The Webhooks plugin can be used to capture incoming events from your Polar organization. + +```typescript +import { polar, webhooks } from "@polar-sh/better-auth"; + +const auth = betterAuth({ + // ... Better Auth config + plugins: [ + polar({ + ... + use: [ + webhooks({ + secret: process.env.POLAR_WEBHOOK_SECRET, + onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes + onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) + ... // Over 25 granular webhook handlers + onPayload: (payload) => // Catch-all for all events + }) + ], + }) + ] +}); +``` + +Configure a Webhook endpoint in your Polar Organization Settings page. Webhook endpoint is configured at /polar/webhooks. + +Add the secret to your environment. + +```bash +# .env +POLAR_WEBHOOK_SECRET=... +``` + +The plugin supports handlers for all Polar webhook events: + +- `onPayload` - Catch-all handler for any incoming Webhook event +- `onCheckoutCreated` - Triggered when a checkout is created +- `onCheckoutUpdated` - Triggered when a checkout is updated +- `onOrderCreated` - Triggered when an order is created +- `onOrderPaid` - Triggered when an order is paid +- `onOrderRefunded` - Triggered when an order is refunded +- `onRefundCreated` - Triggered when a refund is created +- `onRefundUpdated` - Triggered when a refund is updated +- `onSubscriptionCreated` - Triggered when a subscription is created +- `onSubscriptionUpdated` - Triggered when a subscription is updated +- `onSubscriptionActive` - Triggered when a subscription becomes active +- `onSubscriptionCanceled` - Triggered when a subscription is canceled +- `onSubscriptionRevoked` - Triggered when a subscription is revoked +- `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed +- `onProductCreated` - Triggered when a product is created +- `onProductUpdated` - Triggered when a product is updated +- `onOrganizationUpdated` - Triggered when an organization is updated +- `onBenefitCreated` - Triggered when a benefit is created +- `onBenefitUpdated` - Triggered when a benefit is updated +- `onBenefitGrantCreated` - Triggered when a benefit grant is created +- `onBenefitGrantUpdated` - Triggered when a benefit grant is updated +- `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked +- `onCustomerCreated` - Triggered when a customer is created +- `onCustomerUpdated` - Triggered when a customer is updated +- `onCustomerDeleted` - Triggered when a customer is deleted +- `onCustomerStateChanged` - Triggered when a customer is created + + +For configuration options and more information, check out [Polar's Better Auth docs](https://docs.polar.sh/integrate/sdk/adapters/better-auth).