@raideno/convex-stripe 0.2.0-beta.1 → 0.2.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 +361 -5
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,14 +2,370 @@
2
2
 
3
3
  A demo project is available at [https://convex-stripe-demo.vercel.app/](https://convex-stripe-demo.vercel.app/).
4
4
 
5
- Stripe subscriptions, limits and features for Convex apps. Implemented according to the best practices listed in [Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations).
5
+ 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).
6
6
 
7
- ## Install
8
7
 
9
- ```bash
8
+ ## Installation
9
+
10
+ ```sh [npm]
10
11
  npm install @raideno/convex-stripe stripe
11
12
  ```
12
13
 
13
- ## Documentation
14
+ ## Configuration
15
+
16
+ ### 1. Set up Stripe
17
+ - Create a Stripe account.
18
+ - Configure a webhook pointing to:
19
+ ```
20
+ https://<your-convex-app>.convex.site/stripe/webhook
21
+ ```
22
+ - Enable the following [Stripe Events](./references/events.md).
23
+ - Enable the [Stripe Billing Portal](https://dashboard.stripe.com/test/settings/billing/portal).
24
+
25
+ ### 2. Set Environment Variables on Convex
26
+
27
+ ```bash
28
+ npx convex env set STRIPE_SECRET_KEY "<secret>"
29
+ npx convex env set STRIPE_ACCOUNT_WEBHOOK_SECRET "<secret>"
30
+ ```
31
+
32
+ ### 3. Add tables.
33
+
34
+ Check [Tables Schemas](./references/tables.md) to know more about the synced tables.
35
+
36
+ ```ts [convex/schema.ts]
37
+ import { defineSchema } from "convex/server";
38
+ import { stripeTables } from "@raideno/convex-stripe/server";
39
+
40
+ export default defineSchema({
41
+ ...stripeTables,
42
+ // your other tables...
43
+ });
44
+ ```
45
+
46
+ ### 4. Initialize the library
47
+
48
+ ```ts [convex/stripe.ts]
49
+ import { internalConvexStripe } from "@raideno/convex-stripe/server";
50
+
51
+ export const { stripe, store, sync } = internalConvexStripe({
52
+ stripe: {
53
+ secret_key: process.env.STRIPE_SECRET_KEY!,
54
+ account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,
55
+ },
56
+ });
57
+
58
+ export const createCustomer = internalAction({
59
+ args: {
60
+ email: v.optional(v.string()),
61
+ entityId: v.string(),
62
+ },
63
+ handler: async (context, args) => {
64
+ return stripe.customers.create(context, {
65
+ email: args.email,
66
+ entityId: args.entityId,
67
+ });
68
+ },
69
+ });
70
+
71
+ ```
72
+
73
+ > **Note:** All exposed actions (store, sync, createEntity) are **internal**. Meaning they can only be called from other convex functions, you can wrap them in public actions when needed.
74
+ > **Important:** `store` must always be exported, as it is used internally.
75
+
76
+ ### 5. Register HTTP routes
77
+
78
+ ```ts [convex/http.ts]
79
+ import { httpRouter } from "convex/server";
80
+ import { stripe } from "./stripe";
81
+
82
+ const http = httpRouter();
83
+
84
+ // registers POST /stripe/webhook
85
+ // registers GET /stripe/return/*
86
+ stripe.addHttpRoutes(http);
87
+
88
+ export default http;
89
+ ```
90
+
91
+ ### 6. Stripe customers
92
+
93
+ Ideally you want to create a stripe customer the moment a new entity (user, organization, etc) is created.
94
+
95
+ 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 [`createEntity` action](#createentity-action).
96
+
97
+ Below are with different auth providers examples where the user is the entity we are billing.
98
+
99
+ ::: code-group
100
+
101
+ ```ts [convex-auth]
102
+ "convex/auth.ts"
103
+
104
+ // example with convex-auth: https://labs.convex.dev/auth
105
+
106
+ import { convexAuth } from "@convex-dev/auth/server";
107
+ import { Password } from "@convex-dev/auth/providers/Password";
108
+ import { internal } from "./_generated/api";
109
+
110
+ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
111
+ providers: [Password],
112
+ callbacks: {
113
+ afterUserCreatedOrUpdated: async (context, args) => {
114
+ await context.scheduler.runAfter(0, internal.stripe.createCustomer, {
115
+ /*
116
+ * will call stripe.customers.create
117
+ */
118
+ entityId: args.userId,
119
+ email: args.profile.email,
120
+ });
121
+ },
122
+ },
123
+ });
124
+ ```
125
+
126
+ ```ts [better-auth]
127
+ "convex/auth.ts"
128
+
129
+ // example with better-auth: https://convex-better-auth.netlify.app/
130
+
131
+ // coming soon...
132
+ ```
133
+
134
+ ```ts [clerk]
135
+ "convex/auth.ts"
136
+
137
+ // example with clerk: https://docs.convex.dev/auth/clerk
138
+
139
+ // coming soon...
140
+ ```
141
+
142
+ :::
143
+
144
+ ### 7. Run `sync` action
145
+
146
+ In your convex project's dashboard. Go the **Functions** section and execute the `sync` action.
147
+
148
+ This is done to sync already existing stripe data into your convex database.
149
+ It must be done in both your development and production deployments after installing or updating the library.
150
+
151
+ This might not be necessary if you are starting with a fresh empty stripe project.
152
+
153
+ ### 8. Start building
154
+ Now you can use the provided functions to:
155
+ - Generate a subscription or payment link [`stripe.subscribe`](#subscribe-function), [`stripe.pay`](#pay-function) for a given entity.
156
+ - Generate a link to the entity's [`stripe.portal`](#portal-function) to manage their subscriptions.
157
+ - Create stripe connect accounts and link them, [`stripe.accounts.create`](#), [`stripe.accounts.link`](#).
158
+ - Consult the [synced tables](./references/tables.md).
159
+ - Etc.
160
+
161
+
162
+ ## Usage
163
+
164
+ The library automatically syncs the [following tables](./references/tables.md).
165
+
166
+ You can query these tables at any time to:
167
+
168
+ - List available products/plans and prices.
169
+ - Retrieve customers and their `customerId`.
170
+ - Check active subscriptions.
171
+ - Etc.
172
+
173
+
174
+ ### `customers.create` Function
175
+
176
+ 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.
177
+
178
+ 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.
179
+
180
+ ```ts
181
+ import { v } from "convex/values";
182
+ import { stripe } from "./stripe";
183
+ import { action, internal } from "./_generated/api";
184
+
185
+ export const createCustomer = internalAction({
186
+ args: {
187
+ email: v.optional(v.string()),
188
+ entityId: v.string(),
189
+ },
190
+ handler: async (context, args) => {
191
+ return stripe.customers.create(context, {
192
+ email: args.email,
193
+ entityId: args.entityId,
194
+ });
195
+ },
196
+ });
197
+ ```
198
+
199
+ **Notes:**
200
+
201
+ - `entityId` is your app’s internal ID (user/org).
202
+ - `customerId` is stripe's internal ID.
203
+ - `email` is optional, but recommended so the Stripe customer has a contact email.
204
+ - If the entity already has a Stripe customer, `createEntity` will return the existing one instead of creating a duplicate.
205
+ - Typically, you’ll call this automatically in your user/org creation flow (see [Configuration - 6](#configuration)).
206
+
207
+
208
+ ### `sync` Action
209
+
210
+ Synchronizes Stripe resources with your Convex database.
211
+
212
+ This action is typically manually called or setup to be automatically called in your ci/cd pipeline.
213
+
214
+ **Parameters:**
215
+
216
+ - `data` (optional, default: `true`): Syncs all existing Stripe resources to Convex tables.
217
+ - `data.withConnect` (option, default: `false`): Syncs all existing Stripe resources from linked accounts to Convex tables.
218
+ - `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`.
219
+ - `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`.
220
+ - `portal` (optional, default: `false`): Creates the default billing portal configuration if it doesn't exist.
221
+ - `unstable_catalog` (optional, default: `false`): Creates the default provided products and prices passed in the configuration.
222
+
223
+ ### `subscribe` Function
224
+
225
+ 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.
226
+
227
+ ```ts
228
+ import { v } from "convex/values";
229
+
230
+ import { stripe } from "./stripe";
231
+ import { action, internal } from "./_generated/api";
232
+
233
+ export const createCheckout = action({
234
+ args: { entityId: v.string(), priceId: v.string() },
235
+ handler: async (context, args) => {
236
+ // TODO: add your own auth/authorization logic here
237
+
238
+ const response = await stripe.subscribe(context, {
239
+ entityId: args.entityId,
240
+ priceId: args.priceId,
241
+ mode: "subscription",
242
+ success_url: "http://localhost:3000/payments/success",
243
+ cancel_url: "http://localhost:3000/payments/cancel",
244
+ /*
245
+ * Other parameters from stripe.checkout.sessions.create(...)
246
+ */
247
+ }, {
248
+ /*
249
+ * Optional Stripe Request Options
250
+ */
251
+ });
252
+
253
+ return response.url;
254
+ },
255
+ });
256
+ ```
257
+
258
+
259
+ ### `portal` Function
260
+
261
+ 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.
262
+
263
+ ```ts
264
+ import { v } from "convex/values";
265
+
266
+ import { stripe } from "./stripe";
267
+ import { action, internal } from "./_generated/api";
268
+
269
+ export const portal = action({
270
+ args: { entityId: v.string() },
271
+ handler: async (context, args) => {
272
+ const response = await stripe.portal(context, {
273
+ entityId: args.entityId,
274
+ returnUrl: "http://localhost:3000/return-from-portal",
275
+ /*
276
+ * Other parameters from stripe.billingPortal.sessions.create(...)
277
+ */
278
+ }, {
279
+ /*
280
+ * Optional Stripe Request Options
281
+ */
282
+ });
283
+
284
+ return response.url;
285
+ },
286
+ });
287
+ ```
288
+ The provided entityId must have a customerId associated to it otherwise the action will throw an error.
289
+
290
+
291
+ ### `pay` Function
292
+
293
+ 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.
294
+
295
+ ```ts
296
+ import { v } from "convex/values";
297
+
298
+ import { stripe } from "./stripe";
299
+ import { action, internal } from "./_generated/api";
300
+
301
+ export const pay = action({
302
+ args: { entityId: v.string(), orderId: v.string(), priceId: v.string() },
303
+ handler: async (context, args) => {
304
+ // Add your own auth/authorization logic here
305
+
306
+ const response = await stripe.pay(context, {
307
+ referenceId: args.orderId,
308
+ entityId: args.entityId,
309
+ mode: "payment",
310
+ line_items: [{ price: args.priceId, quantity: 1 }],
311
+ success_url: `${process.env.SITE_URL}/?return-from-pay=success`,
312
+ cancel_url: `${process.env.SITE_URL}/?return-from-pay=cancel`,
313
+ /*
314
+ * Other parameters from stripe.checkout.sessions.create(...)
315
+ */
316
+ }, {
317
+ /*
318
+ * Optional Stripe Request Options
319
+ */
320
+ });
321
+
322
+ return response.url;
323
+ },
324
+ });
325
+ ```
326
+
327
+
328
+ ## Best Practices
329
+
330
+ - Always create a Stripe customer (`createEntity`) when a new entity is created.
331
+ - Use `metadata` or `marketing_features` on products to store feature flags or limits.
332
+ - Run `sync` when you first configure the extension to sync already existing stripe resources.
333
+ - Never expose internal actions directly to clients, wrap them in public actions with proper authorization.
334
+
335
+
336
+ ## Resources
337
+
338
+ - [Convex Documentation](https://docs.convex.dev)
339
+ - [Stripe Documentation](https://stripe.com/docs)
340
+ - [Demo App](https://convex-stripe-demo.vercel.app/)
341
+ - [GitHub Repository](https://github.com/raideno/convex-stripe)
342
+ - [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations)
343
+
344
+
345
+ ## Development
346
+
347
+ Clone the repository:
348
+
349
+ ```bash
350
+ git clone git@github.com:raideno/convex-stripe.git
351
+ cd convex-stripe
352
+ ```
353
+
354
+ Install the dependencies:
355
+
356
+ ```bash
357
+ npm install
358
+ ```
359
+
360
+ Start the development server:
361
+
362
+ ```bash
363
+ # automatically rebuild lib on changes
364
+ npm run dev --workspace @raideno/convex-stripe
365
+ # run the demo app
366
+ npm run dev --workspace demo
367
+ ```
368
+
369
+ ## Contributions
14
370
 
15
- You can find the detailed documentation @ [https://raideno.github.io/convex-stripe](https://raideno.github.io/convex-stripe).
371
+ All contributions are welcome! Please open an issue or a PR.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raideno/convex-stripe",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0",
4
4
  "description": "Easy stripe billing for convex apps.",
5
5
  "keywords": [
6
6
  "billing",