@funnelsgrove/payments 0.1.0 → 0.1.2

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.
Files changed (44) hide show
  1. package/README.md +32 -3
  2. package/dist/components/shared/ApplePaySubscribeButton.d.ts +15 -0
  3. package/dist/components/shared/ApplePaySubscribeButton.js +76 -0
  4. package/dist/components/shared/CheckoutBrandAssets.d.ts +12 -0
  5. package/dist/components/shared/CheckoutBrandAssets.js +56 -0
  6. package/dist/components/shared/GooglePaySubscribeButton.d.ts +14 -0
  7. package/dist/components/shared/GooglePaySubscribeButton.js +94 -0
  8. package/dist/components/shared/SharedStripeCheckoutDialog.d.ts +44 -0
  9. package/dist/components/shared/SharedStripeCheckoutDialog.js +717 -0
  10. package/dist/components/shared/SharedStripeCheckoutV2Dialog.d.ts +60 -0
  11. package/dist/components/shared/SharedStripeCheckoutV2Dialog.js +988 -0
  12. package/dist/components/shared/StripeExpressCheckoutButton.d.ts +18 -0
  13. package/dist/components/shared/StripeExpressCheckoutButton.js +164 -0
  14. package/dist/components/shared/StripeExpressCheckoutElement.d.ts +8 -0
  15. package/dist/components/shared/StripeExpressCheckoutElement.js +19 -0
  16. package/dist/components/shared/StripePlanSelector.d.ts +5 -7
  17. package/dist/components/shared/StripePlanSelector.js +16 -57
  18. package/dist/components/shared/walletPlatform.d.ts +11 -0
  19. package/dist/components/shared/walletPlatform.js +39 -0
  20. package/dist/config/billing.config.d.ts +60 -9
  21. package/dist/config/billing.config.js +7 -2
  22. package/dist/hooks/useResolvedPaywallPlans.d.ts +15 -0
  23. package/dist/hooks/useResolvedPaywallPlans.js +68 -0
  24. package/dist/index.d.ts +21 -5
  25. package/dist/index.js +21 -5
  26. package/dist/providers/paymentProvider.types.d.ts +4 -0
  27. package/dist/providers/paymentProvider.types.js +4 -0
  28. package/dist/providers/stripe/ApplePaySubscriptionCheckoutSlot.d.ts +5 -0
  29. package/dist/providers/stripe/ApplePaySubscriptionCheckoutSlot.js +21 -0
  30. package/dist/providers/stripe/GooglePaySubscriptionCheckoutSlot.d.ts +7 -0
  31. package/dist/providers/stripe/GooglePaySubscriptionCheckoutSlot.js +44 -0
  32. package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.d.ts +32 -0
  33. package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.js +69 -0
  34. package/dist/providers/stripe/useStripeSubscriptionCheckoutSession.d.ts +30 -0
  35. package/dist/providers/stripe/useStripeSubscriptionCheckoutSession.js +164 -0
  36. package/dist/services/paywallOffer.service.d.ts +65 -0
  37. package/dist/services/paywallOffer.service.js +312 -0
  38. package/dist/services/planCatalog.service.d.ts +28 -0
  39. package/dist/services/planCatalog.service.js +60 -0
  40. package/dist/services/runtimeBillingPlanCatalog.service.d.ts +6 -0
  41. package/dist/services/runtimeBillingPlanCatalog.service.js +24 -0
  42. package/dist/services/stripe.service.d.ts +66 -6
  43. package/dist/services/stripe.service.js +409 -54
  44. package/package.json +3 -3
