@funnelsgrove/payments 0.1.52 → 0.1.53
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/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './services/checkoutCompletionAnalytics.service.js';
|
|
|
9
9
|
export * from './hooks/useResolvedPaywallPlans.js';
|
|
10
10
|
export * from './providers/paymentProvider.types.js';
|
|
11
11
|
export * from './providers/stripe/useStripeSubscriptionCheckoutSession.js';
|
|
12
|
+
export * from './providers/stripe/useStripeOneTimeCheckoutSession.js';
|
|
12
13
|
export * from './providers/stripe/WalletSubscriptionCheckoutSlot.js';
|
|
13
14
|
export * from './providers/stripe/ApplePaySubscriptionCheckoutSlot.js';
|
|
14
15
|
export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './hooks/useResolvedPaywallPlans.js';
|
|
|
12
12
|
// Provider-neutral and Stripe subscription checkout primitives.
|
|
13
13
|
export * from './providers/paymentProvider.types.js';
|
|
14
14
|
export * from './providers/stripe/useStripeSubscriptionCheckoutSession.js';
|
|
15
|
+
export * from './providers/stripe/useStripeOneTimeCheckoutSession.js';
|
|
15
16
|
export * from './providers/stripe/WalletSubscriptionCheckoutSlot.js';
|
|
16
17
|
export * from './providers/stripe/ApplePaySubscriptionCheckoutSlot.js';
|
|
17
18
|
export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Stripe } from '@stripe/stripe-js';
|
|
2
|
+
import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
3
|
+
import { type CheckoutPreparationLoading } from '../paymentProvider.types.js';
|
|
4
|
+
import { type PaywallPlan, type StripeRuntimeConfigOverrides } from '../../services/stripe.service.js';
|
|
5
|
+
type StripeOneTimeCheckoutPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'description' | 'id' | 'providerPlanId' | 'title'>;
|
|
6
|
+
export type StripeOneTimeCheckoutSessionInput = {
|
|
7
|
+
analyticsMetadata?: Record<string, unknown> | null;
|
|
8
|
+
checkoutMode: RuntimeMode;
|
|
9
|
+
couponId?: string | null;
|
|
10
|
+
customerEmail?: string | null;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
onError?: (message: string | null) => void;
|
|
13
|
+
plan?: StripeOneTimeCheckoutPlanInput | null;
|
|
14
|
+
returnUrl: string;
|
|
15
|
+
runtimeConfig?: StripeRuntimeConfigOverrides;
|
|
16
|
+
userId?: string | null;
|
|
17
|
+
};
|
|
18
|
+
export type StripeOneTimeCheckoutPreparationOptions = {
|
|
19
|
+
checkoutStartSource?: 'card_click' | 'wallet_click' | 'wallet_render' | (string & {});
|
|
20
|
+
forceNew?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type StripeOneTimeCheckoutSession = {
|
|
23
|
+
activeClientSecret: string | null;
|
|
24
|
+
checkoutSessionId: string | null;
|
|
25
|
+
configured: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
intentKey: string;
|
|
28
|
+
loading: CheckoutPreparationLoading;
|
|
29
|
+
prepareCardCheckout: (options?: StripeOneTimeCheckoutPreparationOptions) => Promise<string | null>;
|
|
30
|
+
prepareWalletCheckout: (options?: StripeOneTimeCheckoutPreparationOptions) => Promise<string | null>;
|
|
31
|
+
provider: 'stripe';
|
|
32
|
+
reset: () => void;
|
|
33
|
+
restartCardCheckout: (options?: StripeOneTimeCheckoutPreparationOptions) => Promise<string | null>;
|
|
34
|
+
restartWalletCheckout: (options?: StripeOneTimeCheckoutPreparationOptions) => Promise<string | null>;
|
|
35
|
+
planKey: string;
|
|
36
|
+
setError: (message: string | null) => void;
|
|
37
|
+
stripePromise: Promise<Stripe | null> | null;
|
|
38
|
+
};
|
|
39
|
+
export type StripeOneTimeCheckoutIntentKeyInput = Pick<StripeOneTimeCheckoutSessionInput, 'checkoutMode' | 'couponId' | 'customerEmail' | 'plan' | 'returnUrl' | 'runtimeConfig' | 'userId'> & {
|
|
40
|
+
analyticsMetadata?: Record<string, unknown> | null;
|
|
41
|
+
};
|
|
42
|
+
export declare function buildStripeOneTimeCheckoutIntentKey({ checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, userId, }: StripeOneTimeCheckoutIntentKeyInput): string;
|
|
43
|
+
export declare function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
|
|
44
|
+
export declare function useStripeOneTimeCheckoutSession({ analyticsMetadata, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { createEmptyCheckoutPreparationLoading, } from '../paymentProvider.types.js';
|
|
4
|
+
import { createStripeOneTimeCheckout, getStripePromise, isStripeConfigured, } from '../../services/stripe.service.js';
|
|
5
|
+
import { isInactiveCheckoutSessionError } from './useStripeSubscriptionCheckoutSession.js';
|
|
6
|
+
const getFriendlyStripeCheckoutError = (error) => {
|
|
7
|
+
const message = error instanceof Error ? error.message : 'Unable to initialize checkout';
|
|
8
|
+
return message === 'Internal server error'
|
|
9
|
+
? 'Unable to initialize checkout. Stripe settings may be missing for this funnel.'
|
|
10
|
+
: message;
|
|
11
|
+
};
|
|
12
|
+
const normalizeRuntimeConfigValue = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || '';
|
|
13
|
+
const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
|
|
14
|
+
const buildCheckoutPreparationAnalyticsMetadata = (analyticsMetadata, options = {}) => {
|
|
15
|
+
var _a;
|
|
16
|
+
const checkoutStartSource = (_a = options.checkoutStartSource) === null || _a === void 0 ? void 0 : _a.trim();
|
|
17
|
+
if (!checkoutStartSource) {
|
|
18
|
+
return analyticsMetadata;
|
|
19
|
+
}
|
|
20
|
+
return Object.assign(Object.assign({}, (analyticsMetadata !== null && analyticsMetadata !== void 0 ? analyticsMetadata : {})), { checkoutStartSource });
|
|
21
|
+
};
|
|
22
|
+
export function buildStripeOneTimeCheckoutIntentKey({ checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, userId, }) {
|
|
23
|
+
var _a;
|
|
24
|
+
const runtimeConfigSignature = [
|
|
25
|
+
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
|
|
26
|
+
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId),
|
|
27
|
+
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelVersionId),
|
|
28
|
+
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey),
|
|
29
|
+
].join('|');
|
|
30
|
+
const planSignature = [
|
|
31
|
+
normalizeRuntimeConfigValue(plan === null || plan === void 0 ? void 0 : plan.id),
|
|
32
|
+
normalizeRuntimeConfigValue(plan === null || plan === void 0 ? void 0 : plan.providerPlanId),
|
|
33
|
+
(_a = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _a !== void 0 ? _a : '',
|
|
34
|
+
].join('|');
|
|
35
|
+
const customerEmailSignature = normalizeRuntimeConfigValue(customerEmail);
|
|
36
|
+
const userIdSignature = normalizeRuntimeConfigValue(userId);
|
|
37
|
+
return [
|
|
38
|
+
'stripe-one-time',
|
|
39
|
+
checkoutMode,
|
|
40
|
+
runtimeConfigSignature,
|
|
41
|
+
planSignature,
|
|
42
|
+
customerEmailSignature,
|
|
43
|
+
userIdSignature,
|
|
44
|
+
(couponId === null || couponId === void 0 ? void 0 : couponId.trim()) || '',
|
|
45
|
+
returnUrl.trim(),
|
|
46
|
+
].join('|');
|
|
47
|
+
}
|
|
48
|
+
export function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey, nextIntentKey) {
|
|
49
|
+
return previousIntentKey !== null && previousIntentKey !== nextIntentKey;
|
|
50
|
+
}
|
|
51
|
+
export function useStripeOneTimeCheckoutSession({ analyticsMetadata, checkoutMode, couponId, customerEmail, enabled = true, onError, plan, returnUrl, runtimeConfig, userId, }) {
|
|
52
|
+
var _a, _b, _c;
|
|
53
|
+
const [clientSecret, setClientSecret] = useState(null);
|
|
54
|
+
const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
|
|
55
|
+
const [checkoutSessionId, setCheckoutSessionId] = useState(null);
|
|
56
|
+
const [error, setErrorState] = useState(null);
|
|
57
|
+
const [loading, setLoading] = useState(createEmptyCheckoutPreparationLoading);
|
|
58
|
+
const [runtimeStripePublishableKey, setRuntimeStripePublishableKey] = useState(null);
|
|
59
|
+
const creatingIntentRef = useRef(null);
|
|
60
|
+
const requestIdRef = useRef(0);
|
|
61
|
+
const configured = enabled &&
|
|
62
|
+
(isStripeConfigured(checkoutMode) ||
|
|
63
|
+
Boolean(normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)));
|
|
64
|
+
const intentKey = buildStripeOneTimeCheckoutIntentKey({
|
|
65
|
+
checkoutMode,
|
|
66
|
+
couponId,
|
|
67
|
+
customerEmail,
|
|
68
|
+
plan,
|
|
69
|
+
returnUrl,
|
|
70
|
+
runtimeConfig,
|
|
71
|
+
userId,
|
|
72
|
+
});
|
|
73
|
+
const planKey = [
|
|
74
|
+
(_a = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _a !== void 0 ? _a : '',
|
|
75
|
+
(_b = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _b !== void 0 ? _b : '',
|
|
76
|
+
(_c = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _c !== void 0 ? _c : '',
|
|
77
|
+
].join('|');
|
|
78
|
+
const activeClientSecret = clientSecretIntentKey === intentKey ? clientSecret : null;
|
|
79
|
+
const activeCheckoutSessionId = clientSecretIntentKey === intentKey ? checkoutSessionId : null;
|
|
80
|
+
const stripePromise = useMemo(() => getStripePromise(checkoutMode, runtimeStripePublishableKey !== null && runtimeStripePublishableKey !== void 0 ? runtimeStripePublishableKey : runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey), [checkoutMode, runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey, runtimeStripePublishableKey]);
|
|
81
|
+
const previousIntentKeyRef = useRef(intentKey);
|
|
82
|
+
const analyticsMetadataRef = useRef(analyticsMetadata);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
analyticsMetadataRef.current = analyticsMetadata;
|
|
85
|
+
}, [analyticsMetadata]);
|
|
86
|
+
const clearActiveCheckoutSession = useCallback(() => {
|
|
87
|
+
requestIdRef.current += 1;
|
|
88
|
+
creatingIntentRef.current = null;
|
|
89
|
+
setClientSecret(null);
|
|
90
|
+
setClientSecretIntentKey(null);
|
|
91
|
+
setCheckoutSessionId(null);
|
|
92
|
+
}, []);
|
|
93
|
+
const setError = useCallback((message) => {
|
|
94
|
+
if (isInactiveCheckoutSessionError(message)) {
|
|
95
|
+
clearActiveCheckoutSession();
|
|
96
|
+
setErrorState(null);
|
|
97
|
+
onError === null || onError === void 0 ? void 0 : onError(null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setErrorState(message);
|
|
101
|
+
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
102
|
+
}, [clearActiveCheckoutSession, onError]);
|
|
103
|
+
const reset = useCallback(() => {
|
|
104
|
+
clearActiveCheckoutSession();
|
|
105
|
+
setLoading(createEmptyCheckoutPreparationLoading());
|
|
106
|
+
setRuntimeStripePublishableKey(null);
|
|
107
|
+
setError(null);
|
|
108
|
+
}, [clearActiveCheckoutSession, setError]);
|
|
109
|
+
useIsomorphicLayoutEffect(() => {
|
|
110
|
+
const previousIntentKey = previousIntentKeyRef.current;
|
|
111
|
+
previousIntentKeyRef.current = intentKey;
|
|
112
|
+
if (shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey, intentKey)) {
|
|
113
|
+
reset();
|
|
114
|
+
}
|
|
115
|
+
}, [intentKey, reset]);
|
|
116
|
+
const setSourceLoading = useCallback((source, value) => {
|
|
117
|
+
setLoading((current) => current[source] === value
|
|
118
|
+
? current
|
|
119
|
+
: Object.assign(Object.assign({}, current), { [source]: value }));
|
|
120
|
+
}, []);
|
|
121
|
+
const createCheckoutSession = useCallback(async (options = {}) => {
|
|
122
|
+
var _a;
|
|
123
|
+
const forceNew = options.forceNew === true;
|
|
124
|
+
if (!configured || !plan) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (!forceNew && activeClientSecret) {
|
|
128
|
+
return activeClientSecret;
|
|
129
|
+
}
|
|
130
|
+
if (!forceNew && ((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) === intentKey) {
|
|
131
|
+
return creatingIntentRef.current.promise;
|
|
132
|
+
}
|
|
133
|
+
if (forceNew) {
|
|
134
|
+
clearActiveCheckoutSession();
|
|
135
|
+
}
|
|
136
|
+
const requestKey = intentKey;
|
|
137
|
+
const requestId = requestIdRef.current + 1;
|
|
138
|
+
requestIdRef.current = requestId;
|
|
139
|
+
const checkoutRequest = (async () => {
|
|
140
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
141
|
+
try {
|
|
142
|
+
const response = await createStripeOneTimeCheckout({
|
|
143
|
+
plan,
|
|
144
|
+
analyticsMetadata: buildCheckoutPreparationAnalyticsMetadata(analyticsMetadataRef.current, options),
|
|
145
|
+
couponId,
|
|
146
|
+
customerEmail,
|
|
147
|
+
returnUrl,
|
|
148
|
+
user_id: userId,
|
|
149
|
+
environment: checkoutMode,
|
|
150
|
+
runtimeConfig,
|
|
151
|
+
});
|
|
152
|
+
if (((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) !== requestKey ||
|
|
153
|
+
((_b = creatingIntentRef.current) === null || _b === void 0 ? void 0 : _b.id) !== requestId) {
|
|
154
|
+
return response.clientSecret;
|
|
155
|
+
}
|
|
156
|
+
const publishableKey = ((_c = response.stripePublishableKey) === null || _c === void 0 ? void 0 : _c.trim()) ||
|
|
157
|
+
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey);
|
|
158
|
+
if (publishableKey) {
|
|
159
|
+
setRuntimeStripePublishableKey(publishableKey);
|
|
160
|
+
}
|
|
161
|
+
setClientSecret(response.clientSecret);
|
|
162
|
+
setClientSecretIntentKey(requestKey);
|
|
163
|
+
setCheckoutSessionId(((_d = response.checkoutSessionId) === null || _d === void 0 ? void 0 : _d.trim()) || null);
|
|
164
|
+
return response.clientSecret;
|
|
165
|
+
}
|
|
166
|
+
catch (checkoutError) {
|
|
167
|
+
if (((_e = creatingIntentRef.current) === null || _e === void 0 ? void 0 : _e.key) === requestKey &&
|
|
168
|
+
((_f = creatingIntentRef.current) === null || _f === void 0 ? void 0 : _f.id) === requestId) {
|
|
169
|
+
setError(getFriendlyStripeCheckoutError(checkoutError));
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
if (((_g = creatingIntentRef.current) === null || _g === void 0 ? void 0 : _g.key) === requestKey &&
|
|
175
|
+
((_h = creatingIntentRef.current) === null || _h === void 0 ? void 0 : _h.id) === requestId) {
|
|
176
|
+
creatingIntentRef.current = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
creatingIntentRef.current = {
|
|
181
|
+
id: requestId,
|
|
182
|
+
key: requestKey,
|
|
183
|
+
promise: checkoutRequest,
|
|
184
|
+
};
|
|
185
|
+
return checkoutRequest;
|
|
186
|
+
}, [
|
|
187
|
+
activeClientSecret,
|
|
188
|
+
checkoutMode,
|
|
189
|
+
clearActiveCheckoutSession,
|
|
190
|
+
configured,
|
|
191
|
+
couponId,
|
|
192
|
+
customerEmail,
|
|
193
|
+
intentKey,
|
|
194
|
+
plan,
|
|
195
|
+
returnUrl,
|
|
196
|
+
runtimeConfig,
|
|
197
|
+
setError,
|
|
198
|
+
userId,
|
|
199
|
+
]);
|
|
200
|
+
const prepareCheckout = useCallback(async (source, options = {}) => {
|
|
201
|
+
var _a;
|
|
202
|
+
if (!configured || !plan) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (!options.forceNew && activeClientSecret) {
|
|
206
|
+
return activeClientSecret;
|
|
207
|
+
}
|
|
208
|
+
setSourceLoading(source, true);
|
|
209
|
+
setError(null);
|
|
210
|
+
try {
|
|
211
|
+
return await createCheckoutSession(Object.assign(Object.assign({}, options), { checkoutStartSource: (_a = options.checkoutStartSource) !== null && _a !== void 0 ? _a : (source === 'wallet' ? 'wallet_render' : 'card_click') }));
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
setSourceLoading(source, false);
|
|
215
|
+
}
|
|
216
|
+
}, [
|
|
217
|
+
activeClientSecret,
|
|
218
|
+
configured,
|
|
219
|
+
createCheckoutSession,
|
|
220
|
+
plan,
|
|
221
|
+
setError,
|
|
222
|
+
setSourceLoading,
|
|
223
|
+
]);
|
|
224
|
+
const prepareCardCheckout = useCallback((options = {}) => prepareCheckout('card', options), [prepareCheckout]);
|
|
225
|
+
const prepareWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', options), [prepareCheckout]);
|
|
226
|
+
const restartCardCheckout = useCallback((options = {}) => prepareCheckout('card', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
|
|
227
|
+
const restartWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
|
|
228
|
+
return useMemo(() => ({
|
|
229
|
+
activeClientSecret,
|
|
230
|
+
checkoutSessionId: activeCheckoutSessionId,
|
|
231
|
+
configured,
|
|
232
|
+
error,
|
|
233
|
+
intentKey,
|
|
234
|
+
loading,
|
|
235
|
+
prepareCardCheckout,
|
|
236
|
+
prepareWalletCheckout,
|
|
237
|
+
provider: 'stripe',
|
|
238
|
+
reset,
|
|
239
|
+
restartCardCheckout,
|
|
240
|
+
restartWalletCheckout,
|
|
241
|
+
planKey,
|
|
242
|
+
setError,
|
|
243
|
+
stripePromise,
|
|
244
|
+
}), [
|
|
245
|
+
activeCheckoutSessionId,
|
|
246
|
+
activeClientSecret,
|
|
247
|
+
configured,
|
|
248
|
+
error,
|
|
249
|
+
intentKey,
|
|
250
|
+
loading,
|
|
251
|
+
planKey,
|
|
252
|
+
prepareCardCheckout,
|
|
253
|
+
prepareWalletCheckout,
|
|
254
|
+
reset,
|
|
255
|
+
restartCardCheckout,
|
|
256
|
+
restartWalletCheckout,
|
|
257
|
+
setError,
|
|
258
|
+
stripePromise,
|
|
259
|
+
]);
|
|
260
|
+
}
|