@raideno/convex-stripe 0.2.8 → 0.3.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.
Files changed (2) hide show
  1. package/README.md +675 -172
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,22 +1,73 @@
1
1
  # Convex Stripe
2
2
 
3
- Stripe [syncing](./references/tables.md), subscriptions, [checkouts](#-checkout-action) and stripe connect for Convex apps. Implemented according to the best practices listed in [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations).
3
+ Stripe [syncing](./documentation/references/tables.md), subscriptions, [checkouts](#stripesubscribe), one time payments, billing portal and Stripe Connect for Convex apps. Implemented according to the best practices listed in [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations).
4
+
5
+ ## Table of Contents
6
+
7
+ - [Convex Stripe](#convex-stripe)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [1. Set up Stripe](#1-set-up-stripe)
12
+ - [2. Set Environment Variables on Convex](#2-set-environment-variables-on-convex)
13
+ - [3. Add Tables](#3-add-tables)
14
+ - [4. Initialize the Library](#4-initialize-the-library)
15
+ - [5. Register HTTP Routes](#5-register-http-routes)
16
+ - [6. Stripe Customers](#6-stripe-customers)
17
+ - [7. Run the `sync` Action](#7-run-the-sync-action)
18
+ - [8. Start Building](#8-start-building)
19
+ - [Configuration](#configuration)
20
+ - [Stripe Configuration](#stripe-configuration)
21
+ - [Sync Configuration](#sync-configuration)
22
+ - [Table Selection](#table-selection)
23
+ - [Catalog (Unstable)](#catalog-unstable)
24
+ - [Webhook Configuration](#webhook-configuration)
25
+ - [Portal Configuration](#portal-configuration)
26
+ - [Callbacks](#callbacks)
27
+ - [Custom Webhook Handlers](#custom-webhook-handlers)
28
+ - [Custom Redirect Handlers](#custom-redirect-handlers)
29
+ - [`buildSignedReturnUrl`](#buildsignedreturnurl)
30
+ - [Options](#options)
31
+ - [Stripe Connect](#stripe-connect)
32
+ - [0. Enable Connect on your Stripe dashboard.](#0-enable-connect-on-your-stripe-dashboard)
33
+ - [1. Create a Connect webhook using the `sync` action.](#1-create-a-connect-webhook-using-the-sync-action)
34
+ - [2. Add the new webhook secret to your Convex environment and configuration.](#2-add-the-new-webhook-secret-to-your-convex-environment-and-configuration)
35
+ - [3. Create Stripe Accounts for Sellers \& Onboard them](#3-create-stripe-accounts-for-sellers--onboard-them)
36
+ - [4. Create Products for Sellers](#4-create-products-for-sellers)
37
+ - [5. Send Payouts](#5-send-payouts)
38
+ - [API Reference](#api-reference)
39
+ - [`stripe.customers.create`](#stripecustomerscreate)
40
+ - [`stripe.subscribe`](#stripesubscribe)
41
+ - [`stripe.pay`](#stripepay)
42
+ - [`stripe.portal`](#stripeportal)
43
+ - [`stripe.accounts.create`](#stripeaccountscreate)
44
+ - [`stripe.accounts.link`](#stripeaccountslink)
45
+ - [`stripe.addHttpRoutes`](#stripeaddhttproutes)
46
+ - [`stripe.client`](#stripeclient)
47
+ - [`sync` Action](#sync-action)
48
+ - [`store` Mutation](#store-mutation)
49
+ - [Synced Tables](#synced-tables)
50
+ - [Synced Events](#synced-events)
51
+ - [Best Practices](#best-practices)
52
+ - [Resources](#resources)
53
+ - [Development](#development)
54
+ - [Contributions](#contributions)
4
55
 
5
56
  ## Installation
6
57
 
7
- ```sh [npm]
58
+ ```sh
8
59
  npm install @raideno/convex-stripe stripe
9
60
  ```
10
61
 
11
62
  ## Usage
12
63
 
13
- ### 1. Set up Stripe
14
- - Create a Stripe account.
15
- - Configure a webhook pointing to:
64
+ ### 1. Set up Stripe
65
+ - Create a Stripe account.
66
+ - Configure a webhook endpoint pointing to:
16
67
  ```
17
68
  https://<your-convex-app>.convex.site/stripe/webhook
18
69
  ```
19
- - Enable the following [Stripe Events](./references/events.md).
70
+ - Enable the required [Stripe Events](#synced-events) on the webhook.
20
71
  - Enable the [Stripe Billing Portal](https://dashboard.stripe.com/test/settings/billing/portal).
21
72
 
22
73
  ### 2. Set Environment Variables on Convex
@@ -26,11 +77,19 @@ npx convex env set STRIPE_SECRET_KEY "<secret>"
26
77
  npx convex env set STRIPE_ACCOUNT_WEBHOOK_SECRET "<secret>"
27
78
  ```
28
79
 
29
- ### 3. Add tables.
80
+ If you plan to use Stripe Connect, also set:
30
81
 
31
- Check [Tables Schemas](./references/tables.md) to know more about the synced tables.
82
+ ```bash
83
+ npx convex env set STRIPE_CONNECT_WEBHOOK_SECRET "<secret>"
84
+ ```
85
+
86
+ ### 3. Add Tables
87
+
88
+ Spread the `stripeTables` export into your Convex schema. This creates all the [synced tables](#synced-tables) that the library uses to mirror Stripe data locally.
89
+
90
+ ```ts
91
+ // convex/schema.ts
32
92
 
33
- ```ts [convex/schema.ts]
34
93
  import { defineSchema } from "convex/server";
35
94
  import { stripeTables } from "@raideno/convex-stripe/server";
36
95
 
@@ -40,7 +99,11 @@ export default defineSchema({
40
99
  });
41
100
  ```
42
101
 
43
- ### 4. Initialize the library
102
+ See [Tables Reference](./documentation/references/tables.md) for the full list of tables and their schemas.
103
+
104
+ ### 4. Initialize the Library
105
+
106
+ Call `internalConvexStripe` with your Stripe credentials and sync configuration. This returns a `stripe` object with all the action functions, a `store` internal mutation and a `sync` internal action.
44
107
 
45
108
  ```ts
46
109
  // convex/stripe.ts
@@ -56,26 +119,18 @@ export const { stripe, store, sync } = internalConvexStripe({
56
119
  tables: syncAllTables(),
57
120
  },
58
121
  });
122
+ ```
59
123
 
60
- export const createCustomer = internalAction({
61
- args: {
62
- email: v.optional(v.string()),
63
- entityId: v.string(),
64
- },
65
- handler: async (context, args) => {
66
- return stripe.customers.create(context, {
67
- email: args.email,
68
- entityId: args.entityId,
69
- });
70
- },
71
- });
124
+ > **Note:** All exposed actions (`store`, `sync`) are **internal**. They can only be called from other Convex functions. Wrap them in public actions when needed.
72
125
 
73
- ```
126
+ > **Important:** `store` must always be exported from the same file, as it is used internally by the library to persist webhook data.
74
127
 
75
- > **Note:** All exposed actions (`store`, `sync`) are **internal**. Meaning they can only be called from other convex functions, you can wrap them in public actions when needed.
76
- > **Important:** `store` must always be exported, as it is used internally.
128
+ ### 5. Register HTTP Routes
77
129
 
78
- ### 5. Register HTTP routes
130
+ Register the Stripe webhook and redirect routes on your Convex HTTP router. This sets up two routes:
131
+
132
+ - `POST /stripe/webhook` to receive and verify Stripe webhook events.
133
+ - `GET /stripe/return/*` to handle post-checkout and post-portal redirect flows.
79
134
 
80
135
  ```ts
81
136
  // convex/http.ts
@@ -85,25 +140,21 @@ import { stripe } from "./stripe";
85
140
 
86
141
  const http = httpRouter();
87
142
 
88
- // registers POST /stripe/webhook
89
- // registers GET /stripe/return/*
90
143
  stripe.addHttpRoutes(http);
91
144
 
92
145
  export default http;
93
146
  ```
94
147
 
95
- ### 6. Stripe customers
96
-
97
- Ideally you want to create a stripe customer the moment a new entity (user, organization, etc) is created.
148
+ ### 6. Stripe Customers
98
149
 
99
- An `entityId` refers to something you are billing. It can be a user, organization or any other thing. With each entity must be associated a stripe customer and the stripe customer can be created using the [`stripe.customers.create` function](#stripecustomerscreate-function).
150
+ Create a Stripe customer the moment a new entity (user, organization, etc.) is created in your app. An `entityId` is your app's internal identifier for the thing you are billing. Each entity must be associated with exactly one Stripe customer.
100
151
 
101
- Below are with different auth providers examples where the user is the entity we are billing.
152
+ The customer can be created using `stripe.customers.create`. Below are examples using different auth providers, where the user is the entity being billed.
102
153
 
103
154
  ::: code-group
104
155
 
105
156
  ```ts [convex-auth]
106
- "convex/auth.ts"
157
+ // convex/auth.ts
107
158
 
108
159
  // example with convex-auth: https://labs.convex.dev/auth
109
160
 
@@ -117,9 +168,6 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
117
168
  // NOTE: create a customer immediately after a user is created
118
169
  afterUserCreatedOrUpdated: async (context, args) => {
119
170
  await context.scheduler.runAfter(0, internal.stripe.createCustomer, {
120
- /*
121
- * will call stripe.customers.create
122
- */
123
171
  entityId: args.userId,
124
172
  email: args.profile.email,
125
173
  });
@@ -129,7 +177,7 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
129
177
  ```
130
178
 
131
179
  ```ts [better-auth]
132
- "convex/auth.ts"
180
+ // convex/auth.ts
133
181
 
134
182
  // example with better-auth: https://convex-better-auth.netlify.app/
135
183
 
@@ -137,7 +185,7 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
137
185
  ```
138
186
 
139
187
  ```ts [clerk]
140
- "convex/auth.ts"
188
+ // convex/auth.ts
141
189
 
142
190
  // example with clerk: https://docs.convex.dev/auth/clerk
143
191
 
@@ -146,213 +194,471 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
146
194
 
147
195
  :::
148
196
 
149
- ### 7. Run `sync` action
197
+ When using the example above, you also need to export the `createCustomer` action from your Stripe file:
198
+
199
+ ```ts
200
+ // convex/stripe.ts
201
+
202
+ import { v } from "convex/values";
203
+ import { internalAction } from "./_generated/server";
204
+ import { stripe } from "./stripe";
205
+
206
+ export const createCustomer = internalAction({
207
+ args: {
208
+ email: v.optional(v.string()),
209
+ entityId: v.string(),
210
+ },
211
+ handler: async (context, args) => {
212
+ return stripe.customers.create(context, {
213
+ email: args.email,
214
+ entityId: args.entityId,
215
+ });
216
+ },
217
+ });
218
+ ```
219
+
220
+ ### 7. Run the `sync` Action
221
+
222
+ In your Convex project's dashboard, go to the **Functions** section and execute the `sync` action with `{ tables: true }`.
223
+
224
+ This syncs already existing Stripe data (products, prices, customers, subscriptions, etc.) into your Convex database. It must be done in both your development and production deployments after installing or updating the library.
150
225
 
151
- In your convex project's dashboard. Go the **Functions** section and execute the `sync` action.
226
+ This step is not necessary if you are starting with a fresh, empty Stripe account.
152
227
 
153
- This is done to sync already existing stripe data into your convex database.
154
- It must be done in both your development and production deployments after installing or updating the library.
228
+ ### 8. Start Building
155
229
 
156
- This might not be necessary if you are starting with a fresh empty stripe project.
230
+ With everything set up, you can now use the provided functions to:
157
231
 
158
- ### 8. Start building
232
+ - Create subscription checkout sessions: [`stripe.subscribe`](#stripesubscribe).
233
+ - Create one time payment checkout sessions: [`stripe.pay`](#stripepay).
234
+ - Open the Stripe Billing Portal for an entity: [`stripe.portal`](#stripeportal).
235
+ - Create Stripe Connect accounts: [`stripe.accounts.create`](#stripeaccountscreate).
236
+ - Generate onboarding links for connected accounts: [`stripe.accounts.link`](#stripeaccountslink).
237
+ - Query any of the [synced tables](#synced-tables) directly in your Convex functions.
159
238
 
160
- Now you can use the provided functions to:
161
- - Generate a subscription or payment link [`stripe.subscribe`](#subscribe-function), [`stripe.pay`](#pay-function) for a given entity.
162
- - Generate a link to the entity's [`stripe.portal`](#portal-function) to manage their subscriptions.
163
- - Create stripe connect accounts and link them, [`stripe.accounts.create`](#), [`stripe.accounts.link`](#).
164
- - Consult the [synced tables](documentation/references/tables.md).
165
- - Etc.
239
+ ## Configuration
166
240
 
167
- ## Add Stripe Connect
241
+ The `internalConvexStripe` function accepts a configuration object and an optional options object.
168
242
 
169
- If you wish to also add stripe connect, below is a guide on how to do it. You can find a full example in [`examples/marketplaces/README.md`](examples/marketplace/README.md).
243
+ ```ts
244
+ const { stripe, store, sync } = internalConvexStripe(configuration, options);
245
+ ```
170
246
 
171
- ### 0. Enable connect on your stripe dashboard.
172
- ...
247
+ ### Stripe Configuration
173
248
 
174
- ### 1. Create a connect webhook using `sync` method.
175
- ...
249
+ The `stripe` key in the configuration object holds your Stripe credentials and API settings.
176
250
 
177
- ### 2. Add the new webhook secret to the dashboard and configuration.
178
- ...
251
+ | Property | Type | Required | Description |
252
+ | :----------------------- | :------- | :------- | :-------------------------------------------------------------------------------------- |
253
+ | `secret_key` | `string` | Yes | Your Stripe secret key (starts with `sk_`). |
254
+ | `account_webhook_secret` | `string` | Yes | The webhook signing secret for account level webhooks (starts with `whsec_`). |
255
+ | `connect_webhook_secret` | `string` | No | The webhook signing secret for Stripe Connect webhooks. Required only if using Connect. |
256
+ | `version` | `string` | No | Stripe API version to pin against. Defaults to `2025-08-27.basil`. |
179
257
 
180
- ### 3. Create Stripe Accounts for Sellers & Onboard them
181
- ...
258
+ ### Sync Configuration
182
259
 
183
- ### 4. Create Products for Sellers
184
- ...
260
+ The `sync` key controls which tables are synced and allows you to define catalog items, webhook endpoints, and billing portal configuration.
185
261
 
186
- ### 5. Send payouts
187
- ...
262
+ #### Table Selection
188
263
 
189
- ## References
264
+ Three helper functions are provided to control which Stripe tables are synced into Convex:
190
265
 
191
- The library automatically syncs the [following tables](documentation/references/tables.md).
266
+ ```ts
267
+ import {
268
+ syncAllTables,
269
+ syncAllTablesExcept,
270
+ syncOnlyTables,
271
+ } from "@raideno/convex-stripe/server";
192
272
 
193
- You can query these tables at any time to:
273
+ // Sync all 24 tables (default)
274
+ { tables: syncAllTables() }
194
275
 
195
- - List available products/plans and prices.
196
- - Retrieve customers and their `customerId`.
197
- - Check active subscriptions.
198
- - Etc.
276
+ // Sync all tables except specific ones
277
+ { tables: syncAllTablesExcept(["stripeReviews", "stripePlans"]) }
199
278
 
279
+ // Sync only specific tables
280
+ { tables: syncOnlyTables(["stripeCustomers", "stripeSubscriptions", "stripeProducts", "stripePrices"]) }
281
+ ```
200
282
 
201
- ### `stripe.customers.create` Function
283
+ All 24 available tables are listed in the [Synced Tables](#synced-tables) section.
202
284
 
203
- Creates or updates a Stripe customer for a given entity (user or organization). Will call [`stripe.customers.create`](https://docs.stripe.com/api/customers/create) under the hood.
285
+ #### Catalog (Unstable)
204
286
 
205
- This should be called whenever a new entity is created in your app, or when you want to ensure the entity has a Stripe customer associated with it.
287
+ The `sync.catalog` key lets you pre-define products and prices that should exist in Stripe. When the `sync` action is called with `{ catalog: true }`, the library ensures these objects exist in your Stripe account.
206
288
 
207
289
  ```ts
208
- import { v } from "convex/values";
209
- import { stripe } from "./stripe";
210
- import { action, internal } from "./_generated/api";
290
+ internalConvexStripe({
291
+ // ...
292
+ sync: {
293
+ tables: syncAllTables(),
294
+ catalog: {
295
+ products: [
296
+ { name: "Pro Plan", metadata: { convex_stripe_key: "pro" } },
297
+ ],
298
+ prices: [
299
+ {
300
+ currency: "usd",
301
+ unit_amount: 1999,
302
+ recurring: { interval: "month" },
303
+ product_data: { name: "Pro Plan", metadata: { convex_stripe_key: "pro" } },
304
+ metadata: { convex_stripe_key: "pro-monthly" },
305
+ },
306
+ ],
307
+ metadataKey: "convex_stripe_key", // default
308
+ behavior: {
309
+ onExisting: "update", // "update" | "archive_and_recreate" | "skip" | "error"
310
+ onMissingKey: "create", // "create" | "error"
311
+ },
312
+ },
313
+ },
314
+ });
315
+ ```
211
316
 
212
- export const createCustomer = internalAction({
213
- args: {
214
- email: v.optional(v.string()),
215
- entityId: v.string(),
317
+ #### Webhook Configuration
318
+
319
+ The `sync.webhooks` key lets you customize the metadata and description of the webhook endpoints that the library creates when you call `sync` with `{ webhooks: { account: true } }` or `{ webhooks: { connect: true } }`.
320
+
321
+ ```ts
322
+ {
323
+ sync: {
324
+ webhooks: {
325
+ account: {
326
+ description: "My App - Account Webhook",
327
+ metadata: { app: "my-app" },
328
+ },
329
+ connect: {
330
+ description: "My App - Connect Webhook",
331
+ metadata: { app: "my-app" },
332
+ },
333
+ },
216
334
  },
217
- handler: async (context, args) => {
218
- return stripe.customers.create(context, {
219
- email: args.email,
220
- entityId: args.entityId,
221
- });
335
+ }
336
+ ```
337
+
338
+ #### Portal Configuration
339
+
340
+ The `sync.portal` key accepts a `Stripe.BillingPortal.ConfigurationCreateParams` object. When `sync` is called with `{ portal: true }`, the library creates the billing portal configuration if it does not already exist.
341
+
342
+ ### Callbacks
343
+
344
+ The `callbacks.afterChange` function is called every time a row is inserted, upserted, or deleted in any of the synced Stripe tables. This is useful for triggering side effects when Stripe data changes.
345
+
346
+ ```ts
347
+ internalConvexStripe({
348
+ // ...
349
+ callbacks: {
350
+ afterChange: async (context, operation, event) => {
351
+ // operation: "upsert" | "delete" | "insert"
352
+ // event.table: the name of the table that changed (e.g. "stripeSubscriptions")
353
+ console.log(`Stripe data changed: ${operation} on ${event.table}`);
354
+ },
222
355
  },
223
356
  });
224
357
  ```
225
358
 
226
- **Notes:**
359
+ ### Custom Webhook Handlers
227
360
 
228
- - `entityId` is your app’s internal ID (user/org).
229
- - `customerId` is stripe's internal ID.
230
- - `email` is optional, but recommended so the Stripe customer has a contact email.
231
- - If the entity already has a Stripe customer, `createEntity` will return the existing one instead of creating a duplicate.
232
- - Typically, you’ll call this automatically in your user/org creation flow (see [Configuration - 6](#configuration)).
361
+ You can register additional webhook handlers to react to specific Stripe events beyond the default syncing behavior. Use `defineWebhookHandler` to create a handler:
233
362
 
363
+ ```ts
364
+ import { defineWebhookHandler } from "@raideno/convex-stripe/server";
234
365
 
235
- ### `sync` Action
366
+ const myHandler = defineWebhookHandler({
367
+ events: ["invoice.payment_succeeded"],
368
+ handle: async (event, context, configuration, options) => {
369
+ // react to the event
370
+ },
371
+ });
236
372
 
237
- Synchronizes Stripe resources with your Convex database.
373
+ internalConvexStripe({
374
+ // ...
375
+ webhook: {
376
+ handlers: [myHandler],
377
+ },
378
+ });
379
+ ```
238
380
 
239
- This action is typically manually called or setup to be automatically called in your ci/cd pipeline.
381
+ ### Custom Redirect Handlers
240
382
 
241
- **Parameters:**
383
+ Redirect handlers let you run custom logic when a user is redirected back from Stripe (after a checkout, portal session, or account link flow). Use `defineRedirectHandler` to create one:
242
384
 
243
385
  ```ts
386
+ import { defineRedirectHandler } from "@raideno/convex-stripe/server";
387
+
388
+ const myRedirectHandler = defineRedirectHandler({
389
+ origins: ["subscribe-success", "pay-success"],
390
+ handle: async (origin, context, data, configuration, options) => {
391
+ // run custom logic after a successful payment or subscription
392
+ },
393
+ });
394
+
395
+ internalConvexStripe({
396
+ // ...
397
+ redirect: {
398
+ ttlMs: 15 * 60 * 1000, // default: 15 minutes
399
+ handlers: [myRedirectHandler],
400
+ },
401
+ });
402
+ ```
403
+
404
+ The available redirect origins are:
405
+
406
+ | Origin | Trigger |
407
+ | :---------------------------- | :------------------------------------------------ |
408
+ | `subscribe-success` | User completed a subscription checkout. |
409
+ | `subscribe-cancel` | User cancelled a subscription checkout. |
410
+ | `pay-success` | User completed a one time payment checkout. |
411
+ | `pay-cancel` | User cancelled a one time payment checkout. |
412
+ | `portal-return` | User returned from the billing portal. |
413
+ | `create-account-link-return` | Connected account completed onboarding. |
414
+ | `create-account-link-refresh` | Connected account link expired and needs refresh. |
415
+
416
+ #### `buildSignedReturnUrl`
417
+
418
+ The library also exports a `buildSignedReturnUrl` utility that you can use to manually build signed redirect URLs. This is the same function used internally by `stripe.subscribe`, `stripe.pay`, `stripe.portal`, and `stripe.accounts.link` to generate their `success_url`, `cancel_url`, and `return_url` values.
419
+
420
+ Each signed URL points to `GET /stripe/return/<origin>` on your Convex backend, carries an HMAC-SHA256 signature derived from your `account_webhook_secret`, and expires after the configured `redirect.ttlMs` (default: 15 minutes). When the user hits the URL, the library verifies the signature, checks expiry, runs any matching redirect handler, and then issues a 302 redirect to the `targetUrl` you specified. If verification fails or the link has expired, the user is redirected to your `failureUrl` instead (if provided).
421
+
422
+ ```ts
423
+ import { buildSignedReturnUrl } from "@raideno/convex-stripe/server";
424
+
425
+ const url = await buildSignedReturnUrl({
426
+ configuration, // your InternalConfiguration object
427
+ origin: "pay-success", // one of the redirect origins listed above
428
+ targetUrl: "https://example.com/payments/success",
429
+ failureUrl: "https://example.com/payments/error", // optional
430
+ data: {
431
+ entityId: "user_123",
432
+ referenceId: "order_456",
433
+ // data fields depend on the origin
434
+ },
435
+ });
436
+ ```
437
+
438
+ You typically do not need to call this function directly, as the built-in actions already handle URL signing for you. It is useful when building custom checkout or redirect flows outside of the provided actions.
439
+
440
+ ### Options
441
+
442
+ The second argument to `internalConvexStripe` is an optional options object.
443
+
444
+ ```ts
445
+ const { stripe, store, sync } = internalConvexStripe(configuration, {
446
+ debug: true, // enable debug logging
447
+ base: "stripe", // base path for HTTP routes (default: "stripe")
448
+ store: "store", // name of the store mutation export (default: "store")
449
+ });
450
+ ```
451
+
452
+ ## Stripe Connect
453
+
454
+ If you want to build a marketplace or platform with Stripe Connect, follow these additional steps after completing the [Usage](#usage) setup.
455
+
456
+ ### 0. Enable Connect on your Stripe dashboard.
457
+
458
+ Go to the [Stripe Connect settings](https://dashboard.stripe.com/test/settings/connect) and enable Connect for your account.
459
+
460
+ ### 1. Create a Connect webhook using the `sync` action.
461
+
462
+ Run the `sync` action from your Convex dashboard with the following arguments:
463
+
464
+ ```json
244
465
  {
245
- data?: boolean | { withConnect: boolean };
246
- webhook?: { account?: boolean; connect?: boolean };
247
- portal?: boolean;
248
- unstable_catalog?: boolean;
466
+ "tables": true,
467
+ "webhooks": {
468
+ "connect": true
469
+ }
249
470
  }
250
471
  ```
251
472
 
252
- - `tables` (optional, default: `true`): Syncs all existing Stripe resources to Convex tables.
253
- - `tables.withConnect` (option, default: `false`): Syncs all existing Stripe resources from linked accounts to Convex tables.
254
- - `webhook.account` (optional, default: `false`): Creates/updates the account webhook endpoint. Returns the webhook secret if a new endpoint is created. You must set it in your convex environment variables as `STRIPE_ACCOUNT_WEBHOOK_SECRET`.
255
- - `webhook.connect` (optional, default: `false`): Creates/updates the connect webhook endpoint. Returns the webhook secret if a new endpoint is created. You must set it in your convex environment variables as `STRIPE_CONNECT_WEBHOOK_SECRET`.
256
- - `portal` (optional, default: `false`): Creates the default billing portal configuration if it doesn't exist.
257
- - `unstable_catalog` (optional, default: `false`): Creates the default provided products and prices passed in the configuration.
473
+ This creates a Connect webhook endpoint on your Stripe account. The webhook secret will be printed in the Convex function logs.
474
+
475
+ ### 2. Add the new webhook secret to your Convex environment and configuration.
476
+
477
+ Set the Connect webhook secret as an environment variable:
258
478
 
259
- ### `stripe.subscribe` Function
479
+ ```bash
480
+ npx convex env set STRIPE_CONNECT_WEBHOOK_SECRET "<secret>"
481
+ ```
260
482
 
261
- Creates a Stripe Subscription Checkout session for a given entity. Will call [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood, the same parameters can be passed.
483
+ Then update your configuration to include it:
262
484
 
263
485
  ```ts
264
- import { v } from "convex/values";
486
+ // convex/stripe.ts
265
487
 
488
+ export const { stripe, store, sync } = internalConvexStripe({
489
+ stripe: {
490
+ secret_key: process.env.STRIPE_SECRET_KEY!,
491
+ account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,
492
+ connect_webhook_secret: process.env.STRIPE_CONNECT_WEBHOOK_SECRET!,
493
+ },
494
+ sync: {
495
+ tables: syncAllTables(),
496
+ },
497
+ });
498
+ ```
499
+
500
+ ### 3. Create Stripe Accounts for Sellers & Onboard them
501
+
502
+ Create a connected account for each seller and generate an onboarding link:
503
+
504
+ ```ts
505
+ // convex/connect.ts
506
+
507
+ import { v } from "convex/values";
508
+ import { action } from "./_generated/server";
266
509
  import { stripe } from "./stripe";
267
- import { action, internal } from "./_generated/api";
268
510
 
269
- export const createCheckout = action({
270
- args: { entityId: v.string(), priceId: v.string() },
511
+ export const createSellerAccount = action({
512
+ args: { entityId: v.string(), email: v.string() },
271
513
  handler: async (context, args) => {
272
- // TODO: add your own auth/authorization logic here
273
-
274
- const response = await stripe.subscribe(context, {
514
+ // create the connected account
515
+ const account = await stripe.accounts.create(context, {
275
516
  entityId: args.entityId,
276
- priceId: args.priceId,
277
- mode: "subscription",
278
- success_url: "http://localhost:3000/payments/success",
279
- cancel_url: "http://localhost:3000/payments/cancel",
280
- /*
281
- * Other parameters from stripe.checkout.sessions.create(...)
282
- */
283
- }, {
284
- /*
285
- * Optional Stripe Request Options
286
- */
517
+ email: args.email,
518
+ controller: {
519
+ fees: { payer: "application" },
520
+ losses: { payments: "application" },
521
+ stripe_dashboard: { type: "express" },
522
+ },
287
523
  });
288
524
 
289
- return response.url;
525
+ // generate the onboarding link
526
+ const link = await stripe.accounts.link(context, {
527
+ account: account.accountId,
528
+ refresh_url: "http://localhost:3000/connect/refresh",
529
+ return_url: "http://localhost:3000/connect/return",
530
+ type: "account_onboarding",
531
+ collection_options: { fields: "eventually_due" },
532
+ });
533
+
534
+ return link.url;
290
535
  },
291
536
  });
292
537
  ```
293
538
 
539
+ ### 4. Create Products for Sellers
540
+
541
+ Create products and prices on connected accounts by passing `stripeAccount` in the Stripe request options:
294
542
 
295
- ### `stripe.portal` Function
543
+ ```ts
544
+ const product = await stripe.client.products.create(
545
+ { name: "Widget", default_price_data: { currency: "usd", unit_amount: 1000 } },
546
+ { stripeAccount: account.accountId },
547
+ );
548
+ ```
296
549
 
297
- Allows an entity to manage their subscription via the Stripe Portal. Will call [`stripe.billingPortal.sessions.create`](https://docs.stripe.com/api/customer_portal/sessions/create) under the hood, the same parameters can be passed.
550
+ ### 5. Send Payouts
551
+
552
+ Payouts to connected accounts are handled by Stripe automatically based on your Connect payout schedule. You can also create manual transfers using the `stripe.client`:
298
553
 
299
554
  ```ts
300
- import { v } from "convex/values";
555
+ const transfer = await stripe.client.transfers.create({
556
+ amount: 1000,
557
+ currency: "usd",
558
+ destination: account.accountId,
559
+ });
560
+ ```
301
561
 
302
- import { stripe } from "./stripe";
303
- import { action, internal } from "./_generated/api";
562
+ ## API Reference
304
563
 
305
- export const portal = action({
306
- args: { entityId: v.string() },
564
+ ### `stripe.customers.create`
565
+
566
+ Creates or retrieves a Stripe customer for a given entity. If the entity already has a Stripe customer associated with it, the existing customer is returned instead of creating a duplicate.
567
+
568
+ This should be called whenever a new entity is created in your app. See [Stripe Customers](#6-stripe-customers) for integration examples.
569
+
570
+ **Parameters:**
571
+
572
+ | Parameter | Type | Required | Description |
573
+ | :--------- | :------- | :------- | :---------------------------------------------------------- |
574
+ | `entityId` | `string` | Yes | Your app's internal identifier for the entity being billed. |
575
+ | `email` | `string` | No | Email address for the Stripe customer. Recommended. |
576
+ | `metadata` | `object` | No | Additional metadata to attach to the Stripe customer. |
577
+
578
+ All other parameters from [`Stripe.CustomerCreateParams`](https://docs.stripe.com/api/customers/create) are also accepted.
579
+
580
+ **Returns:** The Stripe customer document from your Convex database.
581
+
582
+ ```ts
583
+ const customer = await stripe.customers.create(context, {
584
+ entityId: args.entityId,
585
+ email: args.email,
586
+ });
587
+ ```
588
+
589
+ ### `stripe.subscribe`
590
+
591
+ Creates a Stripe Checkout session in `subscription` mode. Calls [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood.
592
+
593
+ **Parameters:**
594
+
595
+ | Parameter | Type | Required | Description |
596
+ | :------------------------------ | :--------------- | :------- | :------------------------------------------------------------------------------------------------- |
597
+ | `entityId` | `string` | Yes | Your app's internal identifier for the entity subscribing. |
598
+ | `priceId` | `string` | Yes | The Stripe Price ID for the subscription. |
599
+ | `mode` | `"subscription"` | Yes | Must be `"subscription"`. |
600
+ | `success_url` | `string` | Yes | URL to redirect to after a successful checkout. |
601
+ | `cancel_url` | `string` | Yes | URL to redirect to if the user cancels. |
602
+ | `failure_url` | `string` | No | URL to redirect to if the redirect signing fails. |
603
+ | `createStripeCustomerIfMissing` | `boolean` | No | If `true` (default), creates a Stripe customer automatically if one does not exist for the entity. |
604
+
605
+ All other parameters from [`Stripe.Checkout.SessionCreateParams`](https://docs.stripe.com/api/checkout/sessions/create) (except `customer`, `ui_mode`, `mode`, `line_items`, `client_reference_id`, `success_url`, `cancel_url`) are also accepted.
606
+
607
+ An optional third argument accepts [`Stripe.RequestOptions`](https://docs.stripe.com/api/request_options) (e.g. `stripeAccount` for Connect).
608
+
609
+ **Returns:** A [`Stripe.Checkout.Session`](https://docs.stripe.com/api/checkout/sessions/object). Use the `url` property to redirect the user.
610
+
611
+ ```ts
612
+ export const createSubscription = action({
613
+ args: { entityId: v.string(), priceId: v.string() },
307
614
  handler: async (context, args) => {
308
- const response = await stripe.portal(context, {
615
+ const response = await stripe.subscribe(context, {
309
616
  entityId: args.entityId,
310
- returnUrl: "http://localhost:3000/return-from-portal",
311
- /*
312
- * Other parameters from stripe.billingPortal.sessions.create(...)
313
- */
314
- }, {
315
- /*
316
- * Optional Stripe Request Options
317
- */
617
+ priceId: args.priceId,
618
+ mode: "subscription",
619
+ success_url: "https://example.com/payments/success",
620
+ cancel_url: "https://example.com/payments/cancel",
318
621
  });
319
622
 
320
623
  return response.url;
321
624
  },
322
625
  });
323
626
  ```
324
- The provided entityId must have a customerId associated to it otherwise the action will throw an error.
325
627
 
628
+ ### `stripe.pay`
629
+
630
+ Creates a Stripe Checkout session in `payment` mode for one time payments. Calls [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood.
326
631
 
327
- ### `stripe.pay` Function
632
+ **Parameters:**
328
633
 
329
- Creates a Stripe One Time Payment Checkout session for a given entity. Will call [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood, the same parameters can be passed.
634
+ | Parameter | Type | Required | Description |
635
+ | :------------------------------ | :---------- | :------- | :------------------------------------------------------------------------------------------------------------------------ |
636
+ | `entityId` | `string` | Yes | Your app's internal identifier for the entity making the payment. |
637
+ | `referenceId` | `string` | Yes | Your app's reference ID for this payment (e.g. an order ID). Stored as the `client_reference_id` on the checkout session. |
638
+ | `mode` | `"payment"` | Yes | Must be `"payment"`. |
639
+ | `line_items` | `array` | Yes | The line items for the checkout session (price, quantity, etc.). |
640
+ | `success_url` | `string` | Yes | URL to redirect to after a successful payment. |
641
+ | `cancel_url` | `string` | Yes | URL to redirect to if the user cancels. |
642
+ | `failure_url` | `string` | No | URL to redirect to if the redirect signing fails. |
643
+ | `createStripeCustomerIfMissing` | `boolean` | No | If `true` (default), creates a Stripe customer automatically if one does not exist for the entity. |
330
644
 
331
- ```ts
332
- import { v } from "convex/values";
645
+ All other parameters from [`Stripe.Checkout.SessionCreateParams`](https://docs.stripe.com/api/checkout/sessions/create) (except `customer`, `ui_mode`, `mode`, `client_reference_id`, `success_url`, `cancel_url`) are also accepted.
333
646
 
334
- import { stripe } from "./stripe";
335
- import { action, internal } from "./_generated/api";
647
+ An optional third argument accepts [`Stripe.RequestOptions`](https://docs.stripe.com/api/request_options).
336
648
 
337
- export const pay = action({
649
+ **Returns:** A [`Stripe.Checkout.Session`](https://docs.stripe.com/api/checkout/sessions/object). Use the `url` property to redirect the user.
650
+
651
+ ```ts
652
+ export const createPayment = action({
338
653
  args: { entityId: v.string(), orderId: v.string(), priceId: v.string() },
339
654
  handler: async (context, args) => {
340
- // Add your own auth/authorization logic here
341
-
342
655
  const response = await stripe.pay(context, {
343
656
  referenceId: args.orderId,
344
657
  entityId: args.entityId,
345
658
  mode: "payment",
346
659
  line_items: [{ price: args.priceId, quantity: 1 }],
347
- success_url: `${process.env.SITE_URL}/?return-from-pay=success`,
348
- cancel_url: `${process.env.SITE_URL}/?return-from-pay=cancel`,
349
- /*
350
- * Other parameters from stripe.checkout.sessions.create(...)
351
- */
352
- }, {
353
- /*
354
- * Optional Stripe Request Options
355
- */
660
+ success_url: `${process.env.SITE_URL}/payments/success`,
661
+ cancel_url: `${process.env.SITE_URL}/payments/cancel`,
356
662
  });
357
663
 
358
664
  return response.url;
@@ -360,23 +666,220 @@ export const pay = action({
360
666
  });
361
667
  ```
362
668
 
669
+ ### `stripe.portal`
363
670
 
364
- ## Best Practices
671
+ Opens a Stripe Billing Portal session for an existing customer. Allows users to manage their subscriptions, invoices, and payment methods. Calls [`stripe.billingPortal.sessions.create`](https://docs.stripe.com/api/customer_portal/sessions/create) under the hood.
365
672
 
366
- - Always create a Stripe customer (`stripe.customers.create`) when a new entity is created.
367
- - Use `metadata` or `marketing_features` on products to store feature flags or limits.
368
- - Run `sync` when you first configure the extension to sync already existing stripe resources.
369
- - Never expose internal actions directly to clients, wrap them in public actions with proper authorization.
673
+ **Parameters:**
674
+
675
+ | Parameter | Type | Required | Description |
676
+ | :------------------------------ | :-------- | :------- | :------------------------------------------------------------------------------------------------- |
677
+ | `entityId` | `string` | Yes | Your app's internal identifier for the entity. |
678
+ | `return_url` | `string` | Yes | URL to redirect to when the user leaves the portal. |
679
+ | `failure_url` | `string` | No | URL to redirect to if the redirect signing fails. |
680
+ | `createStripeCustomerIfMissing` | `boolean` | No | If `true` (default), creates a Stripe customer automatically if one does not exist for the entity. |
681
+
682
+ All other parameters from [`Stripe.BillingPortal.SessionCreateParams`](https://docs.stripe.com/api/customer_portal/sessions/create) (except `customer` and `return_url`) are also accepted.
683
+
684
+ An optional third argument accepts [`Stripe.RequestOptions`](https://docs.stripe.com/api/request_options).
685
+
686
+ **Returns:** A [`Stripe.BillingPortal.Session`](https://docs.stripe.com/api/customer_portal/sessions/object). Use the `url` property to redirect the user.
687
+
688
+ ```ts
689
+ export const openPortal = action({
690
+ args: { entityId: v.string() },
691
+ handler: async (context, args) => {
692
+ const response = await stripe.portal(context, {
693
+ entityId: args.entityId,
694
+ return_url: "https://example.com/account",
695
+ });
696
+
697
+ return response.url;
698
+ },
699
+ });
700
+ ```
701
+
702
+ ### `stripe.accounts.create`
703
+
704
+ Creates a new Stripe Connect account and stores it in your Convex database. If an account already exists for the given entity, the existing account is returned.
705
+
706
+ **Parameters:**
707
+
708
+ | Parameter | Type | Required | Description |
709
+ | :--------- | :------- | :------- | :------------------------------------------------------------- |
710
+ | `entityId` | `string` | Yes | Your app's internal identifier for the seller/platform entity. |
711
+
712
+ All other parameters from [`Stripe.AccountCreateParams`](https://docs.stripe.com/api/accounts/create) (except `type`) are also accepted.
713
+
714
+ An optional third argument accepts [`Stripe.RequestOptions`](https://docs.stripe.com/api/request_options).
715
+
716
+ **Returns:** The Stripe account document from your Convex database.
717
+
718
+ ```ts
719
+ const account = await stripe.accounts.create(context, {
720
+ entityId: args.entityId,
721
+ email: args.email,
722
+ controller: {
723
+ fees: { payer: "application" },
724
+ losses: { payments: "application" },
725
+ stripe_dashboard: { type: "express" },
726
+ },
727
+ });
728
+ ```
729
+
730
+ ### `stripe.accounts.link`
731
+
732
+ Creates a Stripe Connect Account Link for onboarding. Redirects the connected account holder to Stripe's hosted onboarding flow.
733
+
734
+ **Parameters:**
370
735
 
736
+ | Parameter | Type | Required | Description |
737
+ | :------------ | :------- | :------- | :-------------------------------------------------- |
738
+ | `account` | `string` | Yes | The Stripe Account ID to onboard (e.g. `acct_...`). |
739
+ | `refresh_url` | `string` | Yes | URL to redirect to if the link expires. |
740
+ | `return_url` | `string` | Yes | URL to redirect to after onboarding is complete. |
741
+ | `failure_url` | `string` | No | URL to redirect to if the redirect signing fails. |
742
+
743
+ All other parameters from [`Stripe.AccountLinkCreateParams`](https://docs.stripe.com/api/account_links/create) (except `refresh_url` and `return_url`) are also accepted.
744
+
745
+ An optional third argument accepts [`Stripe.RequestOptions`](https://docs.stripe.com/api/request_options).
746
+
747
+ **Returns:** A [`Stripe.AccountLink`](https://docs.stripe.com/api/account_links/object). Use the `url` property to redirect the user.
748
+
749
+ ```ts
750
+ const link = await stripe.accounts.link(context, {
751
+ account: account.accountId,
752
+ refresh_url: "https://example.com/connect/refresh",
753
+ return_url: "https://example.com/connect/return",
754
+ type: "account_onboarding",
755
+ collection_options: { fields: "eventually_due" },
756
+ });
757
+ ```
758
+
759
+ ### `stripe.addHttpRoutes`
760
+
761
+ Registers the Stripe webhook and redirect routes on your Convex HTTP router. Call this inside your `convex/http.ts` file.
762
+
763
+ Registers two routes:
764
+ - `POST /stripe/webhook` receives and verifies Stripe webhook events.
765
+ - `GET /stripe/return/*` handles post-checkout and post-portal redirect flows.
766
+
767
+ ```ts
768
+ const http = httpRouter();
769
+ stripe.addHttpRoutes(http);
770
+ export default http;
771
+ ```
772
+
773
+ ### `stripe.client`
774
+
775
+ A pre-configured [Stripe SDK](https://docs.stripe.com/api) client using your `secret_key` and API `version`. Use this for any Stripe API call not covered by the library's built-in functions.
776
+
777
+ ```ts
778
+ const product = await stripe.client.products.create({
779
+ name: "New Product",
780
+ default_price_data: { currency: "usd", unit_amount: 999 },
781
+ });
782
+ ```
783
+
784
+ ### `sync` Action
785
+
786
+ An internal action that synchronizes Stripe resources with your Convex database.
787
+
788
+ This action is typically called manually from the Convex dashboard, or set up to be called automatically in your CI/CD pipeline on each deployment.
789
+
790
+ **Parameters:**
791
+
792
+ | Parameter | Type | Required | Description |
793
+ | :--------- | :----------------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
794
+ | `tables` | `boolean \| { withConnect: boolean }` | Yes | If `true`, syncs all existing Stripe resources to Convex tables. If an object with `withConnect: true`, also syncs resources from linked connected accounts. |
795
+ | `webhooks` | `{ account?: boolean, connect?: boolean }` | No | If `account` is `true`, creates or updates the account webhook endpoint. If `connect` is `true`, creates or updates the Connect webhook endpoint. The webhook secret is printed to the function logs when a new endpoint is created. |
796
+ | `portal` | `boolean` | No | If `true`, creates the default billing portal configuration if it does not already exist. |
797
+ | `catalog` | `boolean` | No | If `true`, creates or updates the products and prices defined in your `sync.catalog` configuration. |
798
+
799
+ ### `store` Mutation
800
+
801
+ An internal mutation that persists Stripe objects into your Convex database. This is called automatically from within the webhook handler and is not meant for direct use. It must be exported from the same file as your `internalConvexStripe` call.
802
+
803
+ ## Synced Tables
804
+
805
+ The library automatically syncs the following 24 Stripe resource types into your Convex database:
806
+
807
+ | Table | ID Field | Description |
808
+ | :---------------------------------- | :----------------------------- | :---------------------------- |
809
+ | `stripeAccounts` | `accountId` | Connected accounts |
810
+ | `stripeProducts` | `productId` | Products |
811
+ | `stripePrices` | `priceId` | Prices |
812
+ | `stripeCustomers` | `customerId` | Customers |
813
+ | `stripeSubscriptions` | `subscriptionId` | Subscriptions |
814
+ | `stripeCoupons` | `couponId` | Coupons |
815
+ | `stripePromotionCodes` | `promotionCodeId` | Promotion codes |
816
+ | `stripePayouts` | `payoutId` | Payouts |
817
+ | `stripeRefunds` | `refundId` | Refunds |
818
+ | `stripePaymentIntents` | `paymentIntentId` | Payment intents |
819
+ | `stripeCheckoutSessions` | `checkoutSessionId` | Checkout sessions |
820
+ | `stripeInvoices` | `invoiceId` | Invoices |
821
+ | `stripeReviews` | `reviewId` | Reviews |
822
+ | `stripePlans` | `planId` | Plans |
823
+ | `stripeDisputes` | `disputeId` | Disputes |
824
+ | `stripeEarlyFraudWarnings` | `earlyFraudWarningId` | Early fraud warnings |
825
+ | `stripeTaxIds` | `taxIdId` | Tax IDs |
826
+ | `stripeSetupIntents` | `setupIntentId` | Setup intents |
827
+ | `stripeCreditNotes` | `creditNoteId` | Credit notes |
828
+ | `stripeCharges` | `chargeId` | Charges |
829
+ | `stripePaymentMethods` | `paymentMethodId` | Payment methods |
830
+ | `stripeSubscriptionSchedules` | `subscriptionScheduleId` | Subscription schedules |
831
+ | `stripeMandates` | `mandateId` | Mandates |
832
+ | `stripeBillingPortalConfigurations` | `billingPortalConfigurationId` | Billing portal configurations |
833
+ | `stripeTransfers` | `transferId` | Transfers |
834
+ | `stripeCapabilities` | `capabilityId` | Capabilities |
835
+
836
+ Each table stores the full Stripe object in a `stripe` field and includes a `lastSyncedAt` timestamp. All tables have a `byStripeId` index on their ID field. Tables with an `accountId` field also have a `byAccountId` index for filtering by connected account.
837
+
838
+ For the full schema of each table, see [Tables Reference](./documentation/references/tables.md).
839
+
840
+ ## Synced Events
841
+
842
+ The library handles a large number of Stripe webhook events to keep your local data in sync. Below is a summary by resource type. For the full list, see [Events Reference](./documentation/references/events.md).
843
+
844
+ | Resource | Events |
845
+ | :--------------------- | :----------------------------------------------------------------------------------------- |
846
+ | Subscriptions | `customer.subscription.created`, `updated`, `deleted`, `paused`, `resumed`, etc. |
847
+ | Checkout Sessions | `checkout.session.completed`, `expired`, `async_payment_succeeded`, `async_payment_failed` |
848
+ | Customers | `customer.created`, `updated`, `deleted` |
849
+ | Invoices | `invoice.created`, `paid`, `payment_failed`, `finalized`, `voided`, etc. |
850
+ | Payment Intents | `payment_intent.created`, `succeeded`, `canceled`, `payment_failed`, etc. |
851
+ | Products | `product.created`, `updated`, `deleted` |
852
+ | Prices | `price.created`, `updated`, `deleted` |
853
+ | Charges | `charge.captured`, `succeeded`, `failed`, `refunded`, etc. |
854
+ | Refunds | `refund.created`, `updated`, `failed` |
855
+ | Payouts | `payout.created`, `paid`, `failed`, `canceled`, etc. |
856
+ | Disputes | `charge.dispute.created`, `updated`, `closed`, etc. |
857
+ | Payment Methods | `payment_method.attached`, `detached`, `updated`, etc. |
858
+ | Setup Intents | `setup_intent.created`, `succeeded`, `canceled`, `setup_failed`, etc. |
859
+ | Coupons | `coupon.created`, `updated`, `deleted` |
860
+ | Promotion Codes | `promotion_code.created`, `updated` |
861
+ | Credit Notes | `credit_note.created`, `updated`, `voided` |
862
+ | Reviews | `review.opened`, `closed` |
863
+ | Plans | `plan.created`, `updated`, `deleted` |
864
+ | Tax IDs | `customer.tax_id.created`, `updated`, `deleted` |
865
+ | Early Fraud Warnings | `radar.early_fraud_warning.created`, `updated` |
866
+ | Subscription Schedules | `subscription_schedule.created`, `updated`, `canceled`, `completed`, etc. |
867
+
868
+ ## Best Practices
869
+
870
+ - Always create a Stripe customer (`stripe.customers.create`) the moment a new entity is created in your app. This ensures every user or organization has a Stripe customer ready for billing.
871
+ - Use `metadata` or `marketing_features` on Stripe products to store feature flags or usage limits. You can then query the synced `stripeProducts` table to check entitlements.
872
+ - Run the `sync` action when you first configure the library, and after each deployment, to ensure your local database is up to date with Stripe.
873
+ - Never expose internal actions directly to clients. Always wrap them in public actions with proper authentication and authorization checks.
874
+ - Keep your webhook secrets secure. Never commit them to source control. Always use Convex environment variables.
371
875
 
372
876
  ## Resources
373
877
 
374
- - [Convex Documentation](https://docs.convex.dev)
375
- - [Stripe Documentation](https://stripe.com/docs)
878
+ - [Convex Documentation](https://docs.convex.dev)
879
+ - [Stripe Documentation](https://stripe.com/docs)
376
880
  - [GitHub Repository](https://github.com/raideno/convex-stripe)
377
881
  - [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations)
378
882
 
379
-
380
883
  ## Development
381
884
 
382
885
  Clone the repository:
@@ -403,4 +906,4 @@ npm run dev --workspace demo
403
906
 
404
907
  ## Contributions
405
908
 
406
- All contributions are welcome! Please open an issue or a PR.
909
+ All contributions are welcome. Please open an issue or a pull request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raideno/convex-stripe",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "Easy stripe billing for convex apps.",
5
5
  "keywords": [
6
6
  "billing",