@mmailaender/convex-creem 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +1176 -0
- package/dist/client/helpers.d.ts +17 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +43 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/index.d.ts +1041 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1068 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/parsers.d.ts +45 -0
- package/dist/client/parsers.d.ts.map +1 -0
- package/dist/client/parsers.js +138 -0
- package/dist/client/parsers.js.map +1 -0
- package/dist/client/polyfill.d.ts +2 -0
- package/dist/client/polyfill.d.ts.map +1 -0
- package/dist/client/polyfill.js +3 -0
- package/dist/client/polyfill.js.map +1 -0
- package/dist/component/_generated/api.d.ts +36 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +542 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/lib.d.ts +1005 -0
- package/dist/component/lib.d.ts.map +1 -0
- package/dist/component/lib.js +647 -0
- package/dist/component/lib.js.map +1 -0
- package/dist/component/schema.d.ts +191 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +104 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/util.d.ts +61 -0
- package/dist/component/util.d.ts.map +1 -0
- package/dist/component/util.js +142 -0
- package/dist/component/util.js.map +1 -0
- package/dist/core/catalog.d.ts +18 -0
- package/dist/core/catalog.d.ts.map +1 -0
- package/dist/core/catalog.js +82 -0
- package/dist/core/catalog.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/markdown.d.ts +12 -0
- package/dist/core/markdown.d.ts.map +1 -0
- package/dist/core/markdown.js +26 -0
- package/dist/core/markdown.js.map +1 -0
- package/dist/core/payments.d.ts +11 -0
- package/dist/core/payments.d.ts.map +1 -0
- package/dist/core/payments.js +27 -0
- package/dist/core/payments.js.map +1 -0
- package/dist/core/pendingCheckout.d.ts +15 -0
- package/dist/core/pendingCheckout.d.ts.map +1 -0
- package/dist/core/pendingCheckout.js +40 -0
- package/dist/core/pendingCheckout.js.map +1 -0
- package/dist/core/resolver.d.ts +11 -0
- package/dist/core/resolver.d.ts.map +1 -0
- package/dist/core/resolver.js +106 -0
- package/dist/core/resolver.js.map +1 -0
- package/dist/core/selectors.d.ts +12 -0
- package/dist/core/selectors.d.ts.map +1 -0
- package/dist/core/selectors.js +18 -0
- package/dist/core/selectors.js.map +1 -0
- package/dist/core/subscriptionUpdate.d.ts +20 -0
- package/dist/core/subscriptionUpdate.d.ts.map +1 -0
- package/dist/core/subscriptionUpdate.js +64 -0
- package/dist/core/subscriptionUpdate.js.map +1 -0
- package/dist/core/types.d.ts +170 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +15 -0
- package/dist/core/types.js.map +1 -0
- package/dist/design-system/colors/color-utils.d.ts +10 -0
- package/dist/design-system/colors/color-utils.d.ts.map +1 -0
- package/dist/design-system/colors/color-utils.js +91 -0
- package/dist/design-system/colors/color-utils.js.map +1 -0
- package/dist/design-system/colors/config.d.ts +33 -0
- package/dist/design-system/colors/config.d.ts.map +1 -0
- package/dist/design-system/colors/config.js +224 -0
- package/dist/design-system/colors/config.js.map +1 -0
- package/dist/design-system/colors/index.d.ts +3 -0
- package/dist/design-system/colors/index.d.ts.map +1 -0
- package/dist/design-system/colors/index.js +3 -0
- package/dist/design-system/colors/index.js.map +1 -0
- package/dist/design-system/rounded/config.d.ts +31 -0
- package/dist/design-system/rounded/config.d.ts.map +1 -0
- package/dist/design-system/rounded/config.js +76 -0
- package/dist/design-system/rounded/config.js.map +1 -0
- package/dist/design-system/rounded/index.d.ts +2 -0
- package/dist/design-system/rounded/index.d.ts.map +1 -0
- package/dist/design-system/rounded/index.js +2 -0
- package/dist/design-system/rounded/index.js.map +1 -0
- package/dist/design-system/typography/config.d.ts +55 -0
- package/dist/design-system/typography/config.d.ts.map +1 -0
- package/dist/design-system/typography/config.js +308 -0
- package/dist/design-system/typography/config.js.map +1 -0
- package/dist/design-system/typography/index.d.ts +3 -0
- package/dist/design-system/typography/index.d.ts.map +1 -0
- package/dist/design-system/typography/index.js +3 -0
- package/dist/design-system/typography/index.js.map +1 -0
- package/dist/design-system/typography/tokens.d.ts +23 -0
- package/dist/design-system/typography/tokens.d.ts.map +1 -0
- package/dist/design-system/typography/tokens.js +99 -0
- package/dist/design-system/typography/tokens.js.map +1 -0
- package/dist/react/hooks/useCheckoutSuccessParams.d.ts +2 -0
- package/dist/react/hooks/useCheckoutSuccessParams.d.ts.map +1 -0
- package/dist/react/hooks/useCheckoutSuccessParams.js +5 -0
- package/dist/react/hooks/useCheckoutSuccessParams.js.map +1 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +22 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/primitives/BillingGate.d.ts +8 -0
- package/dist/react/primitives/BillingGate.d.ts.map +1 -0
- package/dist/react/primitives/BillingGate.js +13 -0
- package/dist/react/primitives/BillingGate.js.map +1 -0
- package/dist/react/primitives/BillingToggle.d.ts +8 -0
- package/dist/react/primitives/BillingToggle.d.ts.map +1 -0
- package/dist/react/primitives/BillingToggle.js +12 -0
- package/dist/react/primitives/BillingToggle.js.map +1 -0
- package/dist/react/primitives/CheckoutButton.d.ts +11 -0
- package/dist/react/primitives/CheckoutButton.d.ts.map +1 -0
- package/dist/react/primitives/CheckoutButton.js +21 -0
- package/dist/react/primitives/CheckoutButton.js.map +1 -0
- package/dist/react/primitives/CheckoutSuccessSummary.d.ts +7 -0
- package/dist/react/primitives/CheckoutSuccessSummary.d.ts.map +1 -0
- package/dist/react/primitives/CheckoutSuccessSummary.js +11 -0
- package/dist/react/primitives/CheckoutSuccessSummary.js.map +1 -0
- package/dist/react/primitives/CustomerPortalButton.d.ts +8 -0
- package/dist/react/primitives/CustomerPortalButton.d.ts.map +1 -0
- package/dist/react/primitives/CustomerPortalButton.js +21 -0
- package/dist/react/primitives/CustomerPortalButton.js.map +1 -0
- package/dist/react/primitives/NumberInput.d.ts +11 -0
- package/dist/react/primitives/NumberInput.d.ts.map +1 -0
- package/dist/react/primitives/NumberInput.js +18 -0
- package/dist/react/primitives/NumberInput.js.map +1 -0
- package/dist/react/primitives/OneTimeCheckoutButton.d.ts +11 -0
- package/dist/react/primitives/OneTimeCheckoutButton.d.ts.map +1 -0
- package/dist/react/primitives/OneTimeCheckoutButton.js +4 -0
- package/dist/react/primitives/OneTimeCheckoutButton.js.map +1 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts +6 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts.map +1 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.js +11 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.js.map +1 -0
- package/dist/react/primitives/PaymentWarningBanner.d.ts +7 -0
- package/dist/react/primitives/PaymentWarningBanner.d.ts.map +1 -0
- package/dist/react/primitives/PaymentWarningBanner.js +18 -0
- package/dist/react/primitives/PaymentWarningBanner.js.map +1 -0
- package/dist/react/primitives/PricingCard.d.ts +37 -0
- package/dist/react/primitives/PricingCard.d.ts.map +1 -0
- package/dist/react/primitives/PricingCard.js +125 -0
- package/dist/react/primitives/PricingCard.js.map +1 -0
- package/dist/react/primitives/PricingSection.d.ts +39 -0
- package/dist/react/primitives/PricingSection.d.ts.map +1 -0
- package/dist/react/primitives/PricingSection.js +24 -0
- package/dist/react/primitives/PricingSection.js.map +1 -0
- package/dist/react/primitives/ScheduledChangeBanner.d.ts +8 -0
- package/dist/react/primitives/ScheduledChangeBanner.d.ts.map +1 -0
- package/dist/react/primitives/ScheduledChangeBanner.js +13 -0
- package/dist/react/primitives/ScheduledChangeBanner.js.map +1 -0
- package/dist/react/primitives/SegmentControl.d.ts +11 -0
- package/dist/react/primitives/SegmentControl.d.ts.map +1 -0
- package/dist/react/primitives/SegmentControl.js +8 -0
- package/dist/react/primitives/SegmentControl.js.map +1 -0
- package/dist/react/primitives/SegmentGroup.d.ts +14 -0
- package/dist/react/primitives/SegmentGroup.d.ts.map +1 -0
- package/dist/react/primitives/SegmentGroup.js +11 -0
- package/dist/react/primitives/SegmentGroup.js.map +1 -0
- package/dist/react/primitives/TrialLimitBanner.d.ts +7 -0
- package/dist/react/primitives/TrialLimitBanner.d.ts.map +1 -0
- package/dist/react/primitives/TrialLimitBanner.js +14 -0
- package/dist/react/primitives/TrialLimitBanner.js.map +1 -0
- package/dist/react/shared.d.ts +28 -0
- package/dist/react/shared.d.ts.map +1 -0
- package/dist/react/shared.js +109 -0
- package/dist/react/shared.js.map +1 -0
- package/dist/react/widgets/BillingPortal.d.ts +9 -0
- package/dist/react/widgets/BillingPortal.d.ts.map +1 -0
- package/dist/react/widgets/BillingPortal.js +30 -0
- package/dist/react/widgets/BillingPortal.js.map +1 -0
- package/dist/react/widgets/ProductItem.d.ts +8 -0
- package/dist/react/widgets/ProductItem.d.ts.map +1 -0
- package/dist/react/widgets/ProductItem.js +14 -0
- package/dist/react/widgets/ProductItem.js.map +1 -0
- package/dist/react/widgets/ProductRoot.d.ts +16 -0
- package/dist/react/widgets/ProductRoot.d.ts.map +1 -0
- package/dist/react/widgets/ProductRoot.js +171 -0
- package/dist/react/widgets/ProductRoot.js.map +1 -0
- package/dist/react/widgets/SubscriptionItem.d.ts +27 -0
- package/dist/react/widgets/SubscriptionItem.d.ts.map +1 -0
- package/dist/react/widgets/SubscriptionItem.js +32 -0
- package/dist/react/widgets/SubscriptionItem.js.map +1 -0
- package/dist/react/widgets/SubscriptionRoot.d.ts +16 -0
- package/dist/react/widgets/SubscriptionRoot.d.ts.map +1 -0
- package/dist/react/widgets/SubscriptionRoot.js +405 -0
- package/dist/react/widgets/SubscriptionRoot.js.map +1 -0
- package/dist/react/widgets/index.d.ts +19 -0
- package/dist/react/widgets/index.d.ts.map +1 -0
- package/dist/react/widgets/index.js +16 -0
- package/dist/react/widgets/index.js.map +1 -0
- package/dist/react/widgets/productGroupContext.d.ts +6 -0
- package/dist/react/widgets/productGroupContext.d.ts.map +1 -0
- package/dist/react/widgets/productGroupContext.js +3 -0
- package/dist/react/widgets/productGroupContext.js.map +1 -0
- package/dist/react/widgets/subscriptionContext.d.ts +6 -0
- package/dist/react/widgets/subscriptionContext.d.ts.map +1 -0
- package/dist/react/widgets/subscriptionContext.js +3 -0
- package/dist/react/widgets/subscriptionContext.js.map +1 -0
- package/dist/react/widgets/types.d.ts +171 -0
- package/dist/react/widgets/types.d.ts.map +1 -0
- package/dist/react/widgets/types.js +2 -0
- package/dist/react/widgets/types.js.map +1 -0
- package/dist/svelte/index.d.ts +22 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +20 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/svelte/primitives/BillingGate.svelte +28 -0
- package/dist/svelte/primitives/BillingToggle.svelte +27 -0
- package/dist/svelte/primitives/CheckoutButton.svelte +60 -0
- package/dist/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
- package/dist/svelte/primitives/CustomerPortalButton.svelte +60 -0
- package/dist/svelte/primitives/NumberInput.svelte +71 -0
- package/dist/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
- package/dist/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
- package/dist/svelte/primitives/PaymentWarningBanner.svelte +30 -0
- package/dist/svelte/primitives/PricingCard.svelte +356 -0
- package/dist/svelte/primitives/PricingSection.svelte +121 -0
- package/dist/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
- package/dist/svelte/primitives/SegmentControl.svelte +38 -0
- package/dist/svelte/primitives/SegmentGroup.svelte +52 -0
- package/dist/svelte/primitives/TrialLimitBanner.svelte +32 -0
- package/dist/svelte/primitives/shared.d.ts +13 -0
- package/dist/svelte/primitives/shared.d.ts.map +1 -0
- package/dist/svelte/primitives/shared.js +87 -0
- package/dist/svelte/primitives/shared.js.map +1 -0
- package/dist/svelte/widgets/BillingPortal.svelte +55 -0
- package/dist/svelte/widgets/Product.svelte +35 -0
- package/dist/svelte/widgets/ProductRoot.svelte +428 -0
- package/dist/svelte/widgets/Subscription.svelte +52 -0
- package/dist/svelte/widgets/SubscriptionRoot.svelte +690 -0
- package/dist/svelte/widgets/index.d.ts +19 -0
- package/dist/svelte/widgets/index.d.ts.map +1 -0
- package/dist/svelte/widgets/index.js +16 -0
- package/dist/svelte/widgets/index.js.map +1 -0
- package/dist/svelte/widgets/productGroupContext.d.ts +6 -0
- package/dist/svelte/widgets/productGroupContext.d.ts.map +1 -0
- package/dist/svelte/widgets/productGroupContext.js +2 -0
- package/dist/svelte/widgets/productGroupContext.js.map +1 -0
- package/dist/svelte/widgets/subscriptionContext.d.ts +6 -0
- package/dist/svelte/widgets/subscriptionContext.d.ts.map +1 -0
- package/dist/svelte/widgets/subscriptionContext.js +2 -0
- package/dist/svelte/widgets/subscriptionContext.js.map +1 -0
- package/dist/svelte/widgets/types.d.ts +171 -0
- package/dist/svelte/widgets/types.d.ts.map +1 -0
- package/dist/svelte/widgets/types.js +2 -0
- package/dist/svelte/widgets/types.js.map +1 -0
- package/package.json +182 -0
- package/src/client/helpers.test.ts +139 -0
- package/src/client/helpers.ts +51 -0
- package/src/client/index.test.ts +1554 -0
- package/src/client/index.ts +1504 -0
- package/src/client/parsers.test.ts +1017 -0
- package/src/client/parsers.ts +182 -0
- package/src/client/polyfill.ts +2 -0
- package/src/component/_generated/api.ts +52 -0
- package/src/component/_generated/component.ts +619 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +1359 -0
- package/src/component/lib.ts +726 -0
- package/src/component/schema.ts +112 -0
- package/src/component/util.test.ts +281 -0
- package/src/component/util.ts +228 -0
- package/src/core/catalog.test.ts +212 -0
- package/src/core/catalog.ts +119 -0
- package/src/core/index.ts +8 -0
- package/src/core/markdown.test.ts +43 -0
- package/src/core/markdown.ts +26 -0
- package/src/core/payments.test.ts +69 -0
- package/src/core/payments.ts +33 -0
- package/src/core/pendingCheckout.test.ts +44 -0
- package/src/core/pendingCheckout.ts +40 -0
- package/src/core/resolver.test.ts +283 -0
- package/src/core/resolver.ts +160 -0
- package/src/core/selectors.test.ts +119 -0
- package/src/core/selectors.ts +35 -0
- package/src/core/subscriptionUpdate.test.ts +164 -0
- package/src/core/subscriptionUpdate.ts +102 -0
- package/src/core/types.ts +220 -0
- package/src/design-system/README.md +40 -0
- package/src/design-system/base.css +27 -0
- package/src/design-system/colors/color-utils.ts +110 -0
- package/src/design-system/colors/config.ts +282 -0
- package/src/design-system/colors/index.ts +2 -0
- package/src/design-system/colors/utilities.css +2328 -0
- package/src/design-system/components/badges.css +65 -0
- package/src/design-system/components/buttons.css +256 -0
- package/src/design-system/components/dialog.css +218 -0
- package/src/design-system/components/icon-buttons.css +115 -0
- package/src/design-system/components/inputs.css +94 -0
- package/src/design-system/components/links.css +53 -0
- package/src/design-system/components/prose.css +67 -0
- package/src/design-system/components/segment-control.css +303 -0
- package/src/design-system/index.css +21 -0
- package/src/design-system/rounded/config.ts +91 -0
- package/src/design-system/rounded/index.ts +1 -0
- package/src/design-system/rounded/utilities.css +37 -0
- package/src/design-system/typography/config.ts +340 -0
- package/src/design-system/typography/index.ts +2 -0
- package/src/design-system/typography/tokens.ts +148 -0
- package/src/design-system/typography/utilities.css +728 -0
- package/src/library.css +20 -0
- package/src/react/hooks/useCheckoutSuccessParams.ts +7 -0
- package/src/react/index.tsx +47 -0
- package/src/react/primitives/BillingGate.tsx +26 -0
- package/src/react/primitives/BillingToggle.tsx +29 -0
- package/src/react/primitives/CheckoutButton.tsx +47 -0
- package/src/react/primitives/CheckoutSuccessSummary.tsx +36 -0
- package/src/react/primitives/CustomerPortalButton.tsx +50 -0
- package/src/react/primitives/NumberInput.tsx +83 -0
- package/src/react/primitives/OneTimeCheckoutButton.tsx +27 -0
- package/src/react/primitives/OneTimePaymentStatusBadge.tsx +18 -0
- package/src/react/primitives/PaymentWarningBanner.tsx +33 -0
- package/src/react/primitives/PricingCard.tsx +421 -0
- package/src/react/primitives/PricingSection.tsx +129 -0
- package/src/react/primitives/ScheduledChangeBanner.tsx +52 -0
- package/src/react/primitives/SegmentControl.tsx +32 -0
- package/src/react/primitives/SegmentGroup.tsx +53 -0
- package/src/react/primitives/TrialLimitBanner.tsx +32 -0
- package/src/react/shared.ts +138 -0
- package/src/react/widgets/BillingPortal.tsx +56 -0
- package/src/react/widgets/ProductItem.tsx +26 -0
- package/src/react/widgets/ProductRoot.tsx +441 -0
- package/src/react/widgets/SubscriptionItem.tsx +71 -0
- package/src/react/widgets/SubscriptionRoot.tsx +759 -0
- package/src/react/widgets/index.ts +36 -0
- package/src/react/widgets/productGroupContext.ts +10 -0
- package/src/react/widgets/subscriptionContext.ts +10 -0
- package/src/react/widgets/types.ts +179 -0
- package/src/svelte/index.ts +43 -0
- package/src/svelte/primitives/BillingGate.svelte +28 -0
- package/src/svelte/primitives/BillingToggle.svelte +27 -0
- package/src/svelte/primitives/CheckoutButton.svelte +60 -0
- package/src/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
- package/src/svelte/primitives/CustomerPortalButton.svelte +60 -0
- package/src/svelte/primitives/NumberInput.svelte +71 -0
- package/src/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
- package/src/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
- package/src/svelte/primitives/PaymentWarningBanner.svelte +30 -0
- package/src/svelte/primitives/PricingCard.svelte +356 -0
- package/src/svelte/primitives/PricingSection.svelte +121 -0
- package/src/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
- package/src/svelte/primitives/SegmentControl.svelte +38 -0
- package/src/svelte/primitives/SegmentGroup.svelte +52 -0
- package/src/svelte/primitives/TrialLimitBanner.svelte +32 -0
- package/src/svelte/primitives/shared.ts +113 -0
- package/src/svelte/svelte.d.ts +6 -0
- package/src/svelte/widgets/BillingPortal.svelte +55 -0
- package/src/svelte/widgets/Product.svelte +35 -0
- package/src/svelte/widgets/ProductRoot.svelte +428 -0
- package/src/svelte/widgets/Subscription.svelte +52 -0
- package/src/svelte/widgets/SubscriptionRoot.svelte +690 -0
- package/src/svelte/widgets/index.ts +36 -0
- package/src/svelte/widgets/productGroupContext.ts +7 -0
- package/src/svelte/widgets/subscriptionContext.ts +7 -0
- package/src/svelte/widgets/types.ts +179 -0
- package/src/tailwind.css +6 -0
- package/src/test.ts +18 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;oCAYhC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;2DAwBzB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAY1B,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAYrB,CAAC;AAEH,uFAAuF;AACvF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+CjC,CAAC;AAEH,gFAAgF;AAChF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAiDhC,CAAC;AAEH,gFAAgF;AAChF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAsCnC,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAYvB,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqB7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuG7B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;iBAqBxB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;iBAsBxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;iBAkBtB,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAqBzB,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAYpC,CAAC;AAEH,eAAO,MAAM,YAAY;;;;iBAwBvB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;iBAiBzB,CAAC;AAEH;;;oFAGoF;AACpF,eAAO,MAAM,iBAAiB;;;;;;;iBAmE5B,CAAC;AAEH;;2EAE2E;AAC3E,eAAO,MAAM,yBAAyB;;;;;;;;;;iBAuEpC,CAAC;AAEH;yDACyD;AACzD,eAAO,MAAM,4BAA4B;;;;;;;;;iBAkDvC,CAAC;AAEH,eAAO,MAAM,gBAAgB,GAC3B,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAEnE,KAAK,CAAC,sDAOP,CAAC"}
|
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { Creem } from "creem";
|
|
2
|
+
import { ConvexError, v } from "convex/values";
|
|
3
|
+
import { action, mutation, query } from "./_generated/server.js";
|
|
4
|
+
import schema from "./schema.js";
|
|
5
|
+
import { asyncMap } from "convex-helpers";
|
|
6
|
+
import { api } from "./_generated/api.js";
|
|
7
|
+
import { convertToDatabaseProduct } from "./util.js";
|
|
8
|
+
export const getCustomerByEntityId = query({
|
|
9
|
+
args: {
|
|
10
|
+
entityId: v.string(),
|
|
11
|
+
},
|
|
12
|
+
returns: v.union(schema.tables.customers.validator, v.null()),
|
|
13
|
+
handler: async (ctx, args) => {
|
|
14
|
+
const customer = await ctx.db
|
|
15
|
+
.query("customers")
|
|
16
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
17
|
+
.unique();
|
|
18
|
+
return omitSystemFields(customer);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
export const insertCustomer = mutation({
|
|
22
|
+
args: schema.tables.customers.validator,
|
|
23
|
+
returns: v.id("customers"),
|
|
24
|
+
handler: async (ctx, args) => {
|
|
25
|
+
const existingCustomer = await ctx.db
|
|
26
|
+
.query("customers")
|
|
27
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
28
|
+
.unique();
|
|
29
|
+
if (existingCustomer) {
|
|
30
|
+
// Enrich existing customer record with any new fields
|
|
31
|
+
const patch = {};
|
|
32
|
+
if (args.email && !existingCustomer.email)
|
|
33
|
+
patch.email = args.email;
|
|
34
|
+
if (args.name && !existingCustomer.name)
|
|
35
|
+
patch.name = args.name;
|
|
36
|
+
if (args.country && !existingCustomer.country)
|
|
37
|
+
patch.country = args.country;
|
|
38
|
+
if (args.mode)
|
|
39
|
+
patch.mode = args.mode;
|
|
40
|
+
if (args.updatedAt)
|
|
41
|
+
patch.updatedAt = args.updatedAt;
|
|
42
|
+
if (Object.keys(patch).length > 0) {
|
|
43
|
+
await ctx.db.patch(existingCustomer._id, patch);
|
|
44
|
+
}
|
|
45
|
+
return existingCustomer._id;
|
|
46
|
+
}
|
|
47
|
+
return ctx.db.insert("customers", args);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
export const getSubscription = query({
|
|
51
|
+
args: {
|
|
52
|
+
id: v.string(),
|
|
53
|
+
},
|
|
54
|
+
returns: v.union(schema.tables.subscriptions.validator, v.null()),
|
|
55
|
+
handler: async (ctx, args) => {
|
|
56
|
+
const subscription = await ctx.db
|
|
57
|
+
.query("subscriptions")
|
|
58
|
+
.withIndex("id", (q) => q.eq("id", args.id))
|
|
59
|
+
.unique();
|
|
60
|
+
return omitSystemFields(subscription);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
export const getProduct = query({
|
|
64
|
+
args: {
|
|
65
|
+
id: v.string(),
|
|
66
|
+
},
|
|
67
|
+
returns: v.union(schema.tables.products.validator, v.null()),
|
|
68
|
+
handler: async (ctx, args) => {
|
|
69
|
+
const product = await ctx.db
|
|
70
|
+
.query("products")
|
|
71
|
+
.withIndex("id", (q) => q.eq("id", args.id))
|
|
72
|
+
.unique();
|
|
73
|
+
return omitSystemFields(product);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
/** For apps that have 0 or 1 active subscription per user. Excludes expired trials. */
|
|
77
|
+
export const getCurrentSubscription = query({
|
|
78
|
+
args: {
|
|
79
|
+
entityId: v.string(),
|
|
80
|
+
},
|
|
81
|
+
returns: v.union(v.object({
|
|
82
|
+
...schema.tables.subscriptions.validator.fields,
|
|
83
|
+
product: schema.tables.products.validator,
|
|
84
|
+
}), v.null()),
|
|
85
|
+
handler: async (ctx, args) => {
|
|
86
|
+
const customer = await ctx.db
|
|
87
|
+
.query("customers")
|
|
88
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
89
|
+
.unique();
|
|
90
|
+
if (!customer) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const subscription = await ctx.db
|
|
94
|
+
.query("subscriptions")
|
|
95
|
+
.withIndex("customerId_endedAt", (q) => q.eq("customerId", customer.id).eq("endedAt", null))
|
|
96
|
+
.first();
|
|
97
|
+
if (!subscription) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (subscription.status === "trialing" &&
|
|
101
|
+
subscription.trialEnd &&
|
|
102
|
+
subscription.trialEnd <= new Date().toISOString()) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const product = await ctx.db
|
|
106
|
+
.query("products")
|
|
107
|
+
.withIndex("id", (q) => q.eq("id", subscription.productId))
|
|
108
|
+
.unique();
|
|
109
|
+
if (!product) {
|
|
110
|
+
throw new ConvexError(`Product not found: ${subscription.productId}`);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...omitSystemFields(subscription),
|
|
114
|
+
product: omitSystemFields(product),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
/** List active subscriptions for a user, excluding ended and expired trials. */
|
|
119
|
+
export const listUserSubscriptions = query({
|
|
120
|
+
args: {
|
|
121
|
+
entityId: v.string(),
|
|
122
|
+
},
|
|
123
|
+
returns: v.array(v.object({
|
|
124
|
+
...schema.tables.subscriptions.validator.fields,
|
|
125
|
+
product: v.union(schema.tables.products.validator, v.null()),
|
|
126
|
+
})),
|
|
127
|
+
handler: async (ctx, args) => {
|
|
128
|
+
const customer = await ctx.db
|
|
129
|
+
.query("customers")
|
|
130
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
131
|
+
.unique();
|
|
132
|
+
if (!customer) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const now = new Date().toISOString();
|
|
136
|
+
const subscriptions = await asyncMap(ctx.db
|
|
137
|
+
.query("subscriptions")
|
|
138
|
+
.withIndex("customerId", (q) => q.eq("customerId", customer.id))
|
|
139
|
+
.collect(), async (subscription) => {
|
|
140
|
+
if ((subscription.endedAt && subscription.endedAt <= now) ||
|
|
141
|
+
(subscription.status === "trialing" &&
|
|
142
|
+
subscription.trialEnd &&
|
|
143
|
+
subscription.trialEnd <= now)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const product = subscription.productId
|
|
147
|
+
? (await ctx.db
|
|
148
|
+
.query("products")
|
|
149
|
+
.withIndex("id", (q) => q.eq("id", subscription.productId))
|
|
150
|
+
.unique()) || null
|
|
151
|
+
: null;
|
|
152
|
+
return {
|
|
153
|
+
...omitSystemFields(subscription),
|
|
154
|
+
product: omitSystemFields(product),
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
return subscriptions.flatMap((subscription) => subscription ? [subscription] : []);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
/** Returns all subscriptions for a user, including ended and expired trials. */
|
|
161
|
+
export const listAllUserSubscriptions = query({
|
|
162
|
+
args: {
|
|
163
|
+
entityId: v.string(),
|
|
164
|
+
},
|
|
165
|
+
returns: v.array(v.object({
|
|
166
|
+
...schema.tables.subscriptions.validator.fields,
|
|
167
|
+
product: v.union(schema.tables.products.validator, v.null()),
|
|
168
|
+
})),
|
|
169
|
+
handler: async (ctx, args) => {
|
|
170
|
+
const customer = await ctx.db
|
|
171
|
+
.query("customers")
|
|
172
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
173
|
+
.unique();
|
|
174
|
+
if (!customer) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const subscriptions = await asyncMap(ctx.db
|
|
178
|
+
.query("subscriptions")
|
|
179
|
+
.withIndex("customerId", (q) => q.eq("customerId", customer.id))
|
|
180
|
+
.collect(), async (subscription) => {
|
|
181
|
+
const product = subscription.productId
|
|
182
|
+
? (await ctx.db
|
|
183
|
+
.query("products")
|
|
184
|
+
.withIndex("id", (q) => q.eq("id", subscription.productId))
|
|
185
|
+
.unique()) || null
|
|
186
|
+
: null;
|
|
187
|
+
return {
|
|
188
|
+
...omitSystemFields(subscription),
|
|
189
|
+
product: omitSystemFields(product),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
return subscriptions;
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
export const listProducts = query({
|
|
196
|
+
args: {
|
|
197
|
+
includeArchived: v.optional(v.boolean()),
|
|
198
|
+
},
|
|
199
|
+
returns: v.array(schema.tables.products.validator),
|
|
200
|
+
handler: async (ctx, args) => {
|
|
201
|
+
const q = ctx.db.query("products");
|
|
202
|
+
const products = args.includeArchived
|
|
203
|
+
? await q.collect()
|
|
204
|
+
: await q.withIndex("status", (q) => q.eq("status", "active")).collect();
|
|
205
|
+
return products.map((product) => omitSystemFields(product));
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
export const createSubscription = mutation({
|
|
209
|
+
args: {
|
|
210
|
+
subscription: schema.tables.subscriptions.validator,
|
|
211
|
+
},
|
|
212
|
+
handler: async (ctx, args) => {
|
|
213
|
+
const existingSubscription = await ctx.db
|
|
214
|
+
.query("subscriptions")
|
|
215
|
+
.withIndex("id", (q) => q.eq("id", args.subscription.id))
|
|
216
|
+
.unique();
|
|
217
|
+
if (!existingSubscription) {
|
|
218
|
+
await ctx.db.insert("subscriptions", args.subscription);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// Timestamp guard: skip if existing record is newer
|
|
222
|
+
const incomingModifiedAt = args.subscription.modifiedAt ?? "";
|
|
223
|
+
const existingModifiedAt = existingSubscription.modifiedAt ?? "";
|
|
224
|
+
if (existingModifiedAt > incomingModifiedAt) {
|
|
225
|
+
return; // stale webhook, skip
|
|
226
|
+
}
|
|
227
|
+
await ctx.db.patch(existingSubscription._id, args.subscription);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
export const updateSubscription = mutation({
|
|
231
|
+
args: {
|
|
232
|
+
subscription: schema.tables.subscriptions.validator,
|
|
233
|
+
},
|
|
234
|
+
handler: async (ctx, args) => {
|
|
235
|
+
const existingSubscription = await ctx.db
|
|
236
|
+
.query("subscriptions")
|
|
237
|
+
.withIndex("id", (q) => q.eq("id", args.subscription.id))
|
|
238
|
+
.unique();
|
|
239
|
+
if (!existingSubscription) {
|
|
240
|
+
// Subscription doesn't exist yet — insert instead of throwing
|
|
241
|
+
await ctx.db.insert("subscriptions", args.subscription);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Timestamp guard: skip if existing record is newer
|
|
245
|
+
const incomingModifiedAt = args.subscription.modifiedAt ?? "";
|
|
246
|
+
const existingModifiedAt = existingSubscription.modifiedAt ?? "";
|
|
247
|
+
if (existingModifiedAt > incomingModifiedAt) {
|
|
248
|
+
return; // stale webhook, skip
|
|
249
|
+
}
|
|
250
|
+
// Optimistic-update guard: if a recent patchSubscription set optimistic
|
|
251
|
+
// fields, don't let intermediate webhook events revert those values.
|
|
252
|
+
const existingMeta = (existingSubscription.metadata ?? {});
|
|
253
|
+
const pendingAt = existingMeta._optimisticPendingAt;
|
|
254
|
+
const optimisticFields = existingMeta._optimisticFields;
|
|
255
|
+
const isOptimisticPending = pendingAt != null && Date.now() - pendingAt < 30_000;
|
|
256
|
+
const subscriptionToWrite = { ...args.subscription };
|
|
257
|
+
if (isOptimisticPending && optimisticFields?.length) {
|
|
258
|
+
console.debug(`[creem] optimistic guard active for sub=${args.subscription.id}`, {
|
|
259
|
+
guardFields: optimisticFields,
|
|
260
|
+
guardAge: `${Math.round((Date.now() - (pendingAt ?? 0)) / 1000)}s`,
|
|
261
|
+
incoming: {
|
|
262
|
+
productId: args.subscription.productId,
|
|
263
|
+
seats: args.subscription.seats,
|
|
264
|
+
},
|
|
265
|
+
db: {
|
|
266
|
+
productId: existingSubscription.productId,
|
|
267
|
+
seats: existingSubscription.seats,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
let allConfirmed = true;
|
|
271
|
+
if (optimisticFields.includes("seats")) {
|
|
272
|
+
if (args.subscription.seats !== existingSubscription.seats) {
|
|
273
|
+
subscriptionToWrite.seats = existingSubscription.seats;
|
|
274
|
+
allConfirmed = false;
|
|
275
|
+
console.log(`[creem] guard: preserving optimistic seats=${existingSubscription.seats} (webhook sent ${args.subscription.seats})`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (optimisticFields.includes("productId")) {
|
|
279
|
+
if (args.subscription.productId !== existingSubscription.productId) {
|
|
280
|
+
subscriptionToWrite.productId = existingSubscription.productId;
|
|
281
|
+
allConfirmed = false;
|
|
282
|
+
console.log(`[creem] guard: preserving optimistic productId=${existingSubscription.productId} (webhook sent ${args.subscription.productId})`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Only clear the guard when ALL tracked fields match in a single webhook.
|
|
286
|
+
// Partial matches are not trusted — Creem sends intermediate states where
|
|
287
|
+
// some fields update temporarily before reverting (e.g. subscription.product
|
|
288
|
+
// changes on upgrade but items[0].product_id stays stale).
|
|
289
|
+
const incomingMeta = (args.subscription.metadata ?? {});
|
|
290
|
+
if (allConfirmed) {
|
|
291
|
+
console.log(`[creem] guard: all optimistic fields confirmed for sub=${args.subscription.id} — clearing`);
|
|
292
|
+
const { _optimisticPendingAt: _, _optimisticFields: __, ...cleanMeta } = { ...existingMeta, ...incomingMeta };
|
|
293
|
+
subscriptionToWrite.metadata = cleanMeta;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
subscriptionToWrite.metadata = {
|
|
297
|
+
...existingMeta,
|
|
298
|
+
...incomingMeta,
|
|
299
|
+
_optimisticPendingAt: pendingAt,
|
|
300
|
+
_optimisticFields: optimisticFields,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
await ctx.db.patch(existingSubscription._id, subscriptionToWrite);
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
export const createProduct = mutation({
|
|
308
|
+
args: {
|
|
309
|
+
product: schema.tables.products.validator,
|
|
310
|
+
},
|
|
311
|
+
handler: async (ctx, args) => {
|
|
312
|
+
const existingProduct = await ctx.db
|
|
313
|
+
.query("products")
|
|
314
|
+
.withIndex("id", (q) => q.eq("id", args.product.id))
|
|
315
|
+
.unique();
|
|
316
|
+
if (!existingProduct) {
|
|
317
|
+
await ctx.db.insert("products", args.product);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// Timestamp guard: skip if existing record is newer
|
|
321
|
+
const incomingModifiedAt = args.product.modifiedAt ?? "";
|
|
322
|
+
const existingModifiedAt = existingProduct.modifiedAt ?? "";
|
|
323
|
+
if (existingModifiedAt > incomingModifiedAt) {
|
|
324
|
+
return; // stale webhook, skip
|
|
325
|
+
}
|
|
326
|
+
await ctx.db.patch(existingProduct._id, args.product);
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
export const updateProduct = mutation({
|
|
330
|
+
args: {
|
|
331
|
+
product: schema.tables.products.validator,
|
|
332
|
+
},
|
|
333
|
+
handler: async (ctx, args) => {
|
|
334
|
+
const existingProduct = await ctx.db
|
|
335
|
+
.query("products")
|
|
336
|
+
.withIndex("id", (q) => q.eq("id", args.product.id))
|
|
337
|
+
.unique();
|
|
338
|
+
if (!existingProduct) {
|
|
339
|
+
// Product doesn't exist yet — insert instead of throwing
|
|
340
|
+
await ctx.db.insert("products", args.product);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Timestamp guard: skip if existing record is newer
|
|
344
|
+
const incomingModifiedAt = args.product.modifiedAt ?? "";
|
|
345
|
+
const existingModifiedAt = existingProduct.modifiedAt ?? "";
|
|
346
|
+
if (existingModifiedAt > incomingModifiedAt) {
|
|
347
|
+
return; // stale webhook, skip
|
|
348
|
+
}
|
|
349
|
+
await ctx.db.patch(existingProduct._id, args.product);
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
export const createOrder = mutation({
|
|
353
|
+
args: {
|
|
354
|
+
order: schema.tables.orders.validator,
|
|
355
|
+
},
|
|
356
|
+
handler: async (ctx, args) => {
|
|
357
|
+
const existing = await ctx.db
|
|
358
|
+
.query("orders")
|
|
359
|
+
.withIndex("id", (q) => q.eq("id", args.order.id))
|
|
360
|
+
.unique();
|
|
361
|
+
if (!existing) {
|
|
362
|
+
await ctx.db.insert("orders", args.order);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Update if incoming is newer
|
|
366
|
+
if ((args.order.updatedAt ?? "") >= (existing.updatedAt ?? "")) {
|
|
367
|
+
await ctx.db.patch(existing._id, args.order);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
/** List paid one-time orders for a user. */
|
|
372
|
+
export const listUserOrders = query({
|
|
373
|
+
args: {
|
|
374
|
+
entityId: v.string(),
|
|
375
|
+
},
|
|
376
|
+
returns: v.array(schema.tables.orders.validator),
|
|
377
|
+
handler: async (ctx, args) => {
|
|
378
|
+
const customer = await ctx.db
|
|
379
|
+
.query("customers")
|
|
380
|
+
.withIndex("entityId", (q) => q.eq("entityId", args.entityId))
|
|
381
|
+
.unique();
|
|
382
|
+
if (!customer) {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
const orders = await ctx.db
|
|
386
|
+
.query("orders")
|
|
387
|
+
.withIndex("customerId", (q) => q.eq("customerId", customer.id))
|
|
388
|
+
.collect();
|
|
389
|
+
return orders
|
|
390
|
+
.filter((o) => o.status === "paid" && o.type === "onetime")
|
|
391
|
+
.map(omitSystemFields);
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
export const listCustomerSubscriptions = query({
|
|
395
|
+
args: {
|
|
396
|
+
customerId: v.string(),
|
|
397
|
+
},
|
|
398
|
+
returns: v.array(schema.tables.subscriptions.validator),
|
|
399
|
+
handler: async (ctx, args) => {
|
|
400
|
+
const subscriptions = await ctx.db
|
|
401
|
+
.query("subscriptions")
|
|
402
|
+
.withIndex("customerId", (q) => q.eq("customerId", args.customerId))
|
|
403
|
+
.collect();
|
|
404
|
+
return subscriptions.map(omitSystemFields);
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
export const syncProducts = action({
|
|
408
|
+
args: {
|
|
409
|
+
apiKey: v.string(),
|
|
410
|
+
serverIdx: v.optional(v.number()),
|
|
411
|
+
serverURL: v.optional(v.string()),
|
|
412
|
+
},
|
|
413
|
+
handler: async (ctx, args) => {
|
|
414
|
+
const creem = new Creem({
|
|
415
|
+
apiKey: args.apiKey,
|
|
416
|
+
...(args.serverIdx !== undefined ? { serverIdx: args.serverIdx } : {}),
|
|
417
|
+
...(args.serverURL ? { serverURL: args.serverURL } : {}),
|
|
418
|
+
});
|
|
419
|
+
let pageNumber = 1;
|
|
420
|
+
let isDone = false;
|
|
421
|
+
do {
|
|
422
|
+
const products = await creem.products.search(pageNumber, 100);
|
|
423
|
+
pageNumber += 1;
|
|
424
|
+
isDone =
|
|
425
|
+
products.pagination.currentPage >= products.pagination.totalPages;
|
|
426
|
+
await ctx.runMutation(api.lib.updateProducts, {
|
|
427
|
+
products: products.items.map(convertToDatabaseProduct),
|
|
428
|
+
});
|
|
429
|
+
} while (!isDone);
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
export const updateProducts = mutation({
|
|
433
|
+
args: {
|
|
434
|
+
products: v.array(schema.tables.products.validator),
|
|
435
|
+
},
|
|
436
|
+
handler: async (ctx, args) => {
|
|
437
|
+
await asyncMap(args.products, async (product) => {
|
|
438
|
+
const existingProduct = await ctx.db
|
|
439
|
+
.query("products")
|
|
440
|
+
.withIndex("id", (q) => q.eq("id", product.id))
|
|
441
|
+
.unique();
|
|
442
|
+
if (existingProduct) {
|
|
443
|
+
await ctx.db.patch(existingProduct._id, product);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
await ctx.db.insert("products", product);
|
|
447
|
+
});
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
/** Lightweight patch for optimistic UI updates (seats, productId, status).
|
|
451
|
+
* Tracks which fields were optimistically changed via `_optimisticPendingAt`
|
|
452
|
+
* and `_optimisticFields` in the subscription's metadata so that incoming
|
|
453
|
+
* webhooks with stale intermediate values don't overwrite the optimistic state. */
|
|
454
|
+
export const patchSubscription = mutation({
|
|
455
|
+
args: {
|
|
456
|
+
subscriptionId: v.string(),
|
|
457
|
+
seats: v.optional(v.union(v.number(), v.null())),
|
|
458
|
+
productId: v.optional(v.string()),
|
|
459
|
+
status: v.optional(v.string()),
|
|
460
|
+
cancelAtPeriodEnd: v.optional(v.boolean()),
|
|
461
|
+
clearOptimistic: v.optional(v.boolean()),
|
|
462
|
+
},
|
|
463
|
+
handler: async (ctx, args) => {
|
|
464
|
+
const sub = await ctx.db
|
|
465
|
+
.query("subscriptions")
|
|
466
|
+
.withIndex("id", (q) => q.eq("id", args.subscriptionId))
|
|
467
|
+
.unique();
|
|
468
|
+
if (!sub)
|
|
469
|
+
throw new ConvexError(`Subscription not found: ${args.subscriptionId}`);
|
|
470
|
+
const patch = {};
|
|
471
|
+
const optimisticFields = [];
|
|
472
|
+
if (args.seats !== undefined) {
|
|
473
|
+
patch.seats = args.seats;
|
|
474
|
+
optimisticFields.push("seats");
|
|
475
|
+
}
|
|
476
|
+
if (args.productId !== undefined) {
|
|
477
|
+
patch.productId = args.productId;
|
|
478
|
+
optimisticFields.push("productId");
|
|
479
|
+
}
|
|
480
|
+
if (args.status !== undefined)
|
|
481
|
+
patch.status = args.status;
|
|
482
|
+
if (args.cancelAtPeriodEnd !== undefined)
|
|
483
|
+
patch.cancelAtPeriodEnd = args.cancelAtPeriodEnd;
|
|
484
|
+
// Track optimistic fields so updateSubscription can guard against stale webhooks.
|
|
485
|
+
// Merge with any existing optimistic fields (cumulative across consecutive patches).
|
|
486
|
+
const existingMeta = (sub.metadata ?? {});
|
|
487
|
+
if (args.clearOptimistic) {
|
|
488
|
+
const { _optimisticPendingAt: _, _optimisticFields: __, ...cleanMeta } = existingMeta;
|
|
489
|
+
patch.metadata = cleanMeta;
|
|
490
|
+
}
|
|
491
|
+
else if (optimisticFields.length > 0) {
|
|
492
|
+
const existingOptimistic = existingMeta._optimisticFields ?? [];
|
|
493
|
+
const mergedOptimistic = [
|
|
494
|
+
...new Set([...existingOptimistic, ...optimisticFields]),
|
|
495
|
+
];
|
|
496
|
+
patch.metadata = {
|
|
497
|
+
...existingMeta,
|
|
498
|
+
_optimisticPendingAt: Date.now(),
|
|
499
|
+
_optimisticFields: mergedOptimistic,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (Object.keys(patch).length > 0) {
|
|
503
|
+
if (optimisticFields.length > 0 || args.clearOptimistic) {
|
|
504
|
+
console.log(`[creem] optimistic patch sub=${args.subscriptionId}`, {
|
|
505
|
+
fields: optimisticFields,
|
|
506
|
+
...(args.seats !== undefined ? { seats: args.seats } : {}),
|
|
507
|
+
...(args.productId !== undefined
|
|
508
|
+
? { productId: args.productId }
|
|
509
|
+
: {}),
|
|
510
|
+
...(args.clearOptimistic ? { clear: true } : {}),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
await ctx.db.patch(sub._id, patch);
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
/** Action that calls Creem API and reverts on error. Scheduled by mutations.
|
|
518
|
+
* Public (not internal) so it's accessible via ComponentApi for scheduling from app-level mutations.
|
|
519
|
+
* Secured by requiring apiKey argument (same pattern as syncProducts). */
|
|
520
|
+
export const executeSubscriptionUpdate = action({
|
|
521
|
+
args: {
|
|
522
|
+
apiKey: v.string(),
|
|
523
|
+
serverIdx: v.optional(v.number()),
|
|
524
|
+
serverURL: v.optional(v.string()),
|
|
525
|
+
subscriptionId: v.string(),
|
|
526
|
+
productId: v.optional(v.string()),
|
|
527
|
+
units: v.optional(v.number()),
|
|
528
|
+
updateBehavior: v.optional(v.string()),
|
|
529
|
+
previousSeats: v.optional(v.union(v.number(), v.null())),
|
|
530
|
+
previousProductId: v.optional(v.string()),
|
|
531
|
+
},
|
|
532
|
+
handler: async (ctx, args) => {
|
|
533
|
+
const sdk = new Creem({
|
|
534
|
+
apiKey: args.apiKey,
|
|
535
|
+
...(args.serverIdx !== undefined ? { serverIdx: args.serverIdx } : {}),
|
|
536
|
+
...(args.serverURL ? { serverURL: args.serverURL } : {}),
|
|
537
|
+
});
|
|
538
|
+
try {
|
|
539
|
+
if (args.productId) {
|
|
540
|
+
// Plan/interval switch
|
|
541
|
+
await sdk.subscriptions.upgrade(args.subscriptionId, {
|
|
542
|
+
productId: args.productId,
|
|
543
|
+
...(args.updateBehavior
|
|
544
|
+
? {
|
|
545
|
+
updateBehavior: args.updateBehavior,
|
|
546
|
+
}
|
|
547
|
+
: {}),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
else if (args.units !== undefined) {
|
|
551
|
+
// Seat update — need live item IDs from Creem
|
|
552
|
+
const live = await sdk.subscriptions.get(args.subscriptionId);
|
|
553
|
+
const item = live.items?.[0];
|
|
554
|
+
if (!item)
|
|
555
|
+
throw new ConvexError("Subscription has no items");
|
|
556
|
+
await sdk.subscriptions.update(args.subscriptionId, {
|
|
557
|
+
items: [
|
|
558
|
+
{
|
|
559
|
+
id: item.id,
|
|
560
|
+
productId: item.productId,
|
|
561
|
+
priceId: item.priceId,
|
|
562
|
+
units: args.units,
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
...(args.updateBehavior
|
|
566
|
+
? {
|
|
567
|
+
updateBehavior: args.updateBehavior,
|
|
568
|
+
}
|
|
569
|
+
: {}),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
console.error(`[creem] subscription update failed:`, error);
|
|
575
|
+
// Revert optimistic state and clear the optimistic guard so webhooks write normally
|
|
576
|
+
await ctx.runMutation(api.lib.patchSubscription, {
|
|
577
|
+
subscriptionId: args.subscriptionId,
|
|
578
|
+
...(args.previousSeats !== undefined
|
|
579
|
+
? { seats: args.previousSeats }
|
|
580
|
+
: {}),
|
|
581
|
+
...(args.previousProductId
|
|
582
|
+
? { productId: args.previousProductId }
|
|
583
|
+
: {}),
|
|
584
|
+
clearOptimistic: true,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
/** Action that calls Creem API for cancel/resume/pause and reverts on error.
|
|
590
|
+
* Scheduled by the corresponding mutations in api(). */
|
|
591
|
+
export const executeSubscriptionLifecycle = action({
|
|
592
|
+
args: {
|
|
593
|
+
apiKey: v.string(),
|
|
594
|
+
serverIdx: v.optional(v.number()),
|
|
595
|
+
serverURL: v.optional(v.string()),
|
|
596
|
+
subscriptionId: v.string(),
|
|
597
|
+
operation: v.union(v.literal("cancel"), v.literal("resume"), v.literal("pause")),
|
|
598
|
+
cancelMode: v.optional(v.string()),
|
|
599
|
+
// For reverting on error:
|
|
600
|
+
previousStatus: v.optional(v.string()),
|
|
601
|
+
previousCancelAtPeriodEnd: v.optional(v.boolean()),
|
|
602
|
+
},
|
|
603
|
+
handler: async (ctx, args) => {
|
|
604
|
+
const sdk = new Creem({
|
|
605
|
+
apiKey: args.apiKey,
|
|
606
|
+
...(args.serverIdx !== undefined ? { serverIdx: args.serverIdx } : {}),
|
|
607
|
+
...(args.serverURL ? { serverURL: args.serverURL } : {}),
|
|
608
|
+
});
|
|
609
|
+
try {
|
|
610
|
+
if (args.operation === "cancel") {
|
|
611
|
+
const cancelParams = args.cancelMode === "immediate"
|
|
612
|
+
? { mode: "immediate" }
|
|
613
|
+
: args.cancelMode === "scheduled"
|
|
614
|
+
? { mode: "scheduled", onExecute: "cancel" }
|
|
615
|
+
: {};
|
|
616
|
+
await sdk.subscriptions.cancel(args.subscriptionId, cancelParams);
|
|
617
|
+
}
|
|
618
|
+
else if (args.operation === "resume") {
|
|
619
|
+
await sdk.subscriptions.resume(args.subscriptionId);
|
|
620
|
+
}
|
|
621
|
+
else if (args.operation === "pause") {
|
|
622
|
+
await sdk.subscriptions.pause(args.subscriptionId);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
console.error(`[creem] subscription ${args.operation} failed:`, error);
|
|
627
|
+
// Revert optimistic state
|
|
628
|
+
await ctx.runMutation(api.lib.patchSubscription, {
|
|
629
|
+
subscriptionId: args.subscriptionId,
|
|
630
|
+
...(args.previousStatus !== undefined
|
|
631
|
+
? { status: args.previousStatus }
|
|
632
|
+
: {}),
|
|
633
|
+
...(args.previousCancelAtPeriodEnd !== undefined
|
|
634
|
+
? { cancelAtPeriodEnd: args.previousCancelAtPeriodEnd }
|
|
635
|
+
: {}),
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
export const omitSystemFields = (doc) => {
|
|
641
|
+
if (!doc) {
|
|
642
|
+
return doc;
|
|
643
|
+
}
|
|
644
|
+
const { _id, _creationTime, ...rest } = doc;
|
|
645
|
+
return rest;
|
|
646
|
+
};
|
|
647
|
+
//# sourceMappingURL=lib.js.map
|