@polar-sh/better-auth 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,8 +6,10 @@ A [Better Auth](https://github.com/better-auth/better-auth) plugin for integrati
6
6
 
7
7
  - Checkout Integration
8
8
  - Customer Portal
9
- - Webhook handling
10
9
  - Automatic Customer creation on signup
10
+ - Event Ingestion & Customer Meters for flexible Usage Based Billing
11
+ - Handle Polar Webhooks securely with signature verification
12
+ - Reference System to associate purchases with organizations
11
13
 
12
14
  ## Installation
13
15
 
@@ -15,7 +17,7 @@ A [Better Auth](https://github.com/better-auth/better-auth) plugin for integrati
15
17
  pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk
16
18
  ```
17
19
 
18
- ### Preparation
20
+ ## Preparation
19
21
 
20
22
  Go to your Polar Organization Settings, and create an Organization Access Token. Add it to your environment.
21
23
 
@@ -24,52 +26,108 @@ Go to your Polar Organization Settings, and create an Organization Access Token.
24
26
  POLAR_ACCESS_TOKEN=...
25
27
  ```
26
28
 
29
+ ### Configuring BetterAuth Server
30
+
31
+ The Polar plugin comes with a handful additional plugins which adds functionality to your stack.
32
+
33
+ - Checkout - Enables a seamless checkout integration
34
+ - Portal - Makes it possible for your customers to manage their orders, subscriptions & granted benefits
35
+ - Usage - Simple extension for listing customer meters & ingesting events for Usage Based Billing
36
+ - Webhooks - Listen for relevant Polar webhooks
37
+
27
38
  ```typescript
28
39
  import { betterAuth } from "better-auth";
29
- import { polar } from "@polar-sh/better-auth";
40
+ import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth";
30
41
  import { Polar } from "@polar-sh/sdk";
31
42
 
32
- const client = new Polar({
43
+ const polarClient = new Polar({
33
44
  accessToken: process.env.POLAR_ACCESS_TOKEN,
34
45
  // Use 'sandbox' if you're using the Polar Sandbox environment
35
46
  // Remember that access tokens, products, etc. are completely separated between environments.
36
47
  // Access tokens obtained in Production are for instance not usable in the Sandbox environment.
37
- server: 'production'
48
+ server: 'sandbox'
38
49
  });
39
50
 
40
51
  const auth = betterAuth({
41
52
  // ... Better Auth config
42
53
  plugins: [
43
54
  polar({
44
- client,
45
- // Enable automatic Polar Customer creation on signup
55
+ client: polarClient,
46
56
  createCustomerOnSignUp: true,
47
- // Enable customer portal
48
- enableCustomerPortal: true, // Deployed under /portal for authenticated users
49
- // Configure checkout
50
- checkout: {
51
- enabled: true,
52
- products: [
53
- {
54
- productId: "123-456-789", // ID of Product from Polar Dashboard
55
- slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro
56
- }
57
- ],
58
- successUrl: "/success?checkout_id={CHECKOUT_ID}",
59
- authenticatedUsersOnly: true
60
- },
61
- // Incoming Webhooks handler will be installed at /polar/webhooks
62
- webhooks: {
63
- secret: process.env.POLAR_WEBHOOK_SECRET,
64
- onPayload: ...,
65
- }
57
+ use: [
58
+ checkout({
59
+ products: [
60
+ {
61
+ productId: "123-456-789", // ID of Product from Polar Dashboard
62
+ slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro
63
+ }
64
+ ],
65
+ successUrl: "/success?checkout_id={CHECKOUT_ID}",
66
+ authenticatedUsersOnly: true
67
+ }),
68
+ portal(),
69
+ usage(),
70
+ webhooks({
71
+ secret: process.env.POLAR_WEBHOOK_SECRET,
72
+ onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes
73
+ onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.)
74
+ ... // Over 25 granular webhook handlers
75
+ onPayload: (payload) => // Catch-all for all events
76
+ })
77
+ ],
66
78
  })
67
79
  ]
68
80
  });
69
81
  ```
70
82
 
83
+ ### Configuring BetterAuth Client
84
+
85
+ You will be using the BetterAuth Client to interact with the Polar functionalities.
86
+
87
+ ```typescript
88
+ import { createAuthClient } from "better-auth/react";
89
+ import { polarClient } from "@polar-sh/better-auth";
90
+ import { organizationClient } from "better-auth/client/plugins";
91
+
92
+ // This is all that is needed
93
+ // All Polar plugins, etc. should be attached to the server-side BetterAuth config
94
+ export const authClient = createAuthClient({
95
+ plugins: [polarClient()],
96
+ });
97
+ ```
98
+
71
99
  ## Configuration Options
72
100
 
101
+ ```typescript
102
+ import { betterAuth } from "better-auth";
103
+ import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth";
104
+ import { Polar } from "@polar-sh/sdk";
105
+
106
+ const polarClient = new Polar({
107
+ accessToken: process.env.POLAR_ACCESS_TOKEN,
108
+ // Use 'sandbox' if you're using the Polar Sandbox environment
109
+ // Remember that access tokens, products, etc. are completely separated between environments.
110
+ // Access tokens obtained in Production are for instance not usable in the Sandbox environment.
111
+ server: 'sandbox'
112
+ });
113
+
114
+ const auth = betterAuth({
115
+ // ... Better Auth config
116
+ plugins: [
117
+ polar({
118
+ client: polarClient,
119
+ createCustomerOnSignUp: true,
120
+ getCustomerCreateParams?: ({ user, session }, request) => ({
121
+ myCustomProperty: 123
122
+ }),
123
+ use: [
124
+ // This is where you add Polar plugins
125
+ ]
126
+ })
127
+ ]
128
+ })
129
+ ```
130
+
73
131
  ### Required Options
74
132
 
75
133
  - `client`: Polar SDK client instance
@@ -77,14 +135,282 @@ const auth = betterAuth({
77
135
  ### Optional Options
78
136
 
79
137
  - `createCustomerOnSignUp`: Automatically create a Polar customer when a user signs up
80
- - `getCustomerCreateParams`: Custom function to provide additional customer creation parameters
81
- - `enableCustomerPortal`: Enable the customer portal functionality. Deployed as GET endpoint at /portal
82
- - `checkout.enabled`: Enable checkout functionality. Deployed as GET endpoint at /checkout
83
- - `checkout.products`: Array of products or function returning products. Slug passed will then be passable as route param.
84
- - `checkout.successUrl`: URL to redirect to after successful checkout
85
- - `checkout.authenticatedUsersOnly`: Only allow authenticated users to access the checkout
138
+ - `getCustomerCreateParams`: Custom function to provide additional customer creation metadata
139
+
140
+ ### Customers
141
+
142
+ When `createCustomerOnSignUp` is enabled, a new Polar Customer is automatically created when a new User is added in the Better-Auth Database.
143
+
144
+ 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.
145
+
146
+ ## Checkout Plugin
147
+
148
+ To support checkouts in your app, simply pass the Checkout plugin to the use-property.
149
+
150
+ ```typescript
151
+ import { polar, checkout } from "@polar-sh/better-auth";
152
+
153
+ const auth = betterAuth({
154
+ // ... Better Auth config
155
+ plugins: [
156
+ polar({
157
+ ...
158
+ use: [
159
+ checkout({
160
+ // Optional field - will make it possible to pass a slug to checkout instead of Product ID
161
+ products: [ { productId: "123-456-789", slug: "pro" } ],
162
+ // Relative URL to return to when checkout is successfully completed
163
+ successUrl: "/success?checkout_id={CHECKOUT_ID}",
164
+ // Wheather you want to allow unauthenticated checkout sessions or not
165
+ authenticatedUsersOnly: true
166
+ })
167
+ ],
168
+ })
169
+ ]
170
+ });
171
+ ```
172
+
173
+ 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.
174
+
175
+ ```typescript
176
+ await authClient.checkout({
177
+ // Any Polar Product ID can be passed here
178
+ products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
179
+ // Or, if you setup "products" in the Checkout Config, you can pass the slug
180
+ slug: "pro",
181
+ });
182
+ ```
183
+
184
+ Checkouts will automatically carry the authenticated User as the customer to the checkout. Email-address will be "locked-in".
185
+
186
+ If `authenticatedUsersOnly` is `false` - then it will be possible to trigger checkout sessions without any associated customer.
187
+
188
+ ### Organization Support
189
+
190
+ 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.
191
+
192
+ ```typescript
193
+ const organizationId = (await authClient.organization.list())?.data?.[0]?.id,
194
+
195
+ await authClient.checkout({
196
+ // Any Polar Product ID can be passed here
197
+ products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
198
+ // Or, if you setup "products" in the Checkout Config, you can pass the slug
199
+ slug: 'my-pro-product',
200
+ // Reference ID will be saved as `referenceId` in the metadata of the checkout, order & subscription object
201
+ referenceId: organizationId
202
+ });
203
+ ```
204
+
205
+ ## Portal Plugin
206
+
207
+ A plugin which enables customer management of their purchases, orders and subscriptions.
208
+
209
+ ```typescript
210
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
211
+
212
+ const auth = betterAuth({
213
+ // ... Better Auth config
214
+ plugins: [
215
+ polar({
216
+ ...
217
+ use: [
218
+ checkout(...),
219
+ portal()
220
+ ],
221
+ })
222
+ ]
223
+ });
224
+ ```
225
+
226
+ The portal-plugin gives the BetterAuth Client a set of customer management methods, scoped under `authClient.customer`.
227
+
228
+ ### Customer Portal Management
229
+
230
+ The following method will redirect the user to the Polar Customer Portal, where they can see orders, purchases, subscriptions, benefits, etc.
231
+
232
+ ```typescript
233
+ await authClient.customer.portal();
234
+ ```
235
+
236
+ ### Customer State
237
+
238
+ The portal plugin also adds a convenient state-method for retrieving the general Customer State.
239
+
240
+ ```typescript
241
+ const { data: customerState } = await authClient.customer.state();
242
+ ```
243
+
244
+ The customer state object contains:
245
+
246
+ - All the data about the customer.
247
+ - The list of their active subscriptions
248
+ - Note: This does not include subscriptions done by a parent organization. See the subscription list-method below for more information.
249
+ - The list of their granted benefits.
250
+ - The list of their active meters, with their current balance.
251
+
252
+ Thus, with that single object, you have all the required information to check if you should provision access to your service or not.
253
+
254
+ [You can learn more about the Polar Customer State in the Polar Docs](https://docs.polar.sh/integrate/customer-state).
86
255
 
87
- ### Webhook Handlers
256
+ ### Benefits, Orders & Subscriptions
257
+
258
+ The portal plugin adds 3 convenient methods for listing benefits, orders & subscriptions relevant to the authenticated user/customer.
259
+
260
+ [All of these methods use the Polar CustomerPortal APIs](https://docs.polar.sh/api-reference/customer-portal)
261
+
262
+ #### Benefits
263
+
264
+ This method only lists granted benefits for the authenticated user/customer.
265
+
266
+ ```typescript
267
+ const { data: benefits } = await authClient.customer.benefits.list({
268
+ query: {
269
+ page: 1,
270
+ limit: 10,
271
+ },
272
+ });
273
+ ```
274
+
275
+ #### Orders
276
+
277
+ This method lists orders like purchases and subscription renewals for the authenticated user/customer.
278
+
279
+ ```typescript
280
+ const { data: orders } = await authClient.customer.orders.list({
281
+ query: {
282
+ page: 1,
283
+ limit: 10,
284
+ productBillingType: "one_time", // or 'recurring'
285
+ },
286
+ });
287
+ ```
288
+
289
+ #### Subscriptions
290
+
291
+ This method lists the subscriptions associated with authenticated user/customer.
292
+
293
+ ```typescript
294
+ const { data: subscriptions } = await authClient.customer.orders.list({
295
+ query: {
296
+ page: 1,
297
+ limit: 10,
298
+ active: true,
299
+ },
300
+ });
301
+ ```
302
+
303
+ **Important** - Organization Support
304
+
305
+ This will **not** return subscriptions made by a parent organization to the authenticated user.
306
+
307
+ 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.
308
+
309
+ 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.
310
+
311
+ ```typescript
312
+ const organizationId = (await authClient.organization.list())?.data?.[0]?.id,
313
+
314
+ const { data: subscriptions } = await authClient.customer.orders.list({
315
+ query: {
316
+ page: 1,
317
+ limit: 10,
318
+ active: true,
319
+ referenceId: organizationId
320
+ },
321
+ });
322
+
323
+ const userShouldHaveAccess = subscriptions.some(
324
+ sub => // Your logic to check subscription product or whatever.
325
+ )
326
+ ```
327
+
328
+ ## Usage Plugin
329
+
330
+ A simple plugin for Usage Based Billing.
331
+
332
+ ```typescript
333
+ import { polar, checkout, portal, usage } from "@polar-sh/better-auth";
334
+
335
+ const auth = betterAuth({
336
+ // ... Better Auth config
337
+ plugins: [
338
+ polar({
339
+ ...
340
+ use: [
341
+ checkout(...),
342
+ portal(),
343
+ usage()
344
+ ],
345
+ })
346
+ ]
347
+ });
348
+ ```
349
+
350
+ ### Event Ingestion
351
+
352
+ 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.
353
+
354
+ [Learn more about Usage Based Billing in the Polar Docs.](https://docs.polar.sh/features/usage-based-billing/introduction)
355
+
356
+ ```typescript
357
+ const { data: ingested } = await authClient.usage.ingest({
358
+ event: "file-uploads",
359
+ metadata: {
360
+ uploadedFiles: 12,
361
+ },
362
+ });
363
+ ```
364
+
365
+ The authenticated user is automatically associated with the ingested event.
366
+
367
+ ### Customer Meters
368
+
369
+ A simple method for listing the authenticated user's Usage Meters, or as we call them, Customer Meters.
370
+
371
+ Customer Meter's contains all information about their consumtion on your defined meters.
372
+
373
+ - Customer Information
374
+ - Meter Information
375
+ - Customer Meter Information
376
+ - Consumed Units
377
+ - Credited Units
378
+ - Balance
379
+
380
+ ```typescript
381
+ const { data: customerMeters } = await authClient.usage.meters.list({
382
+ query: {
383
+ page: 1,
384
+ limit: 10,
385
+ },
386
+ });
387
+ ```
388
+
389
+ ## Webhooks Plugin
390
+
391
+ The Webhooks plugin can be used to capture incoming events from your Polar organization.
392
+
393
+ ```typescript
394
+ import { polar, webhooks } from "@polar-sh/better-auth";
395
+
396
+ const auth = betterAuth({
397
+ // ... Better Auth config
398
+ plugins: [
399
+ polar({
400
+ ...
401
+ use: [
402
+ webhooks({
403
+ secret: process.env.POLAR_WEBHOOK_SECRET,
404
+ onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes
405
+ onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.)
406
+ ... // Over 25 granular webhook handlers
407
+ onPayload: (payload) => // Catch-all for all events
408
+ })
409
+ ],
410
+ })
411
+ ]
412
+ });
413
+ ```
88
414
 
89
415
  Configure a Webhook endpoint in your Polar Organization Settings page. Webhook endpoint is configured at /polar/webhooks.
90
416
 
@@ -123,34 +449,3 @@ The plugin supports handlers for all Polar webhook events:
123
449
  - `onCustomerUpdated` - Triggered when a customer is updated
124
450
  - `onCustomerDeleted` - Triggered when a customer is deleted
125
451
  - `onCustomerStateChanged` - Triggered when a customer is created
126
-
127
- ## API Routes
128
-
129
- The plugin adds the following API routes:
130
-
131
- - `GET /checkout/:slug` - Redirect to Polar checkout
132
- - `GET /state` - Customer state (Customer Data, Active Subscriptions, Entitlements, etc.) for the authenticated user
133
- - `GET /portal` - Redirects to Polar Customer Portal for authenticated user
134
- - `POST /polar/webhooks` - Incoming webhooks are automatically parsed & validated
135
-
136
- ## Customers
137
-
138
- When `createCustomerOnSignUp` is enabled, a new Polar Customer is automatically created when a new User is added in the Better-Auth Database.
139
-
140
- 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.
141
-
142
- ## Checkouts
143
-
144
- When checkouts are enabled, you're able to initialize Checkout Sessions on the `/checkout/:slug` route.
145
-
146
- ### Checkouts with slug
147
-
148
- If you pass an array of products to the configuration, you're able to use the slug as a reference instead of using the product id.
149
-
150
- ### Checkouts with products
151
-
152
- All your organization products are eligible for checkouts, even if they're not passed to the products-configuration.
153
-
154
- You can initialize a checkout session like following - `/checkout?products=123-456-789`
155
-
156
- You can pass multiple products like - `/checkout?products=123-456-789&products=987-654-321`