@raideno/convex-stripe 0.3.4 → 0.3.6
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 +165 -35
- package/dist/index.d.ts +352 -10
- package/dist/server.js +227 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ Stripe [syncing](./documentation/references/tables.md), subscriptions, [checkout
|
|
|
17
17
|
- [7. Run the `sync` Action](#7-run-the-sync-action)
|
|
18
18
|
- [8. Start Building](#8-start-building)
|
|
19
19
|
- [Configuration](#configuration)
|
|
20
|
+
- [Schema Configuration](#schema-configuration)
|
|
20
21
|
- [Stripe Configuration](#stripe-configuration)
|
|
21
22
|
- [Sync Configuration](#sync-configuration)
|
|
22
|
-
- [Table Selection](#table-selection)
|
|
23
23
|
- [Catalog (Unstable)](#catalog-unstable)
|
|
24
24
|
- [Webhook Configuration](#webhook-configuration)
|
|
25
25
|
- [Portal Configuration](#portal-configuration)
|
|
@@ -46,6 +46,11 @@ Stripe [syncing](./documentation/references/tables.md), subscriptions, [checkout
|
|
|
46
46
|
- [`stripe.client`](#stripeclient)
|
|
47
47
|
- [`sync` Action](#sync-action)
|
|
48
48
|
- [`store` Mutation](#store-mutation)
|
|
49
|
+
- [`stripe.helpers`](#stripehelpers)
|
|
50
|
+
- [`helpers.createCustomer`](#helperscreatecustomer)
|
|
51
|
+
- [`helpers.products`](#helpersproducts)
|
|
52
|
+
- [`helpers.subscription`](#helperssubscription)
|
|
53
|
+
- [`helpers.customer`](#helperscustomer)
|
|
49
54
|
- [Synced Tables](#synced-tables)
|
|
50
55
|
- [Synced Events](#synced-events)
|
|
51
56
|
- [Best Practices](#best-practices)
|
|
@@ -113,8 +118,7 @@ export default defineSchema({
|
|
|
113
118
|
});
|
|
114
119
|
```
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
> Ensure that your table configuration in `convex/schema.ts` matches your `sync.tables` configuration in `convex/stripe.ts` exactly! If your `sync.tables` tries to sync a table that is excluded from your schema, Convex Stripe will throw a runtime error when processing webhooks.
|
|
121
|
+
The package will only sync the tables you defined.
|
|
118
122
|
|
|
119
123
|
See [Tables Reference](./documentation/references/tables.md) for the full list of tables and their schemas.
|
|
120
124
|
|
|
@@ -125,16 +129,16 @@ Call `internalConvexStripe` with your Stripe credentials and sync configuration.
|
|
|
125
129
|
```ts
|
|
126
130
|
// convex/stripe.ts
|
|
127
131
|
|
|
128
|
-
import { internalConvexStripe
|
|
132
|
+
import { internalConvexStripe } from "@raideno/convex-stripe/server";
|
|
133
|
+
|
|
134
|
+
import schema from "./schema";
|
|
129
135
|
|
|
130
136
|
export const { stripe, store, sync } = internalConvexStripe({
|
|
137
|
+
schema: schema,
|
|
131
138
|
stripe: {
|
|
132
139
|
secret_key: process.env.STRIPE_SECRET_KEY!,
|
|
133
140
|
account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,
|
|
134
141
|
},
|
|
135
|
-
sync: {
|
|
136
|
-
tables: syncAllTables(),
|
|
137
|
-
},
|
|
138
142
|
});
|
|
139
143
|
```
|
|
140
144
|
|
|
@@ -260,6 +264,9 @@ The `internalConvexStripe` function accepts a configuration object and an option
|
|
|
260
264
|
```ts
|
|
261
265
|
const { stripe, store, sync } = internalConvexStripe(configuration, options);
|
|
262
266
|
```
|
|
267
|
+
### Schema Configuration
|
|
268
|
+
|
|
269
|
+
The `schema` key holds your convex app schema, from it the package will infer which stripe tables should be synced or not depending on what stripeTables where defined.
|
|
263
270
|
|
|
264
271
|
### Stripe Configuration
|
|
265
272
|
|
|
@@ -276,29 +283,6 @@ The `stripe` key in the configuration object holds your Stripe credentials and A
|
|
|
276
283
|
|
|
277
284
|
The `sync` key controls which tables are synced and allows you to define catalog items, webhook endpoints, and billing portal configuration.
|
|
278
285
|
|
|
279
|
-
#### Table Selection
|
|
280
|
-
|
|
281
|
-
Three helper functions are provided to control which Stripe tables are synced into Convex:
|
|
282
|
-
|
|
283
|
-
```ts
|
|
284
|
-
import {
|
|
285
|
-
syncAllTables,
|
|
286
|
-
syncAllTablesExcept,
|
|
287
|
-
syncOnlyTables,
|
|
288
|
-
} from "@raideno/convex-stripe/server";
|
|
289
|
-
|
|
290
|
-
// Sync all 24 tables (default)
|
|
291
|
-
{ tables: syncAllTables() }
|
|
292
|
-
|
|
293
|
-
// Sync all tables except specific ones
|
|
294
|
-
{ tables: syncAllTablesExcept(["stripeReviews", "stripePlans"]) }
|
|
295
|
-
|
|
296
|
-
// Sync only specific tables
|
|
297
|
-
{ tables: syncOnlyTables(["stripeCustomers", "stripeSubscriptions", "stripeProducts", "stripePrices"]) }
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
All 24 available tables are listed in the [Synced Tables](#synced-tables) section.
|
|
301
|
-
|
|
302
286
|
#### Catalog (Unstable)
|
|
303
287
|
|
|
304
288
|
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.
|
|
@@ -307,7 +291,6 @@ The `sync.catalog` key lets you pre-define products and prices that should exist
|
|
|
307
291
|
internalConvexStripe({
|
|
308
292
|
// ...
|
|
309
293
|
sync: {
|
|
310
|
-
tables: syncAllTables(),
|
|
311
294
|
catalog: {
|
|
312
295
|
products: [
|
|
313
296
|
{ name: "Pro Plan", metadata: { convex_stripe_key: "pro" } },
|
|
@@ -502,15 +485,17 @@ Then update your configuration to include it:
|
|
|
502
485
|
```ts
|
|
503
486
|
// convex/stripe.ts
|
|
504
487
|
|
|
488
|
+
import { internalConvexStripe } from "@raideno/convex-stripe/server";
|
|
489
|
+
|
|
490
|
+
import schema from "./schema";
|
|
491
|
+
|
|
505
492
|
export const { stripe, store, sync } = internalConvexStripe({
|
|
493
|
+
schema: schema,
|
|
506
494
|
stripe: {
|
|
507
495
|
secret_key: process.env.STRIPE_SECRET_KEY!,
|
|
508
496
|
account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,
|
|
509
497
|
connect_webhook_secret: process.env.STRIPE_CONNECT_WEBHOOK_SECRET!,
|
|
510
|
-
}
|
|
511
|
-
sync: {
|
|
512
|
-
tables: syncAllTables(),
|
|
513
|
-
},
|
|
498
|
+
}
|
|
514
499
|
});
|
|
515
500
|
```
|
|
516
501
|
|
|
@@ -817,6 +802,151 @@ This action is typically called manually from the Convex dashboard, or set up to
|
|
|
817
802
|
|
|
818
803
|
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.
|
|
819
804
|
|
|
805
|
+
### `stripe.helpers`
|
|
806
|
+
|
|
807
|
+
Returns a set of pre-built, authorization-aware Convex functions that cover the most common Stripe operations. These are ready to export and call from your frontend directly — no boilerplate required.
|
|
808
|
+
|
|
809
|
+
Each returned function invokes your `authenticateAndAuthorize` callback to resolve the caller's identity before delegating to the underlying Stripe implementation. When a caller omits `entityId`, it means they want to act on themselves — your callback is responsible for deriving their identity from the Convex `context`.
|
|
810
|
+
|
|
811
|
+
stripe.helpers({
|
|
812
|
+
authenticateAndAuthorize: async ({ context, operation, entityId }) => {
|
|
813
|
+
// Return [isAuthorized, resolvedEntityId | null]
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
**`authenticateAndAuthorize` parameters (passed as a single object):**
|
|
819
|
+
|
|
820
|
+
| Parameter | Type | Description |
|
|
821
|
+
| :---------- | :--------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
822
|
+
| `context` | `GenericActionCtx<any>` or `GenericQueryCtx<any>` | The Convex context, **narrowed by `operation`**: action ops (`createCustomer`, `subscribe`, `pay`, `portal`) receive `GenericActionCtx`; query ops (`products`, `subscription`, `customer`) receive `GenericQueryCtx`. |
|
|
823
|
+
| `operation` | `"createCustomer" \| "subscribe" \| "pay" \| "products" \| "subscription" \| "customer" \| "portal"` | The operation being performed. |
|
|
824
|
+
| `entityId` | `string \| undefined` | The entity ID passed by the caller, or `undefined` if acting on themselves. |
|
|
825
|
+
|
|
826
|
+
**Returns:** `Promise<[boolean, string | null]>` — `[isAuthorized, entityId]`.
|
|
827
|
+
|
|
828
|
+
**Returned functions:**
|
|
829
|
+
|
|
830
|
+
| Name | Kind | Description |
|
|
831
|
+
| :--------------- | :--------------- | :------------------------------------------- |
|
|
832
|
+
| `createCustomer` | `internalAction` | Create a Stripe customer for a given entity. |
|
|
833
|
+
| `products` | `query` | List all synced products with their prices. |
|
|
834
|
+
| `subscription` | `query` | Get the entity's active subscription. |
|
|
835
|
+
| `customer` | `query` | Get the entity's Stripe customer record. |
|
|
836
|
+
|
|
837
|
+
## Pre-built Helper Functions
|
|
838
|
+
|
|
839
|
+
`stripe.helpers()` is the fastest way to add Stripe to your app. Instead of hand-writing each Convex function, call `stripe.helpers()` once and export the returned functions:
|
|
840
|
+
|
|
841
|
+
```ts
|
|
842
|
+
// convex/stripe.ts
|
|
843
|
+
import { getAuthUserId } from "@convex-dev/auth/server";
|
|
844
|
+
import { internalConvexStripe } from "@raideno/convex-stripe/server";
|
|
845
|
+
import type { HelperAuthCallback } from "@raideno/convex-stripe/server";
|
|
846
|
+
import schema from "./schema";
|
|
847
|
+
|
|
848
|
+
export const { stripe, store, sync } = internalConvexStripe({
|
|
849
|
+
schema,
|
|
850
|
+
stripe: {
|
|
851
|
+
secret_key: process.env.STRIPE_SECRET_KEY!,
|
|
852
|
+
account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// A single callback that authenticates every helper function.
|
|
857
|
+
// `context` is automatically typed as GenericActionCtx or GenericQueryCtx
|
|
858
|
+
// depending on which `operation` is being executed.
|
|
859
|
+
const authenticateAndAuthorize: HelperAuthCallback = async ({
|
|
860
|
+
context, // GenericActionCtx<any> or GenericQueryCtx<any>
|
|
861
|
+
operation,
|
|
862
|
+
entityId,
|
|
863
|
+
}) => {
|
|
864
|
+
// For product listings there is no specific entity — allow everyone.
|
|
865
|
+
if (operation === "products") return [true, null];
|
|
866
|
+
|
|
867
|
+
const userId = await getAuthUserId(context);
|
|
868
|
+
if (!userId) return [false, null];
|
|
869
|
+
|
|
870
|
+
// If the caller passed an explicit entityId, use it; otherwise they act on themselves.
|
|
871
|
+
return [true, entityId ?? userId];
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
export const {
|
|
875
|
+
createCustomer,
|
|
876
|
+
products,
|
|
877
|
+
subscription,
|
|
878
|
+
customer,
|
|
879
|
+
} = stripe.helpers({
|
|
880
|
+
authenticateAndAuthorize
|
|
881
|
+
});
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
Then register `createCustomer` with your auth callbacks (the same way as the manual approach), and call the rest from your frontend:
|
|
885
|
+
|
|
886
|
+
```ts
|
|
887
|
+
// frontend
|
|
888
|
+
const items = await convex.query(api.stripe.products, {});
|
|
889
|
+
const sub = await convex.query(api.stripe.subscription, {});
|
|
890
|
+
const me = await convex.query(api.stripe.customer, {});
|
|
891
|
+
|
|
892
|
+
// Return URLs are handled by helper config, so frontend calls stay clean.
|
|
893
|
+
const { url } = await convex.action(api.stripe.subscribe, {
|
|
894
|
+
priceId: "price_xxx",
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
const { url } = await convex.action(api.stripe.pay, {
|
|
898
|
+
referenceId: "order_123",
|
|
899
|
+
line_items: [{ price: "price_yyy", quantity: 1 }],
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const { url } = await convex.action(api.stripe.portal, {});
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### `helpers.createCustomer`
|
|
906
|
+
|
|
907
|
+
An **internal** Convex action that creates a Stripe customer for the given `entityId`. Designed to be called from auth lifecycle callbacks (e.g. `afterUserCreatedOrUpdated`), not from the frontend directly.
|
|
908
|
+
|
|
909
|
+
**Arguments:**
|
|
910
|
+
|
|
911
|
+
| Parameter | Type | Required | Description |
|
|
912
|
+
| :--------- | :------- | :------- | :-------------------------------------------------- |
|
|
913
|
+
| `entityId` | `string` | Yes | Your app's internal identifier for the entity. |
|
|
914
|
+
| `email` | `string` | No | Email address to set on the Stripe customer record. |
|
|
915
|
+
|
|
916
|
+
**Returns:** The Stripe customer document from your Convex database.
|
|
917
|
+
|
|
918
|
+
### `helpers.products`
|
|
919
|
+
|
|
920
|
+
A **public** Convex query that returns all synced Stripe products with their associated prices nested inside.
|
|
921
|
+
|
|
922
|
+
**Arguments:** none
|
|
923
|
+
|
|
924
|
+
**Returns:** An array of product documents from `stripeProducts`, each with a `prices` field containing all related entries from `stripePrices`.
|
|
925
|
+
|
|
926
|
+
### `helpers.subscription`
|
|
927
|
+
|
|
928
|
+
A **public** Convex query that returns the active Stripe subscription for the authenticated entity, or `null` if they have no subscription.
|
|
929
|
+
|
|
930
|
+
**Arguments:**
|
|
931
|
+
|
|
932
|
+
| Parameter | Type | Required | Description |
|
|
933
|
+
| :--------- | :------- | :------- | :-------------------------------------------- |
|
|
934
|
+
| `entityId` | `string` | No | Override the entity (defaults to the caller). |
|
|
935
|
+
|
|
936
|
+
**Returns:** The subscription document from `stripeSubscriptions`, or `null`.
|
|
937
|
+
|
|
938
|
+
### `helpers.customer`
|
|
939
|
+
|
|
940
|
+
A **public** Convex query that returns the Stripe customer record for the authenticated entity, or `null` if they have no customer yet.
|
|
941
|
+
|
|
942
|
+
**Arguments:**
|
|
943
|
+
|
|
944
|
+
| Parameter | Type | Required | Description |
|
|
945
|
+
| :--------- | :------- | :------- | :-------------------------------------------- |
|
|
946
|
+
| `entityId` | `string` | No | Override the entity (defaults to the caller). |
|
|
947
|
+
|
|
948
|
+
**Returns:** The customer document from `stripeCustomers`, or `null`.
|
|
949
|
+
|
|
820
950
|
## Synced Tables
|
|
821
951
|
|
|
822
952
|
The library automatically syncs the following 24 Stripe resource types into your Convex database:
|
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,13 @@ import { GenericActionCtx } from 'convex/server';
|
|
|
4
4
|
import { GenericDataModel } from 'convex/server';
|
|
5
5
|
import { GenericId } from 'convex/values';
|
|
6
6
|
import { GenericMutationCtx } from 'convex/server';
|
|
7
|
+
import { GenericQueryCtx } from 'convex/server';
|
|
8
|
+
import { GenericSchema } from 'convex/server';
|
|
7
9
|
import { HttpRouter } from 'convex/server';
|
|
8
10
|
import { Infer } from 'convex/values';
|
|
9
11
|
import { RegisteredAction } from 'convex/server';
|
|
10
12
|
import { RegisteredMutation } from 'convex/server';
|
|
13
|
+
import { RegisteredQuery } from 'convex/server';
|
|
11
14
|
import { RoutableMethod } from 'convex/server';
|
|
12
15
|
import { SchemaDefinition } from 'convex/server';
|
|
13
16
|
import { TableDefinition } from 'convex/server';
|
|
@@ -23,12 +26,34 @@ import { VRecord } from 'convex/values';
|
|
|
23
26
|
import { VString } from 'convex/values';
|
|
24
27
|
import { VUnion } from 'convex/values';
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The operations handled by action-type helpers (run in an action context).
|
|
31
|
+
*/
|
|
32
|
+
declare type ActionOperation = "createCustomer";
|
|
33
|
+
|
|
26
34
|
declare type AllRedirectHandlers = (typeof REDIRECT_HANDLERS)[number];
|
|
27
35
|
|
|
28
36
|
export declare const allStripeTablesExcept: (tables: Array<keyof typeof stripeTables>) => typeof stripeTables;
|
|
29
37
|
|
|
30
38
|
declare type ArgSchema = Record<string, Validator<any, "optional" | "required", any>>;
|
|
31
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Arguments passed to the `authenticateAndAuthorize` callback.
|
|
42
|
+
*
|
|
43
|
+
* This is a discriminated union: when `operation` is an action-type operation,
|
|
44
|
+
* `context` is narrowed to `GenericActionCtx<any>`. For query-type operations,
|
|
45
|
+
* it is narrowed to `GenericQueryCtx<any>`.
|
|
46
|
+
*/
|
|
47
|
+
declare type AuthArgs = {
|
|
48
|
+
context: GenericActionCtx<any>;
|
|
49
|
+
operation: ActionOperation;
|
|
50
|
+
entityId?: string;
|
|
51
|
+
} | {
|
|
52
|
+
context: GenericQueryCtx<any>;
|
|
53
|
+
operation: QueryOperation;
|
|
54
|
+
entityId?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
32
57
|
export declare function buildSignedReturnUrl<O extends ReturnOrigin>({ configuration, origin, failureUrl, targetUrl, data, }: {
|
|
33
58
|
configuration: InternalConfiguration;
|
|
34
59
|
origin: O;
|
|
@@ -169,6 +194,32 @@ export declare function defineRedirectHandler<const T extends readonly string[],
|
|
|
169
194
|
|
|
170
195
|
export declare function defineWebhookHandler<const T extends default_2.Event.Type>(handler: WebhookHandler<T>): WebhookHandler<T>;
|
|
171
196
|
|
|
197
|
+
/**
|
|
198
|
+
* The callback used by `stripe.helpers()` to authenticate and authorize
|
|
199
|
+
* the caller for a given operation.
|
|
200
|
+
*
|
|
201
|
+
* **Contract:**
|
|
202
|
+
* - Return `[true, entityId]` when the caller is authenticated and the
|
|
203
|
+
* resolved `entityId` should be used for the Stripe operation.
|
|
204
|
+
* - Return `[false, null]` (or throw) to reject the request.
|
|
205
|
+
* - When no `entityId` is passed to the helper function, it means the
|
|
206
|
+
* caller wants to act on themselves — the callback is responsible for
|
|
207
|
+
* deriving their identity from `context` (e.g. via `getAuthUserId`).
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* const authenticateAndAuthorize: HelperAuthCallback = async ({
|
|
211
|
+
* context,
|
|
212
|
+
* operation,
|
|
213
|
+
* entityId,
|
|
214
|
+
* }) => {
|
|
215
|
+
* if (operation === "products") return [true, null];
|
|
216
|
+
* const userId = await getAuthUserId(context);
|
|
217
|
+
* if (!userId) return [false, null];
|
|
218
|
+
* return [true, entityId ?? userId];
|
|
219
|
+
* };
|
|
220
|
+
*/
|
|
221
|
+
export declare type HelperAuthCallback = (args: AuthArgs) => Promise<[boolean, string | null]>;
|
|
222
|
+
|
|
172
223
|
declare type InferArgs<S extends ArgSchema> = {
|
|
173
224
|
[K in keyof S as S[K] extends Validator<any, "required", any> ? K : never]: Infer<S[K]>;
|
|
174
225
|
} & {
|
|
@@ -176,6 +227,7 @@ declare type InferArgs<S extends ArgSchema> = {
|
|
|
176
227
|
};
|
|
177
228
|
|
|
178
229
|
export declare interface InputConfiguration {
|
|
230
|
+
schema: SchemaDefinition<GenericSchema, true>;
|
|
179
231
|
stripe: {
|
|
180
232
|
/** Stripe API version to pin against (recommended for stability). */
|
|
181
233
|
version?: default_2.StripeConfig["apiVersion"];
|
|
@@ -189,7 +241,7 @@ export declare interface InputConfiguration {
|
|
|
189
241
|
*/
|
|
190
242
|
connect_webhook_secret?: string;
|
|
191
243
|
};
|
|
192
|
-
sync
|
|
244
|
+
sync?: {
|
|
193
245
|
catalog?: {
|
|
194
246
|
/** Products to ensure exist in Stripe (optional bootstrap). */
|
|
195
247
|
products?: default_2.ProductCreateParams[];
|
|
@@ -235,7 +287,7 @@ export declare interface InputConfiguration {
|
|
|
235
287
|
/** Optional Billing Portal configuration to create/sync. */
|
|
236
288
|
portal?: default_2.BillingPortal.ConfigurationCreateParams;
|
|
237
289
|
/** Which Stripe tables you want to sync into Convex. */
|
|
238
|
-
tables
|
|
290
|
+
tables?: Record<keyof typeof stripeTables, boolean>;
|
|
239
291
|
};
|
|
240
292
|
callbacks?: {
|
|
241
293
|
/** Called after a row is inserted/upserted/deleted in your Stripe tables. */
|
|
@@ -282,7 +334,11 @@ export declare type InternalConfiguration = RecursiveDeepRequired<InputConfigura
|
|
|
282
334
|
* },
|
|
283
335
|
* });
|
|
284
336
|
*/
|
|
285
|
-
export declare const internalConvexStripe: (configuration_: InputConfiguration
|
|
337
|
+
export declare const internalConvexStripe: (configuration_: InputConfiguration & {
|
|
338
|
+
sync?: {
|
|
339
|
+
tables?: never;
|
|
340
|
+
};
|
|
341
|
+
}, options_?: InputOptions) => {
|
|
286
342
|
stripe: {
|
|
287
343
|
/** Raw HTTP handler descriptors. Prefer `addHttpRoutes` unless you need manual control. */
|
|
288
344
|
http: {
|
|
@@ -318,7 +374,7 @@ export declare const internalConvexStripe: (configuration_: InputConfiguration,
|
|
|
318
374
|
* stripe.addHttpRoutes(http);
|
|
319
375
|
* export default http;
|
|
320
376
|
*/
|
|
321
|
-
addHttpRoutes: (http: HttpRouter
|
|
377
|
+
addHttpRoutes: (http: HttpRouter) => void;
|
|
322
378
|
/**
|
|
323
379
|
* Opens a Stripe Billing Portal session for an existing customer.
|
|
324
380
|
* Use this to let users manage their subscription, invoices, and payment methods.
|
|
@@ -471,6 +527,283 @@ export declare const internalConvexStripe: (configuration_: InputConfiguration,
|
|
|
471
527
|
*/
|
|
472
528
|
link: (context: GenericActionCtx<any>, args: Parameters<(typeof CreateAccountLinkImplementation)["handler"]>[1], options?: Parameters<(typeof CreateAccountLinkImplementation)["handler"]>[2]) => Promise<default_2.Response<default_2.AccountLink>>;
|
|
473
529
|
};
|
|
530
|
+
/**
|
|
531
|
+
* Returns a set of pre-built, authorization-aware Convex functions
|
|
532
|
+
* (actions and queries) that cover the most common Stripe operations.
|
|
533
|
+
*
|
|
534
|
+
* Each returned function calls your `authenticateAndAuthorize` callback
|
|
535
|
+
* to resolve the caller's identity and enforce access control before
|
|
536
|
+
* delegating to the underlying Stripe implementation.
|
|
537
|
+
*
|
|
538
|
+
* When a caller omits `entityId`, it signals that they want to act on
|
|
539
|
+
* themselves — your callback is responsible for deriving their identity
|
|
540
|
+
* from `context` (e.g. via `getAuthUserId`).
|
|
541
|
+
*
|
|
542
|
+
* **Returned functions:**
|
|
543
|
+
* - `internalCreateCustomer` — internal action: create a Stripe customer for a given entity.
|
|
544
|
+
* - `subscribe` — public action: create a subscription checkout session.
|
|
545
|
+
* - `pay` — public action: create a one-time payment checkout session.
|
|
546
|
+
* - `products` — public query: list all products with their prices.
|
|
547
|
+
* - `subscription` — public query: get the entity's active subscription.
|
|
548
|
+
* - `customer` — public query: get the entity's Stripe customer record.
|
|
549
|
+
* - `portal` — public action: open a Billing Portal session.
|
|
550
|
+
*
|
|
551
|
+
* @param config - Configuration for the helpers.
|
|
552
|
+
* @param config.authenticateAndAuthorize - Callback that authenticates the
|
|
553
|
+
* caller and returns `[isAuthorized, entityId | null]`.
|
|
554
|
+
* @param config.urls - Centralized return URL configuration (required). URLs are grouped
|
|
555
|
+
* by operation: `subscribe`, `pay`, and `portal`.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* // convex/stripe.ts
|
|
559
|
+
* import { getAuthUserId } from "@convex-dev/auth/server";
|
|
560
|
+
*
|
|
561
|
+
* export const { stripe, store, sync } = internalConvexStripe({ ... });
|
|
562
|
+
*
|
|
563
|
+
* export const { internalCreateCustomer, subscribe, pay, products, subscription, customer, portal } =
|
|
564
|
+
* stripe.helpers({
|
|
565
|
+
* authenticateAndAuthorize: async ({ context, operation, entityId }) => {
|
|
566
|
+
* const userId = await getAuthUserId(context);
|
|
567
|
+
* if (!userId) return [false, null];
|
|
568
|
+
* // If caller passed an explicit entityId, use it; otherwise act on themselves.
|
|
569
|
+
* return [true, entityId ?? userId];
|
|
570
|
+
* },
|
|
571
|
+
* urls: {
|
|
572
|
+
* subscribe: {
|
|
573
|
+
* success: "https://example.com/success",
|
|
574
|
+
* cancel: "https://example.com/cancel",
|
|
575
|
+
* failure: "https://example.com/error",
|
|
576
|
+
* },
|
|
577
|
+
* pay: {
|
|
578
|
+
* success: "https://example.com/pay-success",
|
|
579
|
+
* cancel: "https://example.com/pay-cancel",
|
|
580
|
+
* failure: "https://example.com/pay-error",
|
|
581
|
+
* },
|
|
582
|
+
* portal: {
|
|
583
|
+
* return: "https://example.com/account",
|
|
584
|
+
* failure: "https://example.com/portal-error",
|
|
585
|
+
* },
|
|
586
|
+
* },
|
|
587
|
+
* });
|
|
588
|
+
*/
|
|
589
|
+
helpers: (config: StripeHelpersConfig) => {
|
|
590
|
+
createCustomer: RegisteredAction<"internal", {
|
|
591
|
+
email?: string | undefined;
|
|
592
|
+
entityId: string;
|
|
593
|
+
}, Promise<{
|
|
594
|
+
_id: GenericId<"stripeCustomers">;
|
|
595
|
+
_creationTime: number;
|
|
596
|
+
accountId?: string | undefined;
|
|
597
|
+
entityId?: string | undefined;
|
|
598
|
+
stripe: {
|
|
599
|
+
email?: string | null | undefined;
|
|
600
|
+
metadata?: Record<string, string | number | null> | null | undefined;
|
|
601
|
+
address?: {
|
|
602
|
+
country?: string | null | undefined;
|
|
603
|
+
city?: string | null | undefined;
|
|
604
|
+
line1?: string | null | undefined;
|
|
605
|
+
line2?: string | null | undefined;
|
|
606
|
+
postal_code?: string | null | undefined;
|
|
607
|
+
state?: string | null | undefined;
|
|
608
|
+
} | null | undefined;
|
|
609
|
+
name?: string | null | undefined;
|
|
610
|
+
phone?: string | null | undefined;
|
|
611
|
+
shipping?: {
|
|
612
|
+
address?: {
|
|
613
|
+
country?: string | null | undefined;
|
|
614
|
+
city?: string | null | undefined;
|
|
615
|
+
line1?: string | null | undefined;
|
|
616
|
+
line2?: string | null | undefined;
|
|
617
|
+
postal_code?: string | null | undefined;
|
|
618
|
+
state?: string | null | undefined;
|
|
619
|
+
} | undefined;
|
|
620
|
+
name?: string | undefined;
|
|
621
|
+
phone?: string | null | undefined;
|
|
622
|
+
carrier?: string | null | undefined;
|
|
623
|
+
tracking_number?: string | null | undefined;
|
|
624
|
+
} | null | undefined;
|
|
625
|
+
currency?: string | null | undefined;
|
|
626
|
+
description?: string | null | undefined;
|
|
627
|
+
discount?: any;
|
|
628
|
+
tax?: {
|
|
629
|
+
ip_address?: string | null | undefined;
|
|
630
|
+
automatic_tax: "failed" | "not_collecting" | "supported" | "unrecognized_location";
|
|
631
|
+
location: {
|
|
632
|
+
state?: string | null | undefined;
|
|
633
|
+
country: string;
|
|
634
|
+
source: string;
|
|
635
|
+
} | null;
|
|
636
|
+
} | undefined;
|
|
637
|
+
cash_balance?: any;
|
|
638
|
+
default_source?: string | null | undefined;
|
|
639
|
+
delinquent?: boolean | null | undefined;
|
|
640
|
+
invoice_credit_balance?: any;
|
|
641
|
+
invoice_prefix?: string | null | undefined;
|
|
642
|
+
invoice_settings?: any;
|
|
643
|
+
next_invoice_sequence?: number | null | undefined;
|
|
644
|
+
preferred_locales?: string[] | null | undefined;
|
|
645
|
+
sources?: any;
|
|
646
|
+
subscriptions?: any;
|
|
647
|
+
tax_exempt?: "none" | "reverse" | "exempt" | null | undefined;
|
|
648
|
+
tax_ids?: any;
|
|
649
|
+
test_clock?: string | null | undefined;
|
|
650
|
+
object: string;
|
|
651
|
+
id: string;
|
|
652
|
+
created: number;
|
|
653
|
+
livemode: boolean;
|
|
654
|
+
balance: number;
|
|
655
|
+
};
|
|
656
|
+
lastSyncedAt: number;
|
|
657
|
+
customerId: string;
|
|
658
|
+
}>>;
|
|
659
|
+
products: RegisteredQuery<"public", {}, Promise<{
|
|
660
|
+
prices: {
|
|
661
|
+
_id: GenericId<"stripePrices">;
|
|
662
|
+
_creationTime: number;
|
|
663
|
+
accountId?: string | undefined;
|
|
664
|
+
stripe: {
|
|
665
|
+
metadata?: Record<string, string | number | null> | null | undefined;
|
|
666
|
+
object: string;
|
|
667
|
+
type: "one_time" | "recurring";
|
|
668
|
+
id: string;
|
|
669
|
+
created: number;
|
|
670
|
+
active: boolean;
|
|
671
|
+
livemode: boolean;
|
|
672
|
+
currency: string;
|
|
673
|
+
nickname: string | null;
|
|
674
|
+
billing_scheme: "per_unit" | "tiered";
|
|
675
|
+
tiers_mode: "graduated" | "volume" | null;
|
|
676
|
+
recurring: {
|
|
677
|
+
interval: "day" | "week" | "month" | "year";
|
|
678
|
+
interval_count: number;
|
|
679
|
+
meter: string | null;
|
|
680
|
+
trial_period_days: number | null;
|
|
681
|
+
usage_type: "licensed" | "metered";
|
|
682
|
+
} | null;
|
|
683
|
+
productId: string;
|
|
684
|
+
unit_amount: number | null;
|
|
685
|
+
lookup_key: string | null;
|
|
686
|
+
transform_quantity: {
|
|
687
|
+
divide_by: number;
|
|
688
|
+
round: "up" | "down";
|
|
689
|
+
} | null;
|
|
690
|
+
unit_amount_decimal: string | null;
|
|
691
|
+
};
|
|
692
|
+
lastSyncedAt: number;
|
|
693
|
+
priceId: string;
|
|
694
|
+
}[];
|
|
695
|
+
_id: GenericId<"stripeProducts">;
|
|
696
|
+
_creationTime: number;
|
|
697
|
+
accountId?: string | undefined;
|
|
698
|
+
stripe: {
|
|
699
|
+
metadata?: Record<string, string | number | null> | null | undefined;
|
|
700
|
+
statement_descriptor?: string | null | undefined;
|
|
701
|
+
unit_label?: string | null | undefined;
|
|
702
|
+
object: string;
|
|
703
|
+
id: string;
|
|
704
|
+
created: number;
|
|
705
|
+
active: boolean;
|
|
706
|
+
name: string;
|
|
707
|
+
url: string | null;
|
|
708
|
+
livemode: boolean;
|
|
709
|
+
updated: number;
|
|
710
|
+
description: string | null;
|
|
711
|
+
images: string[];
|
|
712
|
+
package_dimensions: {
|
|
713
|
+
length: number;
|
|
714
|
+
height: number;
|
|
715
|
+
weight: number;
|
|
716
|
+
width: number;
|
|
717
|
+
} | null;
|
|
718
|
+
shippable: boolean | null;
|
|
719
|
+
marketing_features: {
|
|
720
|
+
name?: string | null | undefined;
|
|
721
|
+
}[];
|
|
722
|
+
default_price: string | null;
|
|
723
|
+
};
|
|
724
|
+
productId: string;
|
|
725
|
+
lastSyncedAt: number;
|
|
726
|
+
}[]>>;
|
|
727
|
+
subscription: RegisteredQuery<"public", {
|
|
728
|
+
entityId?: string | undefined;
|
|
729
|
+
}, Promise<{
|
|
730
|
+
_id: GenericId<"stripeSubscriptions">;
|
|
731
|
+
_creationTime: number;
|
|
732
|
+
accountId?: string | undefined;
|
|
733
|
+
stripe: any;
|
|
734
|
+
lastSyncedAt: number;
|
|
735
|
+
customerId: string;
|
|
736
|
+
subscriptionId: string | null;
|
|
737
|
+
} | null>>;
|
|
738
|
+
customer: RegisteredQuery<"public", {
|
|
739
|
+
entityId?: string | undefined;
|
|
740
|
+
}, Promise<{
|
|
741
|
+
_id: GenericId<"stripeCustomers">;
|
|
742
|
+
_creationTime: number;
|
|
743
|
+
accountId?: string | undefined;
|
|
744
|
+
entityId?: string | undefined;
|
|
745
|
+
stripe: {
|
|
746
|
+
email?: string | null | undefined;
|
|
747
|
+
metadata?: Record<string, string | number | null> | null | undefined;
|
|
748
|
+
address?: {
|
|
749
|
+
country?: string | null | undefined;
|
|
750
|
+
city?: string | null | undefined;
|
|
751
|
+
line1?: string | null | undefined;
|
|
752
|
+
line2?: string | null | undefined;
|
|
753
|
+
postal_code?: string | null | undefined;
|
|
754
|
+
state?: string | null | undefined;
|
|
755
|
+
} | null | undefined;
|
|
756
|
+
name?: string | null | undefined;
|
|
757
|
+
phone?: string | null | undefined;
|
|
758
|
+
shipping?: {
|
|
759
|
+
address?: {
|
|
760
|
+
country?: string | null | undefined;
|
|
761
|
+
city?: string | null | undefined;
|
|
762
|
+
line1?: string | null | undefined;
|
|
763
|
+
line2?: string | null | undefined;
|
|
764
|
+
postal_code?: string | null | undefined;
|
|
765
|
+
state?: string | null | undefined;
|
|
766
|
+
} | undefined;
|
|
767
|
+
name?: string | undefined;
|
|
768
|
+
phone?: string | null | undefined;
|
|
769
|
+
carrier?: string | null | undefined;
|
|
770
|
+
tracking_number?: string | null | undefined;
|
|
771
|
+
} | null | undefined;
|
|
772
|
+
currency?: string | null | undefined;
|
|
773
|
+
description?: string | null | undefined;
|
|
774
|
+
discount?: any;
|
|
775
|
+
tax?: {
|
|
776
|
+
ip_address?: string | null | undefined;
|
|
777
|
+
automatic_tax: "failed" | "not_collecting" | "supported" | "unrecognized_location";
|
|
778
|
+
location: {
|
|
779
|
+
state?: string | null | undefined;
|
|
780
|
+
country: string;
|
|
781
|
+
source: string;
|
|
782
|
+
} | null;
|
|
783
|
+
} | undefined;
|
|
784
|
+
cash_balance?: any;
|
|
785
|
+
default_source?: string | null | undefined;
|
|
786
|
+
delinquent?: boolean | null | undefined;
|
|
787
|
+
invoice_credit_balance?: any;
|
|
788
|
+
invoice_prefix?: string | null | undefined;
|
|
789
|
+
invoice_settings?: any;
|
|
790
|
+
next_invoice_sequence?: number | null | undefined;
|
|
791
|
+
preferred_locales?: string[] | null | undefined;
|
|
792
|
+
sources?: any;
|
|
793
|
+
subscriptions?: any;
|
|
794
|
+
tax_exempt?: "none" | "reverse" | "exempt" | null | undefined;
|
|
795
|
+
tax_ids?: any;
|
|
796
|
+
test_clock?: string | null | undefined;
|
|
797
|
+
object: string;
|
|
798
|
+
id: string;
|
|
799
|
+
created: number;
|
|
800
|
+
livemode: boolean;
|
|
801
|
+
balance: number;
|
|
802
|
+
};
|
|
803
|
+
lastSyncedAt: number;
|
|
804
|
+
customerId: string;
|
|
805
|
+
} | null>>;
|
|
806
|
+
};
|
|
474
807
|
};
|
|
475
808
|
/**
|
|
476
809
|
* Internal Convex mutation that persists a Stripe object into your database.
|
|
@@ -1656,6 +1989,11 @@ declare const PortalImplementation: {
|
|
|
1656
1989
|
} & Omit<default_2.BillingPortal.SessionCreateParams, "customer" | "return_url">, stripeOptions: default_2.RequestOptions, configuration: InternalConfiguration, options: InternalOptions) => Promise<default_2.Response<default_2.BillingPortal.Session>>;
|
|
1657
1990
|
};
|
|
1658
1991
|
|
|
1992
|
+
/**
|
|
1993
|
+
* The operations handled by query-type helpers (run in a query context).
|
|
1994
|
+
*/
|
|
1995
|
+
declare type QueryOperation = "products" | "subscription" | "customer";
|
|
1996
|
+
|
|
1659
1997
|
declare type RecursiveDeepRequired<T> = T extends (...args: any[]) => any ? T : T extends object ? {
|
|
1660
1998
|
[K in keyof T]-?: RecursiveDeepRequired<T[K]>;
|
|
1661
1999
|
} : T;
|
|
@@ -1691,6 +2029,16 @@ declare type ReturnOrigin = (typeof RETURN_ORIGINS)[number];
|
|
|
1691
2029
|
|
|
1692
2030
|
declare type StripeDataModel = DataModelFromSchemaDefinition<typeof stripeSchema>;
|
|
1693
2031
|
|
|
2032
|
+
/**
|
|
2033
|
+
* Configuration for the pre-built Stripe helpers.
|
|
2034
|
+
*/
|
|
2035
|
+
export declare interface StripeHelpersConfig {
|
|
2036
|
+
/**
|
|
2037
|
+
* Callback that authenticates the caller and returns [isAuthorized, resolvedEntityId].
|
|
2038
|
+
*/
|
|
2039
|
+
authenticateAndAuthorize: HelperAuthCallback;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
1694
2042
|
declare const stripeSchema: SchemaDefinition< {
|
|
1695
2043
|
stripeAccounts: TableDefinition<VObject< {
|
|
1696
2044
|
entityId?: string | undefined;
|
|
@@ -9312,12 +9660,6 @@ export declare const SYNC_HANDLERS: readonly [{
|
|
|
9312
9660
|
}, configuration: InternalConfiguration, options: InternalOptions) => Promise<void>;
|
|
9313
9661
|
}];
|
|
9314
9662
|
|
|
9315
|
-
export declare const syncAllTables: () => Record<keyof typeof stripeTables, boolean>;
|
|
9316
|
-
|
|
9317
|
-
export declare const syncAllTablesExcept: (tables: Array<keyof typeof stripeTables>) => Record<keyof typeof stripeTables, boolean>;
|
|
9318
|
-
|
|
9319
|
-
export declare const syncOnlyTables: (tables: Array<keyof typeof stripeTables>) => Record<keyof typeof stripeTables, boolean>;
|
|
9320
|
-
|
|
9321
9663
|
export declare const WEBHOOK_HANDLERS: readonly [WebhookHandler<"account.updated">, WebhookHandler<"billing_portal.configuration.created" | "billing_portal.configuration.updated">, WebhookHandler<"capability.updated">, WebhookHandler<"charge.captured" | "charge.expired" | "charge.failed" | "charge.pending" | "charge.refunded" | "charge.succeeded" | "charge.updated">, WebhookHandler<"checkout.session.async_payment_failed" | "checkout.session.async_payment_succeeded" | "checkout.session.completed" | "checkout.session.expired">, WebhookHandler<"coupon.created" | "coupon.deleted" | "coupon.updated">, WebhookHandler<"credit_note.created" | "credit_note.updated" | "credit_note.voided">, WebhookHandler<"customer.created" | "customer.deleted" | "customer.updated">, WebhookHandler<"charge.dispute.closed" | "charge.dispute.created" | "charge.dispute.funds_reinstated" | "charge.dispute.funds_withdrawn" | "charge.dispute.updated">, WebhookHandler<"radar.early_fraud_warning.created" | "radar.early_fraud_warning.updated">, WebhookHandler<"invoice.created" | "invoice.deleted" | "invoice.finalization_failed" | "invoice.finalized" | "invoice.marked_uncollectible" | "invoice.overdue" | "invoice.overpaid" | "invoice.paid" | "invoice.payment_action_required" | "invoice.payment_failed" | "invoice.payment_succeeded" | "invoice.sent" | "invoice.upcoming" | "invoice.updated" | "invoice.voided" | "invoice.will_be_due">, WebhookHandler<"mandate.updated">, WebhookHandler<"payment_intent.amount_capturable_updated" | "payment_intent.canceled" | "payment_intent.created" | "payment_intent.partially_funded" | "payment_intent.payment_failed" | "payment_intent.processing" | "payment_intent.requires_action" | "payment_intent.succeeded">, WebhookHandler<"payment_method.attached" | "payment_method.automatically_updated" | "payment_method.detached" | "payment_method.updated">, WebhookHandler<"payout.canceled" | "payout.created" | "payout.failed" | "payout.paid" | "payout.reconciliation_completed" | "payout.updated">, WebhookHandler<"plan.created" | "plan.deleted" | "plan.updated">, WebhookHandler<"price.created" | "price.deleted" | "price.updated">, WebhookHandler<"product.created" | "product.deleted" | "product.updated">, WebhookHandler<"promotion_code.created" | "promotion_code.updated">, WebhookHandler<"charge.refund.updated" | "refund.created" | "refund.failed" | "refund.updated">, WebhookHandler<"review.closed" | "review.opened">, WebhookHandler<"setup_intent.canceled" | "setup_intent.created" | "setup_intent.requires_action" | "setup_intent.setup_failed" | "setup_intent.succeeded">, WebhookHandler<"subscription_schedule.aborted" | "subscription_schedule.canceled" | "subscription_schedule.completed" | "subscription_schedule.created" | "subscription_schedule.expiring" | "subscription_schedule.released" | "subscription_schedule.updated">, WebhookHandler<"customer.subscription.created" | "customer.subscription.deleted" | "customer.subscription.paused" | "customer.subscription.pending_update_applied" | "customer.subscription.pending_update_expired" | "customer.subscription.resumed" | "customer.subscription.trial_will_end" | "customer.subscription.updated">, WebhookHandler<"customer.tax_id.created" | "customer.tax_id.deleted" | "customer.tax_id.updated">, WebhookHandler<"transfer.created" | "transfer.reversed" | "transfer.updated">];
|
|
9322
9664
|
|
|
9323
9665
|
declare type WebhookHandler<TEvents extends default_2.Event.Type> = {
|
package/dist/server.js
CHANGED
|
@@ -2182,6 +2182,31 @@ const internalMutationGeneric = ((functionDefinition) => {
|
|
|
2182
2182
|
func._handler = handler;
|
|
2183
2183
|
return func;
|
|
2184
2184
|
});
|
|
2185
|
+
async function invokeQuery(func, argsStr) {
|
|
2186
|
+
const requestId = "";
|
|
2187
|
+
const args = jsonToConvex(JSON.parse(argsStr));
|
|
2188
|
+
const queryCtx = {
|
|
2189
|
+
db: setupReader(),
|
|
2190
|
+
auth: setupAuth(requestId),
|
|
2191
|
+
storage: setupStorageReader(requestId),
|
|
2192
|
+
runQuery: (reference, args2) => runUdf("query", reference, args2)
|
|
2193
|
+
};
|
|
2194
|
+
const result = await invokeFunction(func, queryCtx, args);
|
|
2195
|
+
validateReturnValue(result);
|
|
2196
|
+
return JSON.stringify(convexToJson(result === void 0 ? null : result));
|
|
2197
|
+
}
|
|
2198
|
+
const queryGeneric = ((functionDefinition) => {
|
|
2199
|
+
const handler = typeof functionDefinition === "function" ? functionDefinition : functionDefinition.handler;
|
|
2200
|
+
const func = dontCallDirectly("query", handler);
|
|
2201
|
+
assertNotBrowser();
|
|
2202
|
+
func.isQuery = true;
|
|
2203
|
+
func.isPublic = true;
|
|
2204
|
+
func.invokeQuery = (argsStr) => invokeQuery(handler, argsStr);
|
|
2205
|
+
func.exportArgs = exportArgs(functionDefinition);
|
|
2206
|
+
func.exportReturns = exportReturns(functionDefinition);
|
|
2207
|
+
func._handler = handler;
|
|
2208
|
+
return func;
|
|
2209
|
+
});
|
|
2185
2210
|
async function invokeAction(func, requestId, argsStr) {
|
|
2186
2211
|
const args = jsonToConvex(JSON.parse(argsStr));
|
|
2187
2212
|
const calls = setupActionCalls(requestId);
|
|
@@ -5612,21 +5637,6 @@ const stripeTables = {
|
|
|
5612
5637
|
}).index("byAccountId", ["accountId"]).index(BY_STRIPE_ID_INDEX_NAME, ["capabilityId"])
|
|
5613
5638
|
};
|
|
5614
5639
|
defineSchema(stripeTables);
|
|
5615
|
-
const syncAllTables = () => Object.fromEntries(
|
|
5616
|
-
Object.keys(stripeTables).map((table) => [table, true])
|
|
5617
|
-
);
|
|
5618
|
-
const syncAllTablesExcept = (tables) => Object.fromEntries(
|
|
5619
|
-
Object.keys(stripeTables).map((table) => [
|
|
5620
|
-
table,
|
|
5621
|
-
!tables.includes(table)
|
|
5622
|
-
])
|
|
5623
|
-
);
|
|
5624
|
-
const syncOnlyTables = (tables) => Object.fromEntries(
|
|
5625
|
-
Object.keys(stripeTables).map((table) => [
|
|
5626
|
-
table,
|
|
5627
|
-
tables.includes(table)
|
|
5628
|
-
])
|
|
5629
|
-
);
|
|
5630
5640
|
const allStripeTablesExcept = (tables) => Object.fromEntries(
|
|
5631
5641
|
Object.entries(stripeTables).map(([table, definition]) => [
|
|
5632
5642
|
table,
|
|
@@ -5640,6 +5650,7 @@ const onlyStripeTables = (tables) => Object.fromEntries(
|
|
|
5640
5650
|
])
|
|
5641
5651
|
);
|
|
5642
5652
|
const DEFAULT_CONFIGURATION = {
|
|
5653
|
+
schema: void 0,
|
|
5643
5654
|
stripe: {
|
|
5644
5655
|
version: "2025-08-27.basil",
|
|
5645
5656
|
secret_key: "",
|
|
@@ -5659,42 +5670,42 @@ const DEFAULT_CONFIGURATION = {
|
|
|
5659
5670
|
}
|
|
5660
5671
|
},
|
|
5661
5672
|
sync: {
|
|
5662
|
-
catalog: {
|
|
5663
|
-
products: [],
|
|
5664
|
-
prices: [],
|
|
5665
|
-
metadataKey: "convex_stripe_key",
|
|
5666
|
-
behavior: {
|
|
5667
|
-
onExisting: "update",
|
|
5668
|
-
onMissingKey: "create"
|
|
5669
|
-
}
|
|
5670
|
-
},
|
|
5671
5673
|
tables: {
|
|
5672
5674
|
stripeAccounts: true,
|
|
5675
|
+
stripeBillingPortalConfigurations: true,
|
|
5673
5676
|
stripeCapabilities: true,
|
|
5674
|
-
|
|
5677
|
+
stripeCharges: true,
|
|
5678
|
+
stripeCheckoutSessions: true,
|
|
5675
5679
|
stripeCoupons: true,
|
|
5680
|
+
stripeCreditNotes: true,
|
|
5676
5681
|
stripeCustomers: true,
|
|
5682
|
+
stripeDisputes: true,
|
|
5683
|
+
stripeEarlyFraudWarnings: true,
|
|
5684
|
+
stripeInvoices: true,
|
|
5685
|
+
stripeMandates: true,
|
|
5686
|
+
stripePaymentIntents: true,
|
|
5687
|
+
stripePaymentMethods: true,
|
|
5688
|
+
stripePayouts: true,
|
|
5689
|
+
stripePlans: true,
|
|
5677
5690
|
stripePrices: true,
|
|
5678
5691
|
stripeProducts: true,
|
|
5679
5692
|
stripePromotionCodes: true,
|
|
5680
|
-
stripeSubscriptions: true,
|
|
5681
|
-
stripePayouts: true,
|
|
5682
|
-
stripeCheckoutSessions: true,
|
|
5683
|
-
stripePaymentIntents: true,
|
|
5684
5693
|
stripeRefunds: true,
|
|
5685
|
-
stripeInvoices: true,
|
|
5686
5694
|
stripeReviews: true,
|
|
5687
|
-
stripeCharges: true,
|
|
5688
|
-
stripeCreditNotes: true,
|
|
5689
|
-
stripeDisputes: true,
|
|
5690
|
-
stripeEarlyFraudWarnings: true,
|
|
5691
|
-
stripePaymentMethods: true,
|
|
5692
|
-
stripePlans: true,
|
|
5693
5695
|
stripeSetupIntents: true,
|
|
5696
|
+
stripeSubscriptions: true,
|
|
5694
5697
|
stripeSubscriptionSchedules: true,
|
|
5695
5698
|
stripeTaxIds: true,
|
|
5696
|
-
|
|
5697
|
-
|
|
5699
|
+
stripeTransfers: true
|
|
5700
|
+
},
|
|
5701
|
+
catalog: {
|
|
5702
|
+
products: [],
|
|
5703
|
+
prices: [],
|
|
5704
|
+
metadataKey: "convex_stripe_key",
|
|
5705
|
+
behavior: {
|
|
5706
|
+
onExisting: "update",
|
|
5707
|
+
onMissingKey: "create"
|
|
5708
|
+
}
|
|
5698
5709
|
},
|
|
5699
5710
|
webhooks: {
|
|
5700
5711
|
account: {
|
|
@@ -5763,8 +5774,13 @@ const DEFAULT_CONFIGURATION = {
|
|
|
5763
5774
|
}
|
|
5764
5775
|
}
|
|
5765
5776
|
};
|
|
5766
|
-
const normalizeConfiguration = (
|
|
5767
|
-
|
|
5777
|
+
const normalizeConfiguration = (configuration) => {
|
|
5778
|
+
const output = deepmerge(DEFAULT_CONFIGURATION, configuration);
|
|
5779
|
+
const stripeTableNames = Object.keys(stripeTables);
|
|
5780
|
+
output.sync.tables = Object.fromEntries(
|
|
5781
|
+
Object.keys(configuration.schema.tables).filter((table) => stripeTableNames.includes(table)).map((table) => [table, true])
|
|
5782
|
+
);
|
|
5783
|
+
return output;
|
|
5768
5784
|
};
|
|
5769
5785
|
const DEFAULT_OPTIONS = {
|
|
5770
5786
|
store: "store",
|
|
@@ -6938,6 +6954,110 @@ const SubscribeImplementation = defineActionCallableFunction({
|
|
|
6938
6954
|
return checkout;
|
|
6939
6955
|
}
|
|
6940
6956
|
});
|
|
6957
|
+
const buildCreateCustomer = (configuration, options, authenticateAndAuthorize) => internalActionGeneric({
|
|
6958
|
+
args: {
|
|
6959
|
+
entityId: v.string(),
|
|
6960
|
+
email: v.optional(v.string())
|
|
6961
|
+
},
|
|
6962
|
+
handler: async (context_, args) => {
|
|
6963
|
+
const context = context_;
|
|
6964
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
6965
|
+
context,
|
|
6966
|
+
operation: "createCustomer",
|
|
6967
|
+
entityId: args.entityId
|
|
6968
|
+
});
|
|
6969
|
+
if (!authorized || !resolvedEntityId) {
|
|
6970
|
+
throw new Error("Unauthorized");
|
|
6971
|
+
}
|
|
6972
|
+
return CreateCustomerImplementation.handler(
|
|
6973
|
+
context,
|
|
6974
|
+
{ entityId: resolvedEntityId, email: args.email },
|
|
6975
|
+
{},
|
|
6976
|
+
configuration,
|
|
6977
|
+
options
|
|
6978
|
+
);
|
|
6979
|
+
}
|
|
6980
|
+
});
|
|
6981
|
+
const buildCustomer = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
6982
|
+
args: {
|
|
6983
|
+
entityId: v.optional(v.string())
|
|
6984
|
+
},
|
|
6985
|
+
handler: async (context_, args) => {
|
|
6986
|
+
const context = context_;
|
|
6987
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
6988
|
+
context,
|
|
6989
|
+
operation: "customer",
|
|
6990
|
+
entityId: args.entityId
|
|
6991
|
+
});
|
|
6992
|
+
if (!authorized || !resolvedEntityId) {
|
|
6993
|
+
throw new Error("Unauthorized");
|
|
6994
|
+
}
|
|
6995
|
+
const customer = await context.db.query("stripeCustomers").withIndex("byEntityId", (q) => q.eq("entityId", resolvedEntityId)).unique();
|
|
6996
|
+
return customer ?? null;
|
|
6997
|
+
}
|
|
6998
|
+
});
|
|
6999
|
+
const buildProducts = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
7000
|
+
args: {},
|
|
7001
|
+
handler: async (context_) => {
|
|
7002
|
+
const context = context_;
|
|
7003
|
+
const [authorized] = await authenticateAndAuthorize({
|
|
7004
|
+
context,
|
|
7005
|
+
operation: "products",
|
|
7006
|
+
entityId: void 0
|
|
7007
|
+
});
|
|
7008
|
+
if (!authorized) {
|
|
7009
|
+
throw new Error("Unauthorized");
|
|
7010
|
+
}
|
|
7011
|
+
const prices = await context.db.query("stripePrices").collect();
|
|
7012
|
+
const products = await context.db.query("stripeProducts").collect();
|
|
7013
|
+
return products.map(
|
|
7014
|
+
(product) => ({
|
|
7015
|
+
...product,
|
|
7016
|
+
prices: prices.filter((price) => price.stripe.productId === product.productId)
|
|
7017
|
+
})
|
|
7018
|
+
);
|
|
7019
|
+
}
|
|
7020
|
+
});
|
|
7021
|
+
const buildSubscription = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
7022
|
+
args: {
|
|
7023
|
+
entityId: v.optional(v.string())
|
|
7024
|
+
},
|
|
7025
|
+
handler: async (context_, args) => {
|
|
7026
|
+
const context = context_;
|
|
7027
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
7028
|
+
context,
|
|
7029
|
+
operation: "subscription",
|
|
7030
|
+
entityId: args.entityId
|
|
7031
|
+
});
|
|
7032
|
+
if (!authorized || !resolvedEntityId) {
|
|
7033
|
+
throw new Error("Unauthorized");
|
|
7034
|
+
}
|
|
7035
|
+
const customer = await context.db.query("stripeCustomers").withIndex("byEntityId", (q) => q.eq("entityId", resolvedEntityId)).unique();
|
|
7036
|
+
if (!customer) return null;
|
|
7037
|
+
const subscription = await context.db.query("stripeSubscriptions").withIndex(
|
|
7038
|
+
"byCustomerId",
|
|
7039
|
+
(q) => q.eq("customerId", customer.customerId)
|
|
7040
|
+
).unique();
|
|
7041
|
+
return subscription ?? null;
|
|
7042
|
+
}
|
|
7043
|
+
});
|
|
7044
|
+
const buildHelpers = (configuration, options, config) => {
|
|
7045
|
+
const { authenticateAndAuthorize } = config;
|
|
7046
|
+
return {
|
|
7047
|
+
createCustomer: buildCreateCustomer(
|
|
7048
|
+
configuration,
|
|
7049
|
+
options,
|
|
7050
|
+
authenticateAndAuthorize
|
|
7051
|
+
),
|
|
7052
|
+
products: buildProducts(configuration, options, authenticateAndAuthorize),
|
|
7053
|
+
subscription: buildSubscription(
|
|
7054
|
+
configuration,
|
|
7055
|
+
options,
|
|
7056
|
+
authenticateAndAuthorize
|
|
7057
|
+
),
|
|
7058
|
+
customer: buildCustomer(configuration, options, authenticateAndAuthorize)
|
|
7059
|
+
};
|
|
7060
|
+
};
|
|
6941
7061
|
const pickProductUpdateParams = (product) => {
|
|
6942
7062
|
const {
|
|
6943
7063
|
active,
|
|
@@ -9527,8 +9647,7 @@ const internalConvexStripe = (configuration_, options_) => {
|
|
|
9527
9647
|
* stripe.addHttpRoutes(http);
|
|
9528
9648
|
* export default http;
|
|
9529
9649
|
*/
|
|
9530
|
-
addHttpRoutes: (http
|
|
9531
|
-
config = normalizeConfiguration(config || configuration_);
|
|
9650
|
+
addHttpRoutes: (http) => {
|
|
9532
9651
|
http.route({
|
|
9533
9652
|
path: http_.webhook.path,
|
|
9534
9653
|
method: http_.webhook.method,
|
|
@@ -9637,7 +9756,71 @@ const internalConvexStripe = (configuration_, options_) => {
|
|
|
9637
9756
|
ConvexStripeInternalOptions
|
|
9638
9757
|
);
|
|
9639
9758
|
}
|
|
9640
|
-
}
|
|
9759
|
+
},
|
|
9760
|
+
/**
|
|
9761
|
+
* Returns a set of pre-built, authorization-aware Convex functions
|
|
9762
|
+
* (actions and queries) that cover the most common Stripe operations.
|
|
9763
|
+
*
|
|
9764
|
+
* Each returned function calls your `authenticateAndAuthorize` callback
|
|
9765
|
+
* to resolve the caller's identity and enforce access control before
|
|
9766
|
+
* delegating to the underlying Stripe implementation.
|
|
9767
|
+
*
|
|
9768
|
+
* When a caller omits `entityId`, it signals that they want to act on
|
|
9769
|
+
* themselves — your callback is responsible for deriving their identity
|
|
9770
|
+
* from `context` (e.g. via `getAuthUserId`).
|
|
9771
|
+
*
|
|
9772
|
+
* **Returned functions:**
|
|
9773
|
+
* - `internalCreateCustomer` — internal action: create a Stripe customer for a given entity.
|
|
9774
|
+
* - `subscribe` — public action: create a subscription checkout session.
|
|
9775
|
+
* - `pay` — public action: create a one-time payment checkout session.
|
|
9776
|
+
* - `products` — public query: list all products with their prices.
|
|
9777
|
+
* - `subscription` — public query: get the entity's active subscription.
|
|
9778
|
+
* - `customer` — public query: get the entity's Stripe customer record.
|
|
9779
|
+
* - `portal` — public action: open a Billing Portal session.
|
|
9780
|
+
*
|
|
9781
|
+
* @param config - Configuration for the helpers.
|
|
9782
|
+
* @param config.authenticateAndAuthorize - Callback that authenticates the
|
|
9783
|
+
* caller and returns `[isAuthorized, entityId | null]`.
|
|
9784
|
+
* @param config.urls - Centralized return URL configuration (required). URLs are grouped
|
|
9785
|
+
* by operation: `subscribe`, `pay`, and `portal`.
|
|
9786
|
+
*
|
|
9787
|
+
* @example
|
|
9788
|
+
* // convex/stripe.ts
|
|
9789
|
+
* import { getAuthUserId } from "@convex-dev/auth/server";
|
|
9790
|
+
*
|
|
9791
|
+
* export const { stripe, store, sync } = internalConvexStripe({ ... });
|
|
9792
|
+
*
|
|
9793
|
+
* export const { internalCreateCustomer, subscribe, pay, products, subscription, customer, portal } =
|
|
9794
|
+
* stripe.helpers({
|
|
9795
|
+
* authenticateAndAuthorize: async ({ context, operation, entityId }) => {
|
|
9796
|
+
* const userId = await getAuthUserId(context);
|
|
9797
|
+
* if (!userId) return [false, null];
|
|
9798
|
+
* // If caller passed an explicit entityId, use it; otherwise act on themselves.
|
|
9799
|
+
* return [true, entityId ?? userId];
|
|
9800
|
+
* },
|
|
9801
|
+
* urls: {
|
|
9802
|
+
* subscribe: {
|
|
9803
|
+
* success: "https://example.com/success",
|
|
9804
|
+
* cancel: "https://example.com/cancel",
|
|
9805
|
+
* failure: "https://example.com/error",
|
|
9806
|
+
* },
|
|
9807
|
+
* pay: {
|
|
9808
|
+
* success: "https://example.com/pay-success",
|
|
9809
|
+
* cancel: "https://example.com/pay-cancel",
|
|
9810
|
+
* failure: "https://example.com/pay-error",
|
|
9811
|
+
* },
|
|
9812
|
+
* portal: {
|
|
9813
|
+
* return: "https://example.com/account",
|
|
9814
|
+
* failure: "https://example.com/portal-error",
|
|
9815
|
+
* },
|
|
9816
|
+
* },
|
|
9817
|
+
* });
|
|
9818
|
+
*/
|
|
9819
|
+
helpers: (config) => buildHelpers(
|
|
9820
|
+
ConvexStripeInternalConfiguration,
|
|
9821
|
+
ConvexStripeInternalOptions,
|
|
9822
|
+
config
|
|
9823
|
+
)
|
|
9641
9824
|
},
|
|
9642
9825
|
/**
|
|
9643
9826
|
* Internal Convex mutation that persists a Stripe object into your database.
|
|
@@ -9679,8 +9862,5 @@ export {
|
|
|
9679
9862
|
defineWebhookHandler,
|
|
9680
9863
|
internalConvexStripe,
|
|
9681
9864
|
onlyStripeTables,
|
|
9682
|
-
stripeTables
|
|
9683
|
-
syncAllTables,
|
|
9684
|
-
syncAllTablesExcept,
|
|
9685
|
-
syncOnlyTables
|
|
9865
|
+
stripeTables
|
|
9686
9866
|
};
|