@funnelsgrove/payments 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/providers/solidgate/components/SolidgateCheckoutDialog.d.ts +13 -1
- package/dist/providers/solidgate/components/SolidgateCheckoutDialog.js +54 -13
- package/dist/providers/stripe/services/stripe.service.d.ts +1 -0
- package/dist/services/publishedBillingRuntime.service.d.ts +2 -0
- package/dist/services/publishedBillingRuntime.service.js +4 -2
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FunnelSdkRuntimeConfigOverrides, FunnelStepType, RuntimeMode } from '@funnelsgrove/runtime';
|
|
1
2
|
import { type PrepareSolidgateCheckoutInput, type SolidgateCheckoutPreparation } from '../services/solidgate.service.js';
|
|
2
3
|
export type SolidgateCheckoutDialogState = 'loading' | 'ready' | 'submitting' | 'verifying' | 'success' | 'recoverable_error' | 'expired';
|
|
3
4
|
export type SolidgateCheckoutDialogContent = {
|
|
@@ -15,6 +16,7 @@ export type SolidgateCheckoutDialogContent = {
|
|
|
15
16
|
export type SolidgateCheckoutDialogProps = {
|
|
16
17
|
open: boolean;
|
|
17
18
|
request: Omit<PrepareSolidgateCheckoutInput, 'signal'>;
|
|
19
|
+
checkoutAnalytics?: SolidgateCheckoutAnalyticsContext | null;
|
|
18
20
|
content?: Partial<SolidgateCheckoutDialogContent>;
|
|
19
21
|
onClose: () => void;
|
|
20
22
|
onSuccess?: (checkout: SolidgateCheckoutPreparation) => void | Promise<void>;
|
|
@@ -22,4 +24,14 @@ export type SolidgateCheckoutDialogProps = {
|
|
|
22
24
|
onError?: (message: string) => void;
|
|
23
25
|
onStateChange?: (state: SolidgateCheckoutDialogState) => void;
|
|
24
26
|
};
|
|
25
|
-
export
|
|
27
|
+
export type SolidgateCheckoutAnalyticsContext = {
|
|
28
|
+
environment?: RuntimeMode;
|
|
29
|
+
featureFlags?: Record<string, string | null | undefined>;
|
|
30
|
+
metadata?: Record<string, unknown> | null;
|
|
31
|
+
runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
|
|
32
|
+
stepId: string;
|
|
33
|
+
stepName: string;
|
|
34
|
+
stepType: Extract<FunnelStepType, 'checkout' | 'paywall_offer' | 'upsell_offer'>;
|
|
35
|
+
stepContractVersion: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, content, onClose, onSuccess, onRetry, onError, onStateChange, }: SolidgateCheckoutDialogProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
|
|
3
4
|
import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
4
5
|
import { pollSolidgateCheckoutStatus, prepareSolidgateCheckout, } from '../services/solidgate.service.js';
|
|
5
6
|
const SolidgatePayment = lazy(async () => ({
|
|
@@ -22,6 +23,32 @@ const initialView = {
|
|
|
22
23
|
checkout: null,
|
|
23
24
|
canResumeVerification: false,
|
|
24
25
|
};
|
|
26
|
+
const trackSolidgateCheckoutEvent = async (eventName, attemptId, context) => {
|
|
27
|
+
var _a;
|
|
28
|
+
if (!context) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const input = {
|
|
32
|
+
eventId: eventName === 'checkout_started' ? attemptId : `add_payment_info:${attemptId}`,
|
|
33
|
+
featureFlags: context.featureFlags,
|
|
34
|
+
stepId: context.stepId,
|
|
35
|
+
stepName: context.stepName,
|
|
36
|
+
stepType: context.stepType,
|
|
37
|
+
stepContractVersion: context.stepContractVersion,
|
|
38
|
+
metadata: Object.assign(Object.assign({}, (context.metadata || {})), { checkoutSessionId: attemptId, checkout_session_id: attemptId, environment: context.environment || ((_a = context.metadata) === null || _a === void 0 ? void 0 : _a.environment), payment_provider: 'solidgate' }),
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const trackedEventId = eventName === 'checkout_started'
|
|
42
|
+
? publicAnalyticsSdk.trackCheckoutStarted(input)
|
|
43
|
+
: publicAnalyticsSdk.trackPaymentInfoSubmitted(input);
|
|
44
|
+
if (trackedEventId) {
|
|
45
|
+
await publicAnalyticsSdk.flush().catch(() => 0);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (_b) {
|
|
49
|
+
// Analytics must never prevent payment.
|
|
50
|
+
}
|
|
51
|
+
};
|
|
25
52
|
const isAbortError = (error) => (error instanceof DOMException && error.name === 'AbortError');
|
|
26
53
|
const safeDestroyPaymentForm = (instance) => {
|
|
27
54
|
var _a;
|
|
@@ -40,39 +67,44 @@ const safeDestroyPaymentForm = (instance) => {
|
|
|
40
67
|
};
|
|
41
68
|
function SolidgatePaymentForm({ checkout, hidden, loadingLabel, onError, onMounted, onSubmit, onVerify, }) {
|
|
42
69
|
const instanceRef = useRef(null);
|
|
70
|
+
const applePayContainerRef = useRef(null);
|
|
71
|
+
const googlePayContainerRef = useRef(null);
|
|
43
72
|
useEffect(() => () => {
|
|
44
73
|
safeDestroyPaymentForm(instanceRef.current);
|
|
45
74
|
instanceRef.current = null;
|
|
46
75
|
}, [checkout.attemptId]);
|
|
47
|
-
return (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
return (_jsxs("div", { className: hidden ? 'hidden' : 'block', "data-solidgate-attempt-id": checkout.attemptId, "aria-hidden": hidden || undefined, children: [_jsxs("div", { className: 'mb-3 grid gap-3', children: [_jsx("div", { ref: applePayContainerRef, "data-solidgate-wallet": 'apple-pay' }), _jsx("div", { ref: googlePayContainerRef, "data-solidgate-wallet": 'google-pay' })] }), _jsx(Suspense, { fallback: (_jsx("p", { role: 'status', className: 'text-sm text-muted-foreground', children: loadingLabel })), children: _jsx(SolidgatePayment, { merchantData: checkout.initialization.merchantData, width: '100%', formParams: { isSolidLogoVisible: true }, applePayContainerRef: applePayContainerRef, googlePayContainerRef: googlePayContainerRef, onReadyPaymentInstance: (instance) => {
|
|
77
|
+
if (instanceRef.current && instanceRef.current !== instance) {
|
|
78
|
+
try {
|
|
79
|
+
instanceRef.current.unsubscribeAll();
|
|
80
|
+
}
|
|
81
|
+
catch (_a) {
|
|
82
|
+
// The active instance remains usable even if stale-listener cleanup fails.
|
|
83
|
+
}
|
|
51
84
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
instanceRef.current = instance;
|
|
57
|
-
}, onMounted: onMounted, onSubmit: onSubmit, onVerify: onVerify, onSuccess: onVerify, onFail: onVerify, onError: onError }, checkout.attemptId) }) }));
|
|
85
|
+
instanceRef.current = instance;
|
|
86
|
+
}, onMounted: onMounted, onSubmit: onSubmit, onVerify: onVerify, onSuccess: onVerify, onFail: onVerify, onError: onError }, checkout.attemptId) })] }));
|
|
58
87
|
}
|
|
59
|
-
export function SolidgateCheckoutDialog({ open, request, content, onClose, onSuccess, onRetry, onError, onStateChange, }) {
|
|
88
|
+
export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, content, onClose, onSuccess, onRetry, onError, onStateChange, }) {
|
|
60
89
|
const copy = useMemo(() => (Object.assign(Object.assign({}, defaultContent), content)), [content]);
|
|
61
90
|
const [view, setView] = useState(initialView);
|
|
62
91
|
const prepareAbortRef = useRef(null);
|
|
63
92
|
const statusAbortRef = useRef(null);
|
|
64
93
|
const successAttemptRef = useRef(null);
|
|
94
|
+
const paymentInfoAttemptRef = useRef(null);
|
|
95
|
+
const checkoutAnalyticsRef = useRef(checkoutAnalytics);
|
|
65
96
|
const closeButtonRef = useRef(null);
|
|
66
97
|
const copyRef = useRef(copy);
|
|
67
98
|
const onErrorRef = useRef(onError);
|
|
68
99
|
const onStateChangeRef = useRef(onStateChange);
|
|
69
100
|
const onSuccessRef = useRef(onSuccess);
|
|
70
101
|
useEffect(() => {
|
|
102
|
+
checkoutAnalyticsRef.current = checkoutAnalytics;
|
|
71
103
|
copyRef.current = copy;
|
|
72
104
|
onErrorRef.current = onError;
|
|
73
105
|
onStateChangeRef.current = onStateChange;
|
|
74
106
|
onSuccessRef.current = onSuccess;
|
|
75
|
-
}, [copy, onError, onStateChange, onSuccess]);
|
|
107
|
+
}, [checkoutAnalytics, copy, onError, onStateChange, onSuccess]);
|
|
76
108
|
const { discountKeyOrProviderId, failUrl, funnelEndUserId, funnelId, idempotencyKey, offerSetId, projectBillingPlanId, returnUrl, runtimeConfig, successUrl, } = request;
|
|
77
109
|
const { apiBaseUrl, funnelId: runtimeFunnelId, funnelSdkPublishableKey, funnelVersionId, } = runtimeConfig || {};
|
|
78
110
|
const hasRuntimeConfig = runtimeConfig !== undefined;
|
|
@@ -125,6 +157,7 @@ export function SolidgateCheckoutDialog({ open, request, content, onClose, onSuc
|
|
|
125
157
|
(_d = statusAbortRef.current) === null || _d === void 0 ? void 0 : _d.abort();
|
|
126
158
|
prepareAbortRef.current = controller;
|
|
127
159
|
successAttemptRef.current = null;
|
|
160
|
+
paymentInfoAttemptRef.current = null;
|
|
128
161
|
queueMicrotask(() => {
|
|
129
162
|
if (!controller.signal.aborted) {
|
|
130
163
|
setState(initialView);
|
|
@@ -134,6 +167,7 @@ export function SolidgateCheckoutDialog({ open, request, content, onClose, onSuc
|
|
|
134
167
|
.then((checkout) => {
|
|
135
168
|
if (!controller.signal.aborted) {
|
|
136
169
|
setState({ state: 'ready', checkout, canResumeVerification: false });
|
|
170
|
+
void trackSolidgateCheckoutEvent('checkout_started', checkout.attemptId, checkoutAnalyticsRef.current);
|
|
137
171
|
}
|
|
138
172
|
})
|
|
139
173
|
.catch((error) => {
|
|
@@ -242,7 +276,14 @@ export function SolidgateCheckoutDialog({ open, request, content, onClose, onSuc
|
|
|
242
276
|
if (view.state === 'loading') {
|
|
243
277
|
setState(Object.assign(Object.assign({}, view), { state: 'ready' }));
|
|
244
278
|
}
|
|
245
|
-
}, onSubmit: () =>
|
|
279
|
+
}, onSubmit: () => {
|
|
280
|
+
var _a, _b, _c;
|
|
281
|
+
if (paymentInfoAttemptRef.current !== ((_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId)) {
|
|
282
|
+
paymentInfoAttemptRef.current = ((_b = view.checkout) === null || _b === void 0 ? void 0 : _b.attemptId) || null;
|
|
283
|
+
void trackSolidgateCheckoutEvent('add_payment_info', ((_c = view.checkout) === null || _c === void 0 ? void 0 : _c.attemptId) || '', checkoutAnalyticsRef.current);
|
|
284
|
+
}
|
|
285
|
+
setState(Object.assign(Object.assign({}, view), { state: 'submitting' }));
|
|
286
|
+
}, onVerify: () => void verifyCanonicalStatus(), onError: () => {
|
|
246
287
|
var _a;
|
|
247
288
|
setState(Object.assign(Object.assign({}, view), { state: 'recoverable_error', canResumeVerification: false }));
|
|
248
289
|
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, copyRef.current.error);
|
|
@@ -5,6 +5,7 @@ import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
|
5
5
|
import { type RemoteProjectBillingPlan } from '../../../services/planCatalog.service.js';
|
|
6
6
|
export type PaywallPlan = {
|
|
7
7
|
id: string;
|
|
8
|
+
projectPlanId?: string;
|
|
8
9
|
offerSetId?: string;
|
|
9
10
|
offerSetKey?: string;
|
|
10
11
|
title: string;
|
|
@@ -6,6 +6,8 @@ export type ResolvedPublishedBillingRuntime = {
|
|
|
6
6
|
revisionId: string | null;
|
|
7
7
|
mode: RuntimeMode;
|
|
8
8
|
offerSetId: string;
|
|
9
|
+
paymentProfileId: string | null;
|
|
10
|
+
provider: 'stripe' | 'solidgate';
|
|
9
11
|
experimentId: string | null;
|
|
10
12
|
variantKey: string | null;
|
|
11
13
|
experiments: readonly FunnelExperimentDefinition[];
|
|
@@ -2,14 +2,14 @@ import { resolveGeneratedOfferSetRuntime, resolvePublishedOfferSetSelection, res
|
|
|
2
2
|
import { buildBillingDiscountCatalog } from './paywallOffer.service.js';
|
|
3
3
|
import { buildConfigPaywallPlans, } from '../providers/stripe/services/stripe.service.js';
|
|
4
4
|
export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, search = '', defaultOfferSetId, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments = resolvePublishedRuntimeExperiments(config), }) => {
|
|
5
|
-
var _a, _b;
|
|
5
|
+
var _a, _b, _c, _d;
|
|
6
6
|
const offerSets = (_a = config === null || config === void 0 ? void 0 : config.offerSets) !== null && _a !== void 0 ? _a : [];
|
|
7
7
|
const selection = resolvePublishedOfferSetSelection({
|
|
8
8
|
offerSets,
|
|
9
9
|
pricingExperiments: resolvedExperiments.pricingExperiments,
|
|
10
10
|
featureFlags,
|
|
11
11
|
search,
|
|
12
|
-
defaultOfferSetId,
|
|
12
|
+
defaultOfferSetId: defaultOfferSetId || (config === null || config === void 0 ? void 0 : config.defaultOfferSetId) || undefined,
|
|
13
13
|
});
|
|
14
14
|
const runtime = resolveGeneratedOfferSetRuntime({
|
|
15
15
|
offerSets,
|
|
@@ -26,6 +26,8 @@ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, sea
|
|
|
26
26
|
revisionId: (_b = config === null || config === void 0 ? void 0 : config.revisionId) !== null && _b !== void 0 ? _b : null,
|
|
27
27
|
mode,
|
|
28
28
|
offerSetId: selection.offerSetId,
|
|
29
|
+
paymentProfileId: ((_d = (_c = runtime.offerSet) === null || _c === void 0 ? void 0 : _c.paymentProfileId) === null || _d === void 0 ? void 0 : _d.trim()) || null,
|
|
30
|
+
provider: runtime.getPaymentProvider(mode),
|
|
29
31
|
experimentId: selection.experimentId,
|
|
30
32
|
variantKey: selection.variantKey,
|
|
31
33
|
experiments: resolvedExperiments.experiments,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"test:run": "vitest run --passWithNoTests"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
36
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
35
|
+
"@funnelsgrove/analytics": "^0.1.65",
|
|
36
|
+
"@funnelsgrove/runtime": "^0.7.16",
|
|
37
37
|
"@solidgate/react-sdk": "1.34.0",
|
|
38
38
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
39
39
|
"@stripe/stripe-js": "^8.7.0",
|