@@ -0,0 +1,18 @@
1
+ import type { AvailablePaymentMethods, StripeExpressCheckoutElementOptions } from '@stripe/stripe-js';
2
+ export type StripeExpressCheckoutButtonProps = {
3
+ amountCents: number;
4
+ beforeConfirm?: () => Promise<string | null>;
5
+ summaryLabel: string;
6
+ returnUrl: string;
7
+ customerEmail?: string | null;
8
+ customerName?: string | null;
9
+ className?: string;
10
+ options?: StripeExpressCheckoutElementOptions;
11
+ availabilityPaymentMethods?: readonly (keyof AvailablePaymentMethods)[];
12
+ initialAvailable?: boolean | null;
13
+ keepInitialAvailableOnReady?: boolean;
14
+ onAvailabilityChange?: (available: boolean) => void;
15
+ onError?: (message: string | null) => void;
16
+ onSuccess?: () => void;
17
+ };
18
+ export declare function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, onAvailabilityChange, onError, onSuccess, }: StripeExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useMemo, useState } from 'react';
4
+ import { ExpressCheckoutElement, useElements, useStripe } from '@stripe/react-stripe-js';
5
+ const normalizeBillingName = (input) => {
6
+ var _a, _b, _c;
7
+ const normalizedName = (_a = input.name) === null || _a === void 0 ? void 0 : _a.trim();
8
+ if (normalizedName) {
9
+ return normalizedName;
10
+ }
11
+ const normalizedEmail = (_b = input.email) === null || _b === void 0 ? void 0 : _b.trim();
12
+ if (normalizedEmail) {
13
+ const localPart = (_c = normalizedEmail
14
+ .split('@')[0]) === null || _c === void 0 ? void 0 : _c.replace(/[._-]+/g, ' ').trim();
15
+ if (localPart) {
16
+ return localPart;
17
+ }
18
+ }
19
+ return 'Customer';
20
+ };
21
+ const buildDefaultOptions = (input) => {
22
+ return {
23
+ buttonHeight: 55,
24
+ buttonTheme: {
25
+ applePay: 'black',
26
+ },
27
+ buttonType: {
28
+ applePay: 'subscribe',
29
+ },
30
+ emailRequired: true,
31
+ layout: {
32
+ maxColumns: 1,
33
+ maxRows: 1,
34
+ overflow: 'auto',
35
+ },
36
+ lineItems: [
37
+ {
38
+ name: input.summaryLabel.trim() || 'Selected plan',
39
+ amount: input.amountCents,
40
+ },
41
+ ],
42
+ paymentMethodOrder: ['apple_pay'],
43
+ paymentMethods: {
44
+ amazonPay: 'never',
45
+ applePay: 'always',
46
+ googlePay: 'never',
47
+ klarna: 'never',
48
+ link: 'never',
49
+ paypal: 'never',
50
+ },
51
+ };
52
+ };
53
+ const isSuccessfulIntentStatus = (status) => {
54
+ return status === 'succeeded' || status === 'processing' || status === 'requires_capture';
55
+ };
56
+ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, onAvailabilityChange, onError, onSuccess, }) {
57
+ const stripe = useStripe();
58
+ const elements = useElements();
59
+ const [walletAvailable, setWalletAvailable] = useState(initialAvailable !== null && initialAvailable !== void 0 ? initialAvailable : null);
60
+ const effectiveWalletAvailable = initialAvailable === true && walletAvailable !== false
61
+ ? true
62
+ : walletAvailable;
63
+ const baseOptions = useMemo(() => buildDefaultOptions({
64
+ amountCents,
65
+ summaryLabel,
66
+ }), [amountCents, summaryLabel]);
67
+ const resolvedOptions = useMemo(() => {
68
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
69
+ return Object.assign(Object.assign(Object.assign({}, baseOptions), (options !== null && options !== void 0 ? options : {})), { buttonTheme: Object.assign(Object.assign({}, ((_a = baseOptions.buttonTheme) !== null && _a !== void 0 ? _a : {})), ((_b = options === null || options === void 0 ? void 0 : options.buttonTheme) !== null && _b !== void 0 ? _b : {})), buttonType: Object.assign(Object.assign({}, ((_c = baseOptions.buttonType) !== null && _c !== void 0 ? _c : {})), ((_d = options === null || options === void 0 ? void 0 : options.buttonType) !== null && _d !== void 0 ? _d : {})), layout: Object.assign(Object.assign({}, ((_e = baseOptions.layout) !== null && _e !== void 0 ? _e : {})), ((_f = options === null || options === void 0 ? void 0 : options.layout) !== null && _f !== void 0 ? _f : {})), paymentMethods: Object.assign(Object.assign({}, ((_g = baseOptions.paymentMethods) !== null && _g !== void 0 ? _g : {})), ((_h = options === null || options === void 0 ? void 0 : options.paymentMethods) !== null && _h !== void 0 ? _h : {})), lineItems: (_j = options === null || options === void 0 ? void 0 : options.lineItems) !== null && _j !== void 0 ? _j : baseOptions.lineItems, paymentMethodOrder: (_k = options === null || options === void 0 ? void 0 : options.paymentMethodOrder) !== null && _k !== void 0 ? _k : baseOptions.paymentMethodOrder });
70
+ }, [baseOptions, options]);
71
+ const handleConfirm = async (event) => {
72
+ var _a;
73
+ if (!stripe || !elements) {
74
+ const message = 'Wallet checkout is not ready yet.';
75
+ onError === null || onError === void 0 ? void 0 : onError(message);
76
+ event.paymentFailed({ reason: 'fail', message });
77
+ return;
78
+ }
79
+ onError === null || onError === void 0 ? void 0 : onError(null);
80
+ let preparedCustomerEmail = (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || null;
81
+ if (beforeConfirm) {
82
+ try {
83
+ preparedCustomerEmail = await beforeConfirm();
84
+ }
85
+ catch (confirmError) {
86
+ const message = confirmError instanceof Error && confirmError.message
87
+ ? confirmError.message
88
+ : 'Unable to prepare wallet checkout.';
89
+ onError === null || onError === void 0 ? void 0 : onError(message);
90
+ event.paymentFailed({ reason: 'invalid_payment_data', message });
91
+ return;
92
+ }
93
+ }
94
+ if (beforeConfirm && !preparedCustomerEmail) {
95
+ const message = 'Email is required to continue.';
96
+ onError === null || onError === void 0 ? void 0 : onError(message);
97
+ event.paymentFailed({ reason: 'invalid_payment_data', message });
98
+ return;
99
+ }
100
+ const submitResult = await elements.submit();
101
+ if (submitResult.error) {
102
+ const message = submitResult.error.message || 'Unable to start wallet checkout.';
103
+ onError === null || onError === void 0 ? void 0 : onError(message);
104
+ event.paymentFailed({ reason: 'invalid_payment_data', message });
105
+ return;
106
+ }
107
+ const result = await stripe.confirmPayment({
108
+ elements,
109
+ confirmParams: {
110
+ payment_method_data: {
111
+ billing_details: {
112
+ email: preparedCustomerEmail || (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || undefined,
113
+ name: normalizeBillingName({
114
+ email: preparedCustomerEmail || customerEmail,
115
+ name: customerName,
116
+ }),
117
+ },
118
+ },
119
+ return_url: returnUrl,
120
+ },
121
+ redirect: 'if_required',
122
+ });
123
+ if (result.error) {
124
+ const message = result.error.message || 'Wallet checkout failed.';
125
+ onError === null || onError === void 0 ? void 0 : onError(message);
126
+ event.paymentFailed({ reason: 'fail', message });
127
+ return;
128
+ }
129
+ onError === null || onError === void 0 ? void 0 : onError(null);
130
+ if (typeof window !== 'undefined' &&
131
+ isSuccessfulIntentStatus((_a = result.paymentIntent) === null || _a === void 0 ? void 0 : _a.status)) {
132
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess();
133
+ window.location.assign(returnUrl);
134
+ }
135
+ };
136
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: [
137
+ 'stripe-express-checkout-button',
138
+ className !== null && className !== void 0 ? className : '',
139
+ effectiveWalletAvailable === false ? 'is-hidden' : '',
140
+ ]
141
+ .filter(Boolean)
142
+ .join(' '), children: _jsx(ExpressCheckoutElement, { options: resolvedOptions, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
143
+ setWalletAvailable(false);
144
+ onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(false);
145
+ onError === null || onError === void 0 ? void 0 : onError(error.message || 'Unable to load wallet checkout.');
146
+ }, onReady: ({ availablePaymentMethods: availablePaymentMethodsMap }) => {
147
+ const available = Boolean(availabilityPaymentMethods.some((paymentMethod) => availablePaymentMethodsMap === null || availablePaymentMethodsMap === void 0 ? void 0 : availablePaymentMethodsMap[paymentMethod]));
148
+ const resolvedAvailable = available || (keepInitialAvailableOnReady && initialAvailable === true);
149
+ setWalletAvailable(resolvedAvailable);
150
+ onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(resolvedAvailable);
151
+ if (resolvedAvailable) {
152
+ onError === null || onError === void 0 ? void 0 : onError(null);
153
+ }
154
+ } }) }), _jsx("style", { children: stripeExpressCheckoutButtonStyles })] }));
155
+ }
156
+ const stripeExpressCheckoutButtonStyles = `
157
+ .stripe-express-checkout-button {
158
+ width: 100%;
159
+ }
160
+
161
+ .stripe-express-checkout-button.is-hidden {
162
+ display: none;
163
+ }
164
+ `;
@@ -0,0 +1,8 @@
1
+ import type { Appearance, Stripe } from '@stripe/stripe-js';
2
+ import { type StripeExpressCheckoutButtonProps } from './StripeExpressCheckoutButton.js';
3
+ export type StripeExpressCheckoutElementProps = StripeExpressCheckoutButtonProps & {
4
+ appearance?: Appearance;
5
+ clientSecret: string;
6
+ stripePromise: Promise<Stripe | null>;
7
+ };
8
+ export declare function StripeExpressCheckoutElement({ appearance, clientSecret, stripePromise, ...buttonProps }: StripeExpressCheckoutElementProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ import { jsx as _jsx } from "react/jsx-runtime";
14
+ import { Elements } from '@stripe/react-stripe-js';
15
+ import { StripeExpressCheckoutButton, } from './StripeExpressCheckoutButton.js';
16
+ export function StripeExpressCheckoutElement(_a) {
17
+ var { appearance, clientSecret, stripePromise } = _a, buttonProps = __rest(_a, ["appearance", "clientSecret", "stripePromise"]);
18
+ return (_jsx(Elements, { stripe: stripePromise, options: Object.assign({ clientSecret }, (appearance ? { appearance } : {})), children: _jsx(StripeExpressCheckoutButton, Object.assign({}, buttonProps)) }));
19
+ }
@@ -1,13 +1,11 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { BillingFallbackPlanConfig, BillingPlanCatalog } from '../../config/billing.config';
3
- import { type PaywallPlan } from '../../services/stripe.service';
4
- import type { RuntimeMode } from '@funnelsgrove/runtime';
2
+ import { type PaywallPlan } from '../../services/stripe.service.js';
5
3
  type StripePlanSelectorProps = {
6
- planCatalog?: BillingPlanCatalog | readonly BillingFallbackPlanConfig[];
7
- runtimeMode: RuntimeMode;
4
+ plans?: readonly PaywallPlan[] | null;
8
5
  selectedPlanId: string | null;
9
6
  onSelectPlan: (planId: string) => void;
10
- onPlansLoaded?: (plans: readonly PaywallPlan[]) => void;
7
+ loading?: boolean;
8
+ error?: string | null;
11
9
  className?: string;
12
10
  loadingClassName?: string;
13
11
  errorClassName?: string;
@@ -18,5 +16,5 @@ type StripePlanSelectorProps = {
18
16
  onSelect: () => void;
19
17
  }) => ReactNode;
20
18
  };
