@raideno/convex-stripe 0.2.9 → 0.3.1
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 +675 -172
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,22 +1,73 @@
|
|
|
1
1
|
# Convex Stripe
|
|
2
2
|
|
|
3
|
-
Stripe [syncing](./references/tables.md), subscriptions, [checkouts](
|
|
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
|
|
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
|
|
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
|
-
|
|
80
|
+
If you plan to use Stripe Connect, also set:
|
|
30
81
|
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
> **Important:** `store` must always be exported, as it is used internally.
|
|
128
|
+
### 5. Register HTTP Routes
|
|
77
129
|
|
|
78
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
+
This step is not necessary if you are starting with a fresh, empty Stripe account.
|
|
152
227
|
|
|
153
|
-
|
|
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
|
-
|
|
230
|
+
With everything set up, you can now use the provided functions to:
|
|
157
231
|
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
+
The `internalConvexStripe` function accepts a configuration object and an optional options object.
|
|
168
242
|
|
|
169
|
-
|
|
243
|
+
```ts
|
|
244
|
+
const { stripe, store, sync } = internalConvexStripe(configuration, options);
|
|
245
|
+
```
|
|
170
246
|
|
|
171
|
-
###
|
|
172
|
-
...
|
|
247
|
+
### Stripe Configuration
|
|
173
248
|
|
|
174
|
-
|
|
175
|
-
...
|
|
249
|
+
The `stripe` key in the configuration object holds your Stripe credentials and API settings.
|
|
176
250
|
|
|
177
|
-
|
|
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
|
-
###
|
|
181
|
-
...
|
|
258
|
+
### Sync Configuration
|
|
182
259
|
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
...
|
|
262
|
+
#### Table Selection
|
|
188
263
|
|
|
189
|
-
|
|
264
|
+
Three helper functions are provided to control which Stripe tables are synced into Convex:
|
|
190
265
|
|
|
191
|
-
|
|
266
|
+
```ts
|
|
267
|
+
import {
|
|
268
|
+
syncAllTables,
|
|
269
|
+
syncAllTablesExcept,
|
|
270
|
+
syncOnlyTables,
|
|
271
|
+
} from "@raideno/convex-stripe/server";
|
|
192
272
|
|
|
193
|
-
|
|
273
|
+
// Sync all 24 tables (default)
|
|
274
|
+
{ tables: syncAllTables() }
|
|
194
275
|
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
283
|
+
All 24 available tables are listed in the [Synced Tables](#synced-tables) section.
|
|
202
284
|
|
|
203
|
-
|
|
285
|
+
#### Catalog (Unstable)
|
|
204
286
|
|
|
205
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
359
|
+
### Custom Webhook Handlers
|
|
227
360
|
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
+
internalConvexStripe({
|
|
374
|
+
// ...
|
|
375
|
+
webhook: {
|
|
376
|
+
handlers: [myHandler],
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
238
380
|
|
|
239
|
-
|
|
381
|
+
### Custom Redirect Handlers
|
|
240
382
|
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
466
|
+
"tables": true,
|
|
467
|
+
"webhooks": {
|
|
468
|
+
"connect": true
|
|
469
|
+
}
|
|
249
470
|
}
|
|
250
471
|
```
|
|
251
472
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
479
|
+
```bash
|
|
480
|
+
npx convex env set STRIPE_CONNECT_WEBHOOK_SECRET "<secret>"
|
|
481
|
+
```
|
|
260
482
|
|
|
261
|
-
|
|
483
|
+
Then update your configuration to include it:
|
|
262
484
|
|
|
263
485
|
```ts
|
|
264
|
-
|
|
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
|
|
270
|
-
args: { entityId: v.string(),
|
|
511
|
+
export const createSellerAccount = action({
|
|
512
|
+
args: { entityId: v.string(), email: v.string() },
|
|
271
513
|
handler: async (context, args) => {
|
|
272
|
-
//
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
+
const transfer = await stripe.client.transfers.create({
|
|
556
|
+
amount: 1000,
|
|
557
|
+
currency: "usd",
|
|
558
|
+
destination: account.accountId,
|
|
559
|
+
});
|
|
560
|
+
```
|
|
301
561
|
|
|
302
|
-
|
|
303
|
-
import { action, internal } from "./_generated/api";
|
|
562
|
+
## API Reference
|
|
304
563
|
|
|
305
|
-
|
|
306
|
-
|
|
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.
|
|
615
|
+
const response = await stripe.subscribe(context, {
|
|
309
616
|
entityId: args.entityId,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
632
|
+
**Parameters:**
|
|
328
633
|
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
348
|
-
cancel_url: `${process.env.SITE_URL}
|
|
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
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Easy stripe billing for convex apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"billing",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"README.md"
|
|
16
|
-
"../../README.md"
|
|
15
|
+
"README.md"
|
|
17
16
|
],
|
|
17
|
+
"readme": "README.md",
|
|
18
18
|
"exports": {
|
|
19
19
|
"./server": {
|
|
20
20
|
"import": "./dist/server.js",
|