@funnelsgrove/runtime 0.7.10 → 0.7.12
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
2
2
|
export type GeneratedOfferSetItemKind = 'plan' | 'upsell' | 'downsell';
|
|
3
3
|
export type GeneratedOfferSetPlanModeConfig = {
|
|
4
|
+
readonly provider?: 'stripe' | 'solidgate' | null;
|
|
4
5
|
readonly providerPlanId?: string | null;
|
|
5
6
|
readonly paymentMode?: string | null;
|
|
6
7
|
readonly priceLabel?: string | null;
|
|
@@ -19,6 +20,8 @@ export type GeneratedOfferSetPlanItem = {
|
|
|
19
20
|
readonly funnelPlanKey: string;
|
|
20
21
|
readonly runtimePlanKey?: string | null;
|
|
21
22
|
readonly projectBillingPlanId?: string | null;
|
|
23
|
+
readonly testProvider?: 'stripe' | 'solidgate' | null;
|
|
24
|
+
readonly liveProvider?: 'stripe' | 'solidgate' | null;
|
|
22
25
|
readonly testProviderPlanId?: string | null;
|
|
23
26
|
readonly liveProviderPlanId?: string | null;
|
|
24
27
|
readonly followUpTestProviderPlanId?: string | null;
|
|
@@ -57,6 +60,7 @@ export type GeneratedOfferSetDiscount = {
|
|
|
57
60
|
export type GeneratedOfferSet = {
|
|
58
61
|
readonly id?: string | null;
|
|
59
62
|
readonly key: string;
|
|
63
|
+
readonly paymentProfileId?: string | null;
|
|
60
64
|
readonly isActive?: boolean | null;
|
|
61
65
|
readonly metadata?: Record<string, unknown> | null;
|
|
62
66
|
readonly items: readonly GeneratedOfferSetPlanItem[];
|
|
@@ -109,6 +113,7 @@ export type OfferSetRuntimeInput<TPlan extends Record<string, unknown>> = {
|
|
|
109
113
|
};
|
|
110
114
|
export type OfferSetRuntimeResult<TPlan extends Record<string, unknown>> = {
|
|
111
115
|
offerSet: GeneratedOfferSet | null;
|
|
116
|
+
paymentProvidersByMode: Record<RuntimeMode, 'stripe' | 'solidgate'>;
|
|
112
117
|
plansByMode: Record<RuntimeMode, Record<string, TPlan & OfferSetRuntimePlanOverrides>>;
|
|
113
118
|
planKeys: string[];
|
|
114
119
|
upsellPlanKeys: string[];
|
|
@@ -116,6 +121,7 @@ export type OfferSetRuntimeResult<TPlan extends Record<string, unknown>> = {
|
|
|
116
121
|
defaultPlanKey: string;
|
|
117
122
|
discountListsByMode: Record<RuntimeMode, readonly OfferSetRuntimeDiscount[]>;
|
|
118
123
|
getPlanCatalog: (mode: RuntimeMode) => Record<string, TPlan & OfferSetRuntimePlanOverrides>;
|
|
124
|
+
getPaymentProvider: (mode: RuntimeMode) => 'stripe' | 'solidgate';
|
|
119
125
|
getDiscountList: (mode: RuntimeMode) => readonly OfferSetRuntimeDiscount[];
|
|
120
126
|
};
|
|
121
127
|
export declare const findGeneratedOfferSet: (offerSets: readonly GeneratedOfferSet[], offerSetKey?: string, offerSetId?: string) => GeneratedOfferSet | null;
|
|
@@ -99,6 +99,22 @@ const readProviderPlanId = (item, mode) => {
|
|
|
99
99
|
? readString(item.testProviderPlanId)
|
|
100
100
|
: readString(item.liveProviderPlanId)));
|
|
101
101
|
};
|
|
102
|
+
const readPaymentProvider = (item, mode) => {
|
|
103
|
+
var _a;
|
|
104
|
+
const modeConfig = readGeneratedModeConfig(item, mode);
|
|
105
|
+
const value = (_a = readString(modeConfig.provider)) !== null && _a !== void 0 ? _a : (mode === 'test' ? readString(item.testProvider) : readString(item.liveProvider));
|
|
106
|
+
return value === 'stripe' || value === 'solidgate' ? value : null;
|
|
107
|
+
};
|
|
108
|
+
const resolveOfferSetPaymentProvider = (offerSet, mode) => {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
const providers = new Set(((_a = offerSet === null || offerSet === void 0 ? void 0 : offerSet.items) !== null && _a !== void 0 ? _a : [])
|
|
111
|
+
.map((item) => readPaymentProvider(item, mode))
|
|
112
|
+
.filter((provider) => Boolean(provider)));
|
|
113
|
+
if (providers.size > 1) {
|
|
114
|
+
throw new Error(`Offer set "${(offerSet === null || offerSet === void 0 ? void 0 : offerSet.key) || 'unknown'}" mixes payment providers in ${mode} mode`);
|
|
115
|
+
}
|
|
116
|
+
return (_b = providers.values().next().value) !== null && _b !== void 0 ? _b : 'stripe';
|
|
117
|
+
};
|
|
102
118
|
const readFollowUpProviderPlanId = (item, mode) => {
|
|
103
119
|
var _a, _b, _c;
|
|
104
120
|
const modeConfig = readGeneratedModeConfig(item, mode);
|
|
@@ -279,12 +295,17 @@ export const resolveGeneratedOfferSetRuntime = (input) => {
|
|
|
279
295
|
test: applyGeneratedOfferSetToPlanCatalog(baseCatalogsByMode.test, 'test', offerSet),
|
|
280
296
|
live: applyGeneratedOfferSetToPlanCatalog(baseCatalogsByMode.live, 'live', offerSet),
|
|
281
297
|
};
|
|
298
|
+
const paymentProvidersByMode = {
|
|
299
|
+
test: resolveOfferSetPaymentProvider(offerSet, 'test'),
|
|
300
|
+
live: resolveOfferSetPaymentProvider(offerSet, 'live'),
|
|
301
|
+
};
|
|
282
302
|
const discountListsByMode = {
|
|
283
|
-
test: buildGeneratedOfferSetDiscountList(offerSet, 'test', input.fallbackDiscounts),
|
|
284
|
-
live: buildGeneratedOfferSetDiscountList(offerSet, 'live', input.fallbackDiscounts),
|
|
303
|
+
test: buildGeneratedOfferSetDiscountList(offerSet, 'test', paymentProvidersByMode.test === 'stripe' ? input.fallbackDiscounts : undefined),
|
|
304
|
+
live: buildGeneratedOfferSetDiscountList(offerSet, 'live', paymentProvidersByMode.live === 'stripe' ? input.fallbackDiscounts : undefined),
|
|
285
305
|
};
|
|
286
306
|
return {
|
|
287
307
|
offerSet,
|
|
308
|
+
paymentProvidersByMode,
|
|
288
309
|
plansByMode,
|
|
289
310
|
planKeys,
|
|
290
311
|
upsellPlanKeys,
|
|
@@ -292,6 +313,7 @@ export const resolveGeneratedOfferSetRuntime = (input) => {
|
|
|
292
313
|
defaultPlanKey,
|
|
293
314
|
discountListsByMode,
|
|
294
315
|
getPlanCatalog: (mode) => plansByMode[mode],
|
|
316
|
+
getPaymentProvider: (mode) => paymentProvidersByMode[mode],
|
|
295
317
|
getDiscountList: (mode) => discountListsByMode[mode],
|
|
296
318
|
};
|
|
297
319
|
};
|
|
@@ -270,7 +270,7 @@ export function resolveRenderedExperimentStepId(input) {
|
|
|
270
270
|
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
271
271
|
}
|
|
272
272
|
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
273
|
-
var _a, _b;
|
|
273
|
+
var _a, _b, _c;
|
|
274
274
|
const [runtimeMode] = useRuntimeMode();
|
|
275
275
|
const [editorModeEnabled, setEditorModeEnabled] = useState(() => isEditorEnabled());
|
|
276
276
|
const [editorPanelExpanded, setEditorPanelExpanded] = useState(false);
|
|
@@ -278,7 +278,6 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
278
278
|
const [editorVariantOverrides, setEditorVariantOverrides] = useState({});
|
|
279
279
|
const shouldLockToInitialStep = lockToInitialStep || isPreviewStepLockRequested();
|
|
280
280
|
const isPreviewRuntime = useMemo(() => isPreviewFrameRuntime(), []);
|
|
281
|
-
const lifecycleEventsSuppressed = isPreviewRuntime || editorModeEnabled;
|
|
282
281
|
const resolveRenderableStepId = useCallback((stepId) => {
|
|
283
282
|
if (!stepId || !isFunnelStepId(stepId)) {
|
|
284
283
|
return null;
|
|
@@ -308,10 +307,14 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
308
307
|
});
|
|
309
308
|
return resolveFallbackStepId(requestedStepId);
|
|
310
309
|
}, [initialStepId, resolveFallbackStepId, resolveRuntimeInitialStepId]);
|
|
310
|
+
const isSubscriptionManagementRuntime = ((_a = stepById[safeInitialStepId]) === null || _a === void 0 ? void 0 : _a.kind) === 'manage-subscription';
|
|
311
|
+
const lifecycleEventsSuppressed = isPreviewRuntime || editorModeEnabled || isSubscriptionManagementRuntime;
|
|
311
312
|
const [activeStepId, setActiveStepId] = useState(safeInitialStepId);
|
|
312
313
|
const [attributes, setAttributes] = useState(() => (Object.assign({}, (initialAttributes || {}))));
|
|
313
314
|
const [user, setUser] = useState(() => ({
|
|
314
|
-
id:
|
|
315
|
+
id: isSubscriptionManagementRuntime
|
|
316
|
+
? resolveUrlUserAttributes().userId || ''
|
|
317
|
+
: api.getOrCreateClientUserId(),
|
|
315
318
|
name: '',
|
|
316
319
|
email: '',
|
|
317
320
|
attributes: {},
|
|
@@ -503,8 +506,16 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
503
506
|
safeInitialStepId,
|
|
504
507
|
]);
|
|
505
508
|
useEffect(() => {
|
|
506
|
-
const localUserId = api.getOrCreateClientUserId();
|
|
507
509
|
const urlUserAttributes = resolveUrlUserAttributes();
|
|
510
|
+
if (isSubscriptionManagementRuntime) {
|
|
511
|
+
initialAttributionRef.current = collectCurrentFunnelAttribution();
|
|
512
|
+
setUser((prev) => (Object.assign(Object.assign({}, prev), { id: urlUserAttributes.userId || '' })));
|
|
513
|
+
setUserBootstrapped(true);
|
|
514
|
+
setAttributionReady(true);
|
|
515
|
+
setPostHogReady(true);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const localUserId = api.getOrCreateClientUserId();
|
|
508
519
|
const bootstrapCandidateUserId = api.getBootstrapCandidateUserId(localUserId);
|
|
509
520
|
const initialAttribution = collectCurrentFunnelAttribution();
|
|
510
521
|
initialAttributionRef.current = initialAttribution;
|
|
@@ -578,7 +589,7 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
578
589
|
logger.error('Failed to identify PostHog user:', error);
|
|
579
590
|
});
|
|
580
591
|
return cancelAttributionFailOpen;
|
|
581
|
-
}, [api, isPreviewRuntime]);
|
|
592
|
+
}, [api, isPreviewRuntime, isSubscriptionManagementRuntime]);
|
|
582
593
|
const recordStepCompletion = useCallback((intent, options = {}) => {
|
|
583
594
|
var _a;
|
|
584
595
|
const meta = stepById[intent.visit.stepId];
|
|
@@ -1198,10 +1209,10 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
1198
1209
|
safeInitialStepId,
|
|
1199
1210
|
]);
|
|
1200
1211
|
const choiceTargets = getChoiceTargetsForStep(renderedStepId);
|
|
1201
|
-
const actionBar = (
|
|
1212
|
+
const actionBar = (_b = renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.actionBar) !== null && _b !== void 0 ? _b : activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.actionBar;
|
|
1202
1213
|
const isAutoAdvanceStep = typeof (actionBar === null || actionBar === void 0 ? void 0 : actionBar.autoAdvanceMs) === 'number';
|
|
1203
1214
|
const autoAdvanceMs = actionBar === null || actionBar === void 0 ? void 0 : actionBar.autoAdvanceMs;
|
|
1204
|
-
const buttonVariant = (
|
|
1215
|
+
const buttonVariant = (_c = actionBar === null || actionBar === void 0 ? void 0 : actionBar.buttonVariant) !== null && _c !== void 0 ? _c : null;
|
|
1205
1216
|
const buttonText = (actionBar === null || actionBar === void 0 ? void 0 : actionBar.buttonText) || 'Continue';
|
|
1206
1217
|
const hideActionBar = (actionBar === null || actionBar === void 0 ? void 0 : actionBar.hidden) === true;
|
|
1207
1218
|
useEffect(() => {
|
|
@@ -512,9 +512,6 @@ class ApiService {
|
|
|
512
512
|
if (!payload.user) {
|
|
513
513
|
throw new Error(SUBSCRIPTION_MANAGEMENT_LINK_ERROR);
|
|
514
514
|
}
|
|
515
|
-
if (userId) {
|
|
516
|
-
persistUserId(payload.user.user_id || userId, FUNNEL_ID);
|
|
517
|
-
}
|
|
518
515
|
return payload;
|
|
519
516
|
}
|
|
520
517
|
async updateSubscription(input) {
|
|
@@ -3,6 +3,17 @@ const isRecord = (value) => (value !== null && typeof value === 'object' && !Arr
|
|
|
3
3
|
const readString = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
4
4
|
const readNumber = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
5
5
|
const readBoolean = (value) => (typeof value === 'boolean' ? value : null);
|
|
6
|
+
const readPaymentProvider = (value) => (value === 'stripe' || value === 'solidgate' ? value : null);
|
|
7
|
+
const parsePaymentProvider = (value, context) => {
|
|
8
|
+
if (value === undefined || value === null || value === '') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const provider = readPaymentProvider(value);
|
|
12
|
+
if (!provider) {
|
|
13
|
+
throw new Error(`${context} has an unsupported payment provider`);
|
|
14
|
+
}
|
|
15
|
+
return provider;
|
|
16
|
+
};
|
|
6
17
|
const readRecordArray = (value) => (Array.isArray(value) ? value.filter(isRecord) : []);
|
|
7
18
|
const pick = (record, ...keys) => {
|
|
8
19
|
for (const key of keys) {
|
|
@@ -26,6 +37,7 @@ const deepFreeze = (value) => {
|
|
|
26
37
|
return value;
|
|
27
38
|
};
|
|
28
39
|
const buildPlanModeConfig = (item, mode) => {
|
|
40
|
+
var _a;
|
|
29
41
|
const metadata = isRecord(item.metadata) ? item.metadata : {};
|
|
30
42
|
const directMappings = isRecord(item.providerMappingsByMode)
|
|
31
43
|
? item.providerMappingsByMode
|
|
@@ -39,6 +51,7 @@ const buildPlanModeConfig = (item, mode) => {
|
|
|
39
51
|
? metadataMappings[mode]
|
|
40
52
|
: {};
|
|
41
53
|
const prefix = mode === 'test' ? 'test' : 'live';
|
|
54
|
+
const provider = (_a = parsePaymentProvider(pick(item, `${prefix}Provider`, `${prefix}_provider`), `Published runtime ${mode} plan mapping`)) !== null && _a !== void 0 ? _a : parsePaymentProvider(pick(modeMapping, 'provider'), `Published runtime ${mode} plan mapping`);
|
|
42
55
|
const providerPlanId = (pickString(item, `${prefix}ProviderPlanId`, `${prefix}_provider_plan_id`)
|
|
43
56
|
|| pickString(modeMapping, 'providerPlanId'));
|
|
44
57
|
if (!providerPlanId) {
|
|
@@ -57,7 +70,7 @@ const buildPlanModeConfig = (item, mode) => {
|
|
|
57
70
|
minimumFractionDigits: 2,
|
|
58
71
|
maximumFractionDigits: 2,
|
|
59
72
|
}).format(amountMajor) + (interval ? `/${interval}` : ''));
|
|
60
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ providerPlanId }, (priceLabel ? { priceLabel } : {})), (pickString(modeMapping, 'perDayAmount')
|
|
73
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (provider ? { provider } : {})), { providerPlanId }), (priceLabel ? { priceLabel } : {})), (pickString(modeMapping, 'perDayAmount')
|
|
61
74
|
? { perDayAmount: pickString(modeMapping, 'perDayAmount') }
|
|
62
75
|
: {})), (pickString(modeMapping, 'perDayLabel')
|
|
63
76
|
? { perDayLabel: pickString(modeMapping, 'perDayLabel') }
|
|
@@ -87,15 +100,17 @@ const normalizePublishedOfferSets = (config) => {
|
|
|
87
100
|
throw new Error(`Published runtime offer set "${key}" has an item without a plan key`);
|
|
88
101
|
}
|
|
89
102
|
const projectBillingPlanId = pickString(item, 'projectBillingPlanId', 'project_billing_plan_id');
|
|
103
|
+
const testProvider = parsePaymentProvider(pick(item, 'testProvider', 'test_provider'), `Published runtime offer set "${key}"`);
|
|
104
|
+
const liveProvider = parsePaymentProvider(pick(item, 'liveProvider', 'live_provider'), `Published runtime offer set "${key}"`);
|
|
90
105
|
const testMapping = buildPlanModeConfig(item, 'test');
|
|
91
106
|
const liveMapping = buildPlanModeConfig(item, 'live');
|
|
92
107
|
const title = pickString(item, 'title', 'displayNameOverride', 'display_name_override')
|
|
93
108
|
|| pickString(metadata, 'title', 'publicPlanName', 'public_plan_name');
|
|
94
109
|
const description = pickString(item, 'description')
|
|
95
110
|
|| pickString(metadata, 'description');
|
|
96
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ funnelPlanKey }, (pickString(metadata, 'runtimePlanKey', 'planKey', 'billingPlanKey')
|
|
111
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ funnelPlanKey }, (pickString(metadata, 'runtimePlanKey', 'planKey', 'billingPlanKey')
|
|
97
112
|
? { runtimePlanKey: pickString(metadata, 'runtimePlanKey', 'planKey', 'billingPlanKey') }
|
|
98
|
-
: {})), (projectBillingPlanId ? { projectBillingPlanId } : {})), (pickString(item, 'testProviderPlanId', 'test_provider_plan_id')
|
|
113
|
+
: {})), (projectBillingPlanId ? { projectBillingPlanId } : {})), (testProvider ? { testProvider } : {})), (liveProvider ? { liveProvider } : {})), (pickString(item, 'testProviderPlanId', 'test_provider_plan_id')
|
|
99
114
|
? { testProviderPlanId: pickString(item, 'testProviderPlanId', 'test_provider_plan_id') }
|
|
100
115
|
: {})), (pickString(item, 'liveProviderPlanId', 'live_provider_plan_id')
|
|
101
116
|
? { liveProviderPlanId: pickString(item, 'liveProviderPlanId', 'live_provider_plan_id') }
|
|
@@ -119,7 +134,9 @@ const normalizePublishedOfferSets = (config) => {
|
|
|
119
134
|
? { oldPriceLabel: pickString(item, 'oldPriceLabel', 'old_price_label') }
|
|
120
135
|
: {})), { isDefault: (_a = pickBoolean(item, 'isDefault', 'is_default')) !== null && _a !== void 0 ? _a : false, providerMappingsByMode: Object.assign(Object.assign({}, (testMapping ? { test: testMapping } : {})), (liveMapping ? { live: liveMapping } : {})), metadata });
|
|
121
136
|
});
|
|
122
|
-
return Object.assign(Object.assign({}, (pickString(offerSet, 'id') ? { id: pickString(offerSet, 'id') } : {})), { key,
|
|
137
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, (pickString(offerSet, 'id') ? { id: pickString(offerSet, 'id') } : {})), { key }), (pickString(offerSet, 'paymentProfileId', 'payment_profile_id')
|
|
138
|
+
? { paymentProfileId: pickString(offerSet, 'paymentProfileId', 'payment_profile_id') }
|
|
139
|
+
: {})), { isActive: (_a = pickBoolean(offerSet, 'isActive', 'is_active')) !== null && _a !== void 0 ? _a : true, metadata: isRecord(offerSet.metadata) ? offerSet.metadata : {}, items });
|
|
123
140
|
});
|
|
124
141
|
};
|
|
125
142
|
const normalizePublishedExperiments = (config, publishedAt, offerSets) => readRecordArray(config.experiments).map((experiment) => {
|