21
- export declare function StripePlanSelector({ planCatalog, runtimeMode, selectedPlanId, onSelectPlan, onPlansLoaded, className, loadingClassName, errorClassName, renderPlan, }: StripePlanSelectorProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function StripePlanSelector({ plans, selectedPlanId, onSelectPlan, loading, error, className, loadingClassName, errorClassName, renderPlan, }: StripePlanSelectorProps): import("react/jsx-runtime").JSX.Element;
22
20
  export {};
@@ -1,74 +1,33 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useMemo, useState } from 'react';
4
- import { getDefaultPlanId, getPaywallPlans, getFallbackPlans, } from '../../services/stripe.service';
5
- import { isPreviewFrameRuntime } from '@funnelsgrove/runtime';
6
- export function StripePlanSelector({ planCatalog, runtimeMode, selectedPlanId, onSelectPlan, onPlansLoaded, className, loadingClassName, errorClassName, renderPlan, }) {
7
- const [plans, setPlans] = useState(() => getFallbackPlans(planCatalog));
8
- const [loading, setLoading] = useState(true);
9
- const [error, setError] = useState(null);
3
+ import { useEffect, useMemo } from 'react';
4
+ import { findPaywallPlan, getDefaultPlanSelectionValue, } from '../../services/stripe.service.js';
5
+ export function StripePlanSelector({ plans, selectedPlanId, onSelectPlan, loading = false, error = null, className, loadingClassName, errorClassName, renderPlan, }) {
6
+ const safePlans = useMemo(() => {
7
+ return Array.isArray(plans) ? plans : [];
8
+ }, [plans]);
10
9
  useEffect(() => {
11
- let isActive = true;
12
- const load = async () => {
13
- setLoading(true);
14
- setError(null);
15
- if (isPreviewFrameRuntime()) {
16
- const fallbackPlans = getFallbackPlans(planCatalog);
17
- setPlans(fallbackPlans);
18
- onPlansLoaded === null || onPlansLoaded === void 0 ? void 0 : onPlansLoaded(fallbackPlans);
19
- setLoading(false);
20
- return;
21
- }
22
- try {
23
- const nextPlans = await getPaywallPlans(runtimeMode, planCatalog);
24
- if (!isActive) {
25
- return;
26
- }
27
- setPlans(nextPlans);
28
- onPlansLoaded === null || onPlansLoaded === void 0 ? void 0 : onPlansLoaded(nextPlans);
29
- }
30
- catch (nextError) {
31
- if (!isActive) {
32
- return;
33
- }
34
- const fallbackPlans = getFallbackPlans(planCatalog);
35
- setPlans(fallbackPlans);
36
- onPlansLoaded === null || onPlansLoaded === void 0 ? void 0 : onPlansLoaded(fallbackPlans);
37
- setError(nextError instanceof Error ? nextError.message : 'Unable to load plans');
38
- }
39
- finally {
40
- if (isActive) {
41
- setLoading(false);
42
- }
43
- }
44
- };
45
- void load();
46
- return () => {
47
- isActive = false;
48
- };
49
- }, [onPlansLoaded, planCatalog, runtimeMode]);
50
- useEffect(() => {
51
- if (plans.length === 0) {
10
+ if (safePlans.length === 0) {
52
11
  return;
53
12
  }
54
- if (selectedPlanId && plans.some((plan) => plan.id === selectedPlanId)) {
13
+ if (findPaywallPlan(safePlans, selectedPlanId)) {
55
14
  return;
56
15
  }
57
- const defaultPlanId = getDefaultPlanId(plans);
58
- if (defaultPlanId) {
59
- onSelectPlan(defaultPlanId);
16
+ const defaultPlanSelectionValue = getDefaultPlanSelectionValue(safePlans);
17
+ if (defaultPlanSelectionValue) {
18
+ onSelectPlan(defaultPlanSelectionValue);
60
19
  }
61
- }, [onSelectPlan, plans, selectedPlanId]);
20
+ }, [onSelectPlan, safePlans, selectedPlanId]);
62
21
  const renderedPlans = useMemo(() => {
63
- return plans.map((plan, index) => {
64
- const isSelected = selectedPlanId === plan.id;
22
+ return safePlans.map((plan, index) => {
23
+ const isSelected = findPaywallPlan([plan], selectedPlanId) !== null;
65
24
  return renderPlan({
66
25
  plan,
67
26
  index,
68
27
  isSelected,
69
- onSelect: () => onSelectPlan(plan.id),
28
+ onSelect: () => { var _a; return onSelectPlan(((_a = plan.providerPlanId) === null || _a === void 0 ? void 0 : _a.trim()) || plan.id); },
70
29
  });
71
30
  });
72
- }, [onSelectPlan, plans, renderPlan, selectedPlanId]);
31
+ }, [onSelectPlan, renderPlan, safePlans, selectedPlanId]);
73
32
  return (_jsxs(_Fragment, { children: [loading ? _jsx("p", { className: loadingClassName, children: "Loading plans..." }) : null, error ? _jsx("p", { className: errorClassName, children: error }) : null, _jsx("div", { className: className, children: renderedPlans })] }));
74
33
  }
@@ -0,0 +1,11 @@
1
+ export type WalletPlatform = 'android' | 'ios' | 'web';
2
+ export type PlatformWalletPaymentMethod = 'applePay' | 'googlePay';
3
+ type WalletPlatformInput = {
4
+ maxTouchPoints?: number;
5
+ userAgent?: string | null;
6
+ };
7
+ export declare function resolveWalletPlatform({ maxTouchPoints, userAgent, }: WalletPlatformInput): WalletPlatform;
8
+ export declare function getPlatformWalletPaymentMethods(platform: WalletPlatform): readonly PlatformWalletPaymentMethod[];
9
+ export declare function getStripeWalletPaymentMethodOrder(walletPaymentMethods: readonly PlatformWalletPaymentMethod[]): string[];
10
+ export declare function usePlatformWalletPaymentMethods(): readonly PlatformWalletPaymentMethod[];
11
+ export {};
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+ import { useSyncExternalStore } from 'react';
3
+ export function resolveWalletPlatform({ maxTouchPoints = 0, userAgent, }) {
4
+ var _a;
5
+ const normalizedUserAgent = (_a = userAgent === null || userAgent === void 0 ? void 0 : userAgent.toLowerCase()) !== null && _a !== void 0 ? _a : '';
6
+ if (normalizedUserAgent.includes('android')) {
7
+ return 'android';
8
+ }
9
+ if (/iphone|ipad|ipod/.test(normalizedUserAgent) ||
10
+ (normalizedUserAgent.includes('macintosh') && maxTouchPoints > 1)) {
11
+ return 'ios';
12
+ }
13
+ return 'web';
14
+ }
15
+ export function getPlatformWalletPaymentMethods(platform) {
16
+ if (platform === 'android') {
17
+ return ['googlePay'];
18
+ }
19
+ if (platform === 'ios') {
20
+ return ['applePay'];
21
+ }
22
+ return ['applePay', 'googlePay'];
23
+ }
24
+ export function getStripeWalletPaymentMethodOrder(walletPaymentMethods) {
25
+ return walletPaymentMethods.map((method) => method === 'applePay' ? 'apple_pay' : 'google_pay');
26
+ }
27
+ function getCurrentWalletPlatform() {
28
+ if (typeof window === 'undefined') {
29
+ return 'web';
30
+ }
31
+ return resolveWalletPlatform({
32
+ maxTouchPoints: window.navigator.maxTouchPoints,
33
+ userAgent: window.navigator.userAgent,
34
+ });
35
+ }
36
+ export function usePlatformWalletPaymentMethods() {
37
+ const walletPlatform = useSyncExternalStore(() => () => undefined, getCurrentWalletPlatform, () => 'web');
38
+ return getPlatformWalletPaymentMethods(walletPlatform);
39
+ }
@@ -4,25 +4,62 @@ export type BillingPlanComparison = {
4
4
  perMonth: string;
5
5
  perYear: string;
6
6
  };
7
- export type BillingFallbackPlanConfig = {
8
- id: string;
7
+ export type BillingPlanInterval = 'day' | 'week' | 'month' | 'year';
8
+ export type BillingPlanConfig = {
9
+ projectPlanId: string;
9
10
  title: string;
10
11
  description?: string;
11
12
  providerPlanId?: string;
12
13
  priceLabel: string;
13
- oldPriceLabel?: string;
14
14
  featuredTag?: string;
15
15
  perDayAmount: string;
16
- perDayLabel: string;
17
16
  amountCents: number;
17
+ checkoutSummaryLabel?: string;
18
+ };
19
+ export type BillingFallbackPlanConfig = BillingPlanConfig & {
20
+ id?: string;
21
+ oldPriceLabel?: string;
22
+ perDayLabel?: string;
18
23
  comparison?: BillingPlanComparison;
19
24
  checkoutOriginalAmountCents?: number;
20
- checkoutSummaryLabel?: string;
25
+ billingInterval?: BillingPlanInterval;
26
+ billingIntervalCount?: number;
27
+ isDefault?: boolean;
21
28
  };
22
29
  export type BillingPlanCatalog = Record<string, BillingFallbackPlanConfig>;
30
+ export type BillingPlanMappingConfig = {
31
+ projectPlanId: string;
32
+ };
33
+ export type BillingPlanMappingCatalog = Record<string, BillingPlanMappingConfig>;
34
+ export type BillingPlanInputCatalog = BillingPlanCatalog | BillingPlanMappingCatalog;
35
+ export type BillingFirstDiscountConfig = {
36
+ stage: 'first';
37
+ couponId: string;
38
+ discountPercent: number;
39
+ durationSeconds: number;
40
+ previousDiscountPercent?: null;
41
+ };
42
+ export type BillingSecondDiscountConfig = {
43
+ stage: 'second';
44
+ couponId: string;
45
+ discountPercent: number;
46
+ durationSeconds: number;
47
+ previousDiscountPercent: number;
48
+ };
49
+ export type BillingDiscountCatalog = {
50
+ first: BillingFirstDiscountConfig;
51
+ second: BillingSecondDiscountConfig;
52
+ };
53
+ export type BillingDiscountConfig = BillingFirstDiscountConfig | BillingSecondDiscountConfig;
54
+ export type BillingDiscountList = readonly BillingDiscountConfig[];
55
+ export type BillingConfig = {
56
+ plans: BillingPlanCatalog;
57
+ discounts?: BillingDiscountCatalog | BillingDiscountList;
58
+ };
23
59
  export declare const billing: {
24
60
  readonly plans: {
25
- firstPlan: {
61
+ primary: {
62
+ projectPlanId: string;
26
63
  id: string;
27
64
  title: string;
28
65
  providerPlanId: string;
@@ -31,14 +68,17 @@ export declare const billing: {
31
68
  perDayAmount: string;
32
69
  perDayLabel: string;
33
70
  amountCents: number;
71
+ billingInterval: "month";
34
72
  comparison: {
35
73
  perDay: string;
36
74
  perWeek: string;
37
75
  perMonth: string;
38
76
  perYear: string;
39
77
  };
78
+ isDefault: true;
40
79
  };
41
- secondPlan: {
80
+ secondary: {
81
+ projectPlanId: string;
42
82
  id: string;
43
83
  title: string;
44
84
  providerPlanId: string;
@@ -47,6 +87,7 @@ export declare const billing: {
47
87
  perDayAmount: string;
48
88
  perDayLabel: string;
49
89
  amountCents: number;
90
+ billingInterval: "year";
50
91
  comparison: {
51
92
  perDay: string;
52
93
  perWeek: string;
@@ -57,7 +98,8 @@ export declare const billing: {
57
98
  };
58
99
  };
59
100
  export declare const billingPlans: {
60
- firstPlan: {
101
+ primary: {
102
+ projectPlanId: string;
61
103
  id: string;
62
104
  title: string;
63
105
  providerPlanId: string;
@@ -66,14 +108,17 @@ export declare const billingPlans: {
66
108
  perDayAmount: string;
67
109
  perDayLabel: string;
68
110
  amountCents: number;
111
+ billingInterval: "month";
69
112
  comparison: {
70
113
  perDay: string;
71
114
  perWeek: string;
72
115
  perMonth: string;
73
116
  perYear: string;
74
117
  };
118
+ isDefault: true;
75
119
  };
76
- secondPlan: {
120
+ secondary: {
121
+ projectPlanId: string;
77
122
  id: string;
78
123
  title: string;
79
124
  providerPlanId: string;
@@ -82,6 +127,7 @@ export declare const billingPlans: {
82
127
  perDayAmount: string;
83
128
  perDayLabel: string;
84
129
  amountCents: number;
130
+ billingInterval: "year";
85
131
  comparison: {
86
132
  perDay: string;
87
133
  perWeek: string;
@@ -91,6 +137,7 @@ export declare const billingPlans: {
91
137
  };
92
138
  };
93
139
  export declare const billingPlanList: ({
140
+ projectPlanId: string;
94
141
  id: string;
95
142
  title: string;
96
143
  providerPlanId: string;
@@ -99,13 +146,16 @@ export declare const billingPlanList: ({
99
146
  perDayAmount: string;
100
147
  perDayLabel: string;
101
148
  amountCents: number;
149
+ billingInterval: "month";
102
150
  comparison: {
103
151
  perDay: string;
104
152
  perWeek: string;
105
153
  perMonth: string;
106
154
  perYear: string;
107
155
  };
156
+ isDefault: true;
108
157
  } | {
158
+ projectPlanId: string;
109
159
  id: string;
110
160
  title: string;
111
161
  providerPlanId: string;
@@ -114,6 +164,7 @@ export declare const billingPlanList: ({
114
164
  perDayAmount: string;
115
165
  perDayLabel: string;
116
166
  amountCents: number;
167
+ billingInterval: "year";
117
168
  comparison: {
118
169
  perDay: string;
119
170
  perWeek: string;
@@ -1,6 +1,7 @@
1
1
  export const billing = {
2
2
  plans: {
3
- firstPlan: {
3
+ primary: {
4
+ projectPlanId: 'price_monthly',
4
5
  id: 'monthly',
5
6
  title: '1-Month Plan',
6
7
  providerPlanId: 'price_monthly',
@@ -9,14 +10,17 @@ export const billing = {
9
10
  perDayAmount: '$0.99',
10
11
  perDayLabel: 'per day',
11
12
  amountCents: 2995,
13
+ billingInterval: 'month',
12
14
  comparison: {
13
15
  perDay: '$0.99/day',
14
16
  perWeek: '$6.99/week',
15
17
  perMonth: '$29.95/month',
16
18
  perYear: '$363.34/year',
17
19
  },
20
+ isDefault: true,
18
21
  },
19
- secondPlan: {
22
+ secondary: {
23
+ projectPlanId: 'price_yearly',
20
24
  id: 'yearly',
21
25
  title: '1-Year Plan',
22
26
  providerPlanId: 'price_yearly',
@@ -25,6 +29,7 @@ export const billing = {
25
29
  perDayAmount: '$0.53',
26
30
  perDayLabel: 'per day',
27
31
  amountCents: 19140,
32
+ billingInterval: 'year',
28
33
  comparison: {
29
34
  perDay: '$0.53/day',
30
35
  perWeek: '$3.67/week',
@@ -0,0 +1,15 @@
1
+ import { type RuntimeMode } from '@funnelsgrove/runtime';
2
+ import type { BillingFallbackPlanConfig, BillingPlanInputCatalog } from '../config/billing.config.js';
3
+ import { type PaywallPlan, type PaywallPlanResolutionOptions } from '../services/stripe.service.js';
4
+ type UseResolvedPaywallPlansInput = {
5
+ runtimeMode: RuntimeMode;
6
+ planCatalog?: BillingPlanInputCatalog | readonly BillingFallbackPlanConfig[];
7
+ resolutionOptions?: PaywallPlanResolutionOptions;
8
+ previewPlans?: readonly PaywallPlan[] | null;
9
+ };
10
+ export declare const useResolvedPaywallPlans: ({ runtimeMode, planCatalog, resolutionOptions, previewPlans, }: UseResolvedPaywallPlansInput) => {
11
+ plans: readonly PaywallPlan[];
12
+ loading: boolean;
13
+ error: string | null;
14
+ };
15
+ export {};
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { isPreviewFrameRuntime } from '@funnelsgrove/runtime';
4
+ import { getFallbackPlans, getPaywallPlans, } from '../services/stripe.service.js';
5
+ import { isBillingPlanMappingCatalog } from '../services/planCatalog.service.js';
6
+ export const useResolvedPaywallPlans = ({ runtimeMode, planCatalog, resolutionOptions, previewPlans, }) => {
7
+ const fallbackPlans = useMemo(() => getFallbackPlans(planCatalog, resolutionOptions), [planCatalog, resolutionOptions]);
8
+ const isMappingCatalog = Boolean(planCatalog) && !Array.isArray(planCatalog) && isBillingPlanMappingCatalog(planCatalog);
9
+ const shouldLoadRemotePlans = !planCatalog || isMappingCatalog;
10
+ const [plans, setPlans] = useState(() => (previewPlans === null || previewPlans === void 0 ? void 0 : previewPlans.length) ? previewPlans : fallbackPlans);
11
+ const [loading, setLoading] = useState(shouldLoadRemotePlans);
12
+ const [error, setError] = useState(null);
13
+ useEffect(() => {
14
+ if (previewPlans === null || previewPlans === void 0 ? void 0 : previewPlans.length) {
15
+ setPlans(previewPlans);
16
+ setLoading(false);
17
+ setError(null);
18
+ return;
19
+ }
20
+ if (!shouldLoadRemotePlans || (isPreviewFrameRuntime() && !isMappingCatalog)) {
21
+ setPlans(fallbackPlans);
22
+ setLoading(false);
23
+ setError(null);
24
+ return;
25
+ }
26
+ let isActive = true;
27
+ const load = async () => {
28
+ setLoading(true);
29
+ setError(null);
30
+ try {
31
+ const nextPlans = await getPaywallPlans(runtimeMode, planCatalog, resolutionOptions);
32
+ if (!isActive) {
33
+ return;
34
+ }
35
+ setPlans(nextPlans);
36
+ }
37
+ catch (nextError) {
38
+ if (!isActive) {
39
+ return;
40
+ }
41
+ setPlans(fallbackPlans);
42
+ setError(nextError instanceof Error ? nextError.message : 'Unable to load plans');
43
+ }
44
+ finally {
45
+ if (isActive) {
46
+ setLoading(false);
47
+ }
48
+ }
49
+ };
50
+ void load();
51
+ return () => {
52
+ isActive = false;
53
+ };
54
+ }, [
55
+ fallbackPlans,
56
+ isMappingCatalog,
57
+ planCatalog,
58
+ previewPlans,
59
+ resolutionOptions,
60
+ runtimeMode,
61
+ shouldLoadRemotePlans,
62
+ ]);
63
+ return {
64
+ plans,
65
+ loading,
66
+ error,
67
+ };
68
+ };