@raideno/convex-stripe 0.3.5 → 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 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);
@@ -6929,6 +6954,110 @@ const SubscribeImplementation = defineActionCallableFunction({
6929
6954
  return checkout;
6930
6955
  }
6931
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
+ };
6932
7061
  const pickProductUpdateParams = (product) => {
6933
7062
  const {
6934
7063
  active,
@@ -9627,7 +9756,71 @@ const internalConvexStripe = (configuration_, options_) => {
9627
9756
  ConvexStripeInternalOptions
9628
9757
  );
9629
9758
  }
9630
- }
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
+ )
9631
9824
  },
9632
9825
  /**
9633
9826
  * Internal Convex mutation that persists a Stripe object into your database.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raideno/convex-stripe",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Easy stripe billing for convex apps.",
5
5
  "keywords": [
6
6
  "billing",