@raideno/convex-stripe 0.3.5 → 0.3.7
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 +150 -0
- package/dist/index.d.ts +342 -0
- package/dist/server.js +200 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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)
|
|
@@ -797,6 +802,151 @@ This action is typically called manually from the Convex dashboard, or set up to
|
|
|
797
802
|
|
|
798
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.
|
|
799
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
|
+
|
|
800
950
|
## Synced Tables
|
|
801
951
|
|
|
802
952
|
The library automatically syncs the following 24 Stripe resource types into your Convex database:
|
package/dist/index.d.ts
CHANGED
|
@@ -4,11 +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';
|
|
7
8
|
import { GenericSchema } from 'convex/server';
|
|
8
9
|
import { HttpRouter } from 'convex/server';
|
|
9
10
|
import { Infer } from 'convex/values';
|
|
10
11
|
import { RegisteredAction } from 'convex/server';
|
|
11
12
|
import { RegisteredMutation } from 'convex/server';
|
|
13
|
+
import { RegisteredQuery } from 'convex/server';
|
|
12
14
|
import { RoutableMethod } from 'convex/server';
|
|
13
15
|
import { SchemaDefinition } from 'convex/server';
|
|
14
16
|
import { TableDefinition } from 'convex/server';
|
|
@@ -24,12 +26,34 @@ import { VRecord } from 'convex/values';
|
|
|
24
26
|
import { VString } from 'convex/values';
|
|
25
27
|
import { VUnion } from 'convex/values';
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The operations handled by action-type helpers (run in an action context).
|
|
31
|
+
*/
|
|
32
|
+
declare type ActionOperation = "createCustomer";
|
|
33
|
+
|
|
27
34
|
declare type AllRedirectHandlers = (typeof REDIRECT_HANDLERS)[number];
|
|
28
35
|
|
|
29
36
|
export declare const allStripeTablesExcept: (tables: Array<keyof typeof stripeTables>) => typeof stripeTables;
|
|
30
37
|
|
|
31
38
|
declare type ArgSchema = Record<string, Validator<any, "optional" | "required", any>>;
|
|
32
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
|
+
|
|
33
57
|
export declare function buildSignedReturnUrl<O extends ReturnOrigin>({ configuration, origin, failureUrl, targetUrl, data, }: {
|
|
34
58
|
configuration: InternalConfiguration;
|
|
35
59
|
origin: O;
|
|
@@ -170,6 +194,32 @@ export declare function defineRedirectHandler<const T extends readonly string[],
|
|
|
170
194
|
|
|
171
195
|
export declare function defineWebhookHandler<const T extends default_2.Event.Type>(handler: WebhookHandler<T>): WebhookHandler<T>;
|
|
172
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
|
+
|
|
173
223
|
declare type InferArgs<S extends ArgSchema> = {
|
|
174
224
|
[K in keyof S as S[K] extends Validator<any, "required", any> ? K : never]: Infer<S[K]>;
|
|
175
225
|
} & {
|
|
@@ -477,6 +527,283 @@ export declare const internalConvexStripe: (configuration_: InputConfiguration &
|
|
|
477
527
|
*/
|
|
478
528
|
link: (context: GenericActionCtx<any>, args: Parameters<(typeof CreateAccountLinkImplementation)["handler"]>[1], options?: Parameters<(typeof CreateAccountLinkImplementation)["handler"]>[2]) => Promise<default_2.Response<default_2.AccountLink>>;
|
|
479
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
|
+
};
|
|
480
807
|
};
|
|
481
808
|
/**
|
|
482
809
|
* Internal Convex mutation that persists a Stripe object into your database.
|
|
@@ -1662,6 +1989,11 @@ declare const PortalImplementation: {
|
|
|
1662
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>>;
|
|
1663
1990
|
};
|
|
1664
1991
|
|
|
1992
|
+
/**
|
|
1993
|
+
* The operations handled by query-type helpers (run in a query context).
|
|
1994
|
+
*/
|
|
1995
|
+
declare type QueryOperation = "products" | "subscription" | "customer";
|
|
1996
|
+
|
|
1665
1997
|
declare type RecursiveDeepRequired<T> = T extends (...args: any[]) => any ? T : T extends object ? {
|
|
1666
1998
|
[K in keyof T]-?: RecursiveDeepRequired<T[K]>;
|
|
1667
1999
|
} : T;
|
|
@@ -1697,6 +2029,16 @@ declare type ReturnOrigin = (typeof RETURN_ORIGINS)[number];
|
|
|
1697
2029
|
|
|
1698
2030
|
declare type StripeDataModel = DataModelFromSchemaDefinition<typeof stripeSchema>;
|
|
1699
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
|
+
|
|
1700
2042
|
declare const stripeSchema: SchemaDefinition< {
|
|
1701
2043
|
stripeAccounts: TableDefinition<VObject< {
|
|
1702
2044
|
entityId?: string | undefined;
|
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);
|
|
@@ -5764,7 +5789,12 @@ const DEFAULT_OPTIONS = {
|
|
|
5764
5789
|
base: "stripe"
|
|
5765
5790
|
};
|
|
5766
5791
|
const normalizeOptions = (options) => {
|
|
5767
|
-
|
|
5792
|
+
const { logger, ...rest } = options;
|
|
5793
|
+
const merged = deepmerge(DEFAULT_OPTIONS, rest);
|
|
5794
|
+
return {
|
|
5795
|
+
...merged,
|
|
5796
|
+
logger: logger || DEFAULT_OPTIONS.logger
|
|
5797
|
+
};
|
|
5768
5798
|
};
|
|
5769
5799
|
const defineActionCallableFunction = (spec) => spec;
|
|
5770
5800
|
const defineActionImplementation = (spec) => spec;
|
|
@@ -6929,6 +6959,110 @@ const SubscribeImplementation = defineActionCallableFunction({
|
|
|
6929
6959
|
return checkout;
|
|
6930
6960
|
}
|
|
6931
6961
|
});
|
|
6962
|
+
const buildCreateCustomer = (configuration, options, authenticateAndAuthorize) => internalActionGeneric({
|
|
6963
|
+
args: {
|
|
6964
|
+
entityId: v.string(),
|
|
6965
|
+
email: v.optional(v.string())
|
|
6966
|
+
},
|
|
6967
|
+
handler: async (context_, args) => {
|
|
6968
|
+
const context = context_;
|
|
6969
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
6970
|
+
context,
|
|
6971
|
+
operation: "createCustomer",
|
|
6972
|
+
entityId: args.entityId
|
|
6973
|
+
});
|
|
6974
|
+
if (!authorized || !resolvedEntityId) {
|
|
6975
|
+
throw new Error("Unauthorized");
|
|
6976
|
+
}
|
|
6977
|
+
return CreateCustomerImplementation.handler(
|
|
6978
|
+
context,
|
|
6979
|
+
{ entityId: resolvedEntityId, email: args.email },
|
|
6980
|
+
{},
|
|
6981
|
+
configuration,
|
|
6982
|
+
options
|
|
6983
|
+
);
|
|
6984
|
+
}
|
|
6985
|
+
});
|
|
6986
|
+
const buildCustomer = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
6987
|
+
args: {
|
|
6988
|
+
entityId: v.optional(v.string())
|
|
6989
|
+
},
|
|
6990
|
+
handler: async (context_, args) => {
|
|
6991
|
+
const context = context_;
|
|
6992
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
6993
|
+
context,
|
|
6994
|
+
operation: "customer",
|
|
6995
|
+
entityId: args.entityId
|
|
6996
|
+
});
|
|
6997
|
+
if (!authorized || !resolvedEntityId) {
|
|
6998
|
+
throw new Error("Unauthorized");
|
|
6999
|
+
}
|
|
7000
|
+
const customer = await context.db.query("stripeCustomers").withIndex("byEntityId", (q) => q.eq("entityId", resolvedEntityId)).unique();
|
|
7001
|
+
return customer ?? null;
|
|
7002
|
+
}
|
|
7003
|
+
});
|
|
7004
|
+
const buildProducts = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
7005
|
+
args: {},
|
|
7006
|
+
handler: async (context_) => {
|
|
7007
|
+
const context = context_;
|
|
7008
|
+
const [authorized] = await authenticateAndAuthorize({
|
|
7009
|
+
context,
|
|
7010
|
+
operation: "products",
|
|
7011
|
+
entityId: void 0
|
|
7012
|
+
});
|
|
7013
|
+
if (!authorized) {
|
|
7014
|
+
throw new Error("Unauthorized");
|
|
7015
|
+
}
|
|
7016
|
+
const prices = await context.db.query("stripePrices").collect();
|
|
7017
|
+
const products = await context.db.query("stripeProducts").collect();
|
|
7018
|
+
return products.map(
|
|
7019
|
+
(product) => ({
|
|
7020
|
+
...product,
|
|
7021
|
+
prices: prices.filter((price) => price.stripe.productId === product.productId)
|
|
7022
|
+
})
|
|
7023
|
+
);
|
|
7024
|
+
}
|
|
7025
|
+
});
|
|
7026
|
+
const buildSubscription = (_configuration, _options, authenticateAndAuthorize) => queryGeneric({
|
|
7027
|
+
args: {
|
|
7028
|
+
entityId: v.optional(v.string())
|
|
7029
|
+
},
|
|
7030
|
+
handler: async (context_, args) => {
|
|
7031
|
+
const context = context_;
|
|
7032
|
+
const [authorized, resolvedEntityId] = await authenticateAndAuthorize({
|
|
7033
|
+
context,
|
|
7034
|
+
operation: "subscription",
|
|
7035
|
+
entityId: args.entityId
|
|
7036
|
+
});
|
|
7037
|
+
if (!authorized || !resolvedEntityId) {
|
|
7038
|
+
throw new Error("Unauthorized");
|
|
7039
|
+
}
|
|
7040
|
+
const customer = await context.db.query("stripeCustomers").withIndex("byEntityId", (q) => q.eq("entityId", resolvedEntityId)).unique();
|
|
7041
|
+
if (!customer) return null;
|
|
7042
|
+
const subscription = await context.db.query("stripeSubscriptions").withIndex(
|
|
7043
|
+
"byCustomerId",
|
|
7044
|
+
(q) => q.eq("customerId", customer.customerId)
|
|
7045
|
+
).unique();
|
|
7046
|
+
return subscription ?? null;
|
|
7047
|
+
}
|
|
7048
|
+
});
|
|
7049
|
+
const buildHelpers = (configuration, options, config) => {
|
|
7050
|
+
const { authenticateAndAuthorize } = config;
|
|
7051
|
+
return {
|
|
7052
|
+
createCustomer: buildCreateCustomer(
|
|
7053
|
+
configuration,
|
|
7054
|
+
options,
|
|
7055
|
+
authenticateAndAuthorize
|
|
7056
|
+
),
|
|
7057
|
+
products: buildProducts(configuration, options, authenticateAndAuthorize),
|
|
7058
|
+
subscription: buildSubscription(
|
|
7059
|
+
configuration,
|
|
7060
|
+
options,
|
|
7061
|
+
authenticateAndAuthorize
|
|
7062
|
+
),
|
|
7063
|
+
customer: buildCustomer(configuration, options, authenticateAndAuthorize)
|
|
7064
|
+
};
|
|
7065
|
+
};
|
|
6932
7066
|
const pickProductUpdateParams = (product) => {
|
|
6933
7067
|
const {
|
|
6934
7068
|
active,
|
|
@@ -9627,7 +9761,71 @@ const internalConvexStripe = (configuration_, options_) => {
|
|
|
9627
9761
|
ConvexStripeInternalOptions
|
|
9628
9762
|
);
|
|
9629
9763
|
}
|
|
9630
|
-
}
|
|
9764
|
+
},
|
|
9765
|
+
/**
|
|
9766
|
+
* Returns a set of pre-built, authorization-aware Convex functions
|
|
9767
|
+
* (actions and queries) that cover the most common Stripe operations.
|
|
9768
|
+
*
|
|
9769
|
+
* Each returned function calls your `authenticateAndAuthorize` callback
|
|
9770
|
+
* to resolve the caller's identity and enforce access control before
|
|
9771
|
+
* delegating to the underlying Stripe implementation.
|
|
9772
|
+
*
|
|
9773
|
+
* When a caller omits `entityId`, it signals that they want to act on
|
|
9774
|
+
* themselves — your callback is responsible for deriving their identity
|
|
9775
|
+
* from `context` (e.g. via `getAuthUserId`).
|
|
9776
|
+
*
|
|
9777
|
+
* **Returned functions:**
|
|
9778
|
+
* - `internalCreateCustomer` — internal action: create a Stripe customer for a given entity.
|
|
9779
|
+
* - `subscribe` — public action: create a subscription checkout session.
|
|
9780
|
+
* - `pay` — public action: create a one-time payment checkout session.
|
|
9781
|
+
* - `products` — public query: list all products with their prices.
|
|
9782
|
+
* - `subscription` — public query: get the entity's active subscription.
|
|
9783
|
+
* - `customer` — public query: get the entity's Stripe customer record.
|
|
9784
|
+
* - `portal` — public action: open a Billing Portal session.
|
|
9785
|
+
*
|
|
9786
|
+
* @param config - Configuration for the helpers.
|
|
9787
|
+
* @param config.authenticateAndAuthorize - Callback that authenticates the
|
|
9788
|
+
* caller and returns `[isAuthorized, entityId | null]`.
|
|
9789
|
+
* @param config.urls - Centralized return URL configuration (required). URLs are grouped
|
|
9790
|
+
* by operation: `subscribe`, `pay`, and `portal`.
|
|
9791
|
+
*
|
|
9792
|
+
* @example
|
|
9793
|
+
* // convex/stripe.ts
|
|
9794
|
+
* import { getAuthUserId } from "@convex-dev/auth/server";
|
|
9795
|
+
*
|
|
9796
|
+
* export const { stripe, store, sync } = internalConvexStripe({ ... });
|
|
9797
|
+
*
|
|
9798
|
+
* export const { internalCreateCustomer, subscribe, pay, products, subscription, customer, portal } =
|
|
9799
|
+
* stripe.helpers({
|
|
9800
|
+
* authenticateAndAuthorize: async ({ context, operation, entityId }) => {
|
|
9801
|
+
* const userId = await getAuthUserId(context);
|
|
9802
|
+
* if (!userId) return [false, null];
|
|
9803
|
+
* // If caller passed an explicit entityId, use it; otherwise act on themselves.
|
|
9804
|
+
* return [true, entityId ?? userId];
|
|
9805
|
+
* },
|
|
9806
|
+
* urls: {
|
|
9807
|
+
* subscribe: {
|
|
9808
|
+
* success: "https://example.com/success",
|
|
9809
|
+
* cancel: "https://example.com/cancel",
|
|
9810
|
+
* failure: "https://example.com/error",
|
|
9811
|
+
* },
|
|
9812
|
+
* pay: {
|
|
9813
|
+
* success: "https://example.com/pay-success",
|
|
9814
|
+
* cancel: "https://example.com/pay-cancel",
|
|
9815
|
+
* failure: "https://example.com/pay-error",
|
|
9816
|
+
* },
|
|
9817
|
+
* portal: {
|
|
9818
|
+
* return: "https://example.com/account",
|
|
9819
|
+
* failure: "https://example.com/portal-error",
|
|
9820
|
+
* },
|
|
9821
|
+
* },
|
|
9822
|
+
* });
|
|
9823
|
+
*/
|
|
9824
|
+
helpers: (config) => buildHelpers(
|
|
9825
|
+
ConvexStripeInternalConfiguration,
|
|
9826
|
+
ConvexStripeInternalOptions,
|
|
9827
|
+
config
|
|
9828
|
+
)
|
|
9631
9829
|
},
|
|
9632
9830
|
/**
|
|
9633
9831
|
* Internal Convex mutation that persists a Stripe object into your database.
|