@funnelsgrove/cli 0.1.246 → 0.1.255
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/docs-release-history.json +4 -0
- package/funnel-contract-compatibility.json +28 -28
- package/legacy-docs-catalog.json +195 -0
- package/package.json +4 -4
- package/template_docs/.funnelsgrove-docs.json +3 -3
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +3 -3
- package/template_scaffold/.funnelsgrove-scaffold.json +25 -45
- package/template_scaffold/context/build-context.md +1 -1
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/funnel.runtime-capabilities.json +4 -0
- package/template_scaffold/package-lock.json +64 -15
- package/template_scaffold/package.json +5 -3
- package/template_scaffold/src/app/sdk/page.tsx +1 -1
- package/template_scaffold/src/components/FunnelFlow.tsx +2 -0
- package/template_scaffold/src/components/StripeFraudDetection.tsx +36 -0
- package/template_scaffold/src/config/billing.plans.ts +6 -3
- package/template_scaffold/src/config/billing.test.plans.ts +3 -3
- package/template_scaffold/src/steps/content/step-32-paywall.content.ts +3 -0
- package/template_scaffold/src/steps/editor/step-32-paywall.editor.ts +6 -0
- package/template_scaffold/src/steps/step-32-paywall.tsx +90 -34
- package/template_scaffold/docs/AGENT_FUNNEL_IMPLEMENTATION_SPEC.md +0 -5
- package/template_scaffold/docs/ARCHITECTURE.md +0 -5
- package/template_scaffold/docs/FLOW_CONFIG_AND_ROUTING.md +0 -5
- package/template_scaffold/docs/PAYWALL_AND_PAYMENTS.md +0 -5
- package/template_scaffold/docs/README.md +0 -5
- package/template_scaffold/docs/references/funnel-sdk-user-answers.md +0 -5
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
import { stepPaywallContent } from '@/steps/content/step-32-paywall.content';
|
|
15
15
|
import {
|
|
16
16
|
PaymentRuntimeBoundary,
|
|
17
|
+
formatProviderMoney,
|
|
18
|
+
moneyToMajor,
|
|
19
|
+
type ProviderMoney,
|
|
17
20
|
apiService,
|
|
18
21
|
collectCurrentFunnelAttribution,
|
|
19
22
|
readPaywallStateValue,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
updatePaywallStateValue,
|
|
22
25
|
useFunnel,
|
|
23
26
|
useFunnelRuntimeConfig,
|
|
27
|
+
useBrowserOfferLocale,
|
|
24
28
|
usePreviewStepLocalizedContent,
|
|
25
29
|
useRuntimeMode,
|
|
26
30
|
type FunnelStepMeta,
|
|
@@ -38,7 +42,9 @@ import {
|
|
|
38
42
|
getDefaultPlanId,
|
|
39
43
|
getPaywallPromoDisplayName,
|
|
40
44
|
resolvePaywallDiscountState,
|
|
41
|
-
|
|
45
|
+
EMPTY_PUBLISHED_BILLING_RUNTIME,
|
|
46
|
+
resolvePublishedBillingRuntimeState,
|
|
47
|
+
usePinnedPublishedBillingRuntime,
|
|
42
48
|
serializePaywallDiscountState,
|
|
43
49
|
useStripeSubscriptionCheckoutSession,
|
|
44
50
|
type PaywallDiscountState,
|
|
@@ -93,12 +99,7 @@ const deferPaywallStateSync = (syncState: () => void): (() => void) => {
|
|
|
93
99
|
const timeoutId = window.setTimeout(syncState, 0);
|
|
94
100
|
return () => window.clearTimeout(timeoutId);
|
|
95
101
|
};
|
|
96
|
-
|
|
97
|
-
style: 'currency',
|
|
98
|
-
currency: 'USD',
|
|
99
|
-
minimumFractionDigits: 2,
|
|
100
|
-
maximumFractionDigits: 2,
|
|
101
|
-
});
|
|
102
|
+
|
|
102
103
|
const inactiveBillingDiscounts = buildBillingDiscountCatalog([
|
|
103
104
|
{
|
|
104
105
|
stage: 'first',
|
|
@@ -116,8 +117,8 @@ const inactiveBillingDiscounts = buildBillingDiscountCatalog([
|
|
|
116
117
|
},
|
|
117
118
|
]);
|
|
118
119
|
|
|
119
|
-
function formatCheckoutAmount(
|
|
120
|
-
return
|
|
120
|
+
function formatCheckoutAmount(amountMinor: number, money?: ProviderMoney): string {
|
|
121
|
+
return money ? formatProviderMoney({ ...money, amountMinor }, 'en-US') : '';
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
type StepPaywallProps = {
|
|
@@ -141,6 +142,7 @@ function StepPaywallRuntime({
|
|
|
141
142
|
const checkoutMode = runtimeMode;
|
|
142
143
|
const { user, setUser, attributes, featureFlags, completeStep, userBootstrapped } = useFunnel();
|
|
143
144
|
const publishedRuntime = useFunnelRuntimeConfig();
|
|
145
|
+
const browserLocale = useBrowserOfferLocale();
|
|
144
146
|
const content = usePreviewStepLocalizedContent(
|
|
145
147
|
stepPaywallId,
|
|
146
148
|
stepPaywallContent,
|
|
@@ -148,27 +150,33 @@ function StepPaywallRuntime({
|
|
|
148
150
|
);
|
|
149
151
|
const supportEmail = runtimePublicConfig.supportEmail;
|
|
150
152
|
const paywallPreviewAsset = funnelManifest.assets.paywallPreview;
|
|
151
|
-
const
|
|
152
|
-
() =>
|
|
153
|
-
config: publishedRuntime.config
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
const billingResolution = useMemo(
|
|
154
|
+
() => resolvePublishedBillingRuntimeState({
|
|
155
|
+
config: publishedRuntime.config ?? (
|
|
156
|
+
publishedRuntime.status === 'fallback' && !publishedRuntime.error
|
|
157
|
+
? starterBillingRuntimeFallback
|
|
158
|
+
: null
|
|
159
|
+
),
|
|
160
|
+
featureFlags: publishedRuntime.featureFlags ?? featureFlags,
|
|
157
161
|
mode: checkoutMode,
|
|
158
162
|
search: typeof window === 'undefined' ? '' : window.location.search,
|
|
159
163
|
baseCatalogsByMode: starterPlansByMode,
|
|
160
164
|
fallbackDiscounts: billingDiscountList,
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
locale: browserLocale.status === 'resolved' ? browserLocale.locale : undefined,
|
|
166
|
+
}, browserLocale.status === 'resolved' && publishedRuntime.status !== 'loading'),
|
|
167
|
+
[browserLocale, checkoutMode, featureFlags, publishedRuntime.config, publishedRuntime.featureFlags, publishedRuntime.status, publishedRuntime.error],
|
|
163
168
|
);
|
|
169
|
+
const billingSelection = usePinnedPublishedBillingRuntime(billingResolution);
|
|
170
|
+
const activeBillingRuntime = billingSelection.runtime ?? EMPTY_PUBLISHED_BILLING_RUNTIME;
|
|
171
|
+
const checkoutSelectionReady = billingSelection.resolution.status === 'resolved';
|
|
164
172
|
const activeCheckoutRuntimeConfig = useMemo(() => ({
|
|
165
173
|
...checkoutRuntimeConfig,
|
|
166
|
-
funnelVersionId:
|
|
174
|
+
funnelVersionId: activeBillingRuntime.sourceVersionId,
|
|
167
175
|
paymentsApiVersion: activeBillingRuntime.paymentsApiVersion,
|
|
168
176
|
}), [
|
|
169
177
|
activeBillingRuntime.paymentsApiVersion,
|
|
170
178
|
checkoutRuntimeConfig,
|
|
171
|
-
|
|
179
|
+
activeBillingRuntime.sourceVersionId,
|
|
172
180
|
]);
|
|
173
181
|
const activeBillingDiscounts = useMemo(
|
|
174
182
|
() => activeBillingRuntime.discounts ?? (
|
|
@@ -179,6 +187,28 @@ function StepPaywallRuntime({
|
|
|
179
187
|
[activeBillingRuntime.discounts, activeBillingRuntime.provider],
|
|
180
188
|
);
|
|
181
189
|
const availablePlans = activeBillingRuntime.plans;
|
|
190
|
+
const unavailablePlanKeys = activeBillingRuntime.unavailablePlanKeys;
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (unavailablePlanKeys.length === 0) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Visitors only see the neutral note below; the author opening the funnel needs to know
|
|
197
|
+
// which published plans this payment mode cannot charge.
|
|
198
|
+
console.error('[funnelsgrove] published plans are unavailable in this payment mode', {
|
|
199
|
+
versionId: activeBillingRuntime.sourceVersionId,
|
|
200
|
+
revisionId: activeBillingRuntime.revisionId,
|
|
201
|
+
mode: activeBillingRuntime.mode,
|
|
202
|
+
offerSetId: activeBillingRuntime.offerSetId,
|
|
203
|
+
unavailablePlanKeys,
|
|
204
|
+
});
|
|
205
|
+
}, [
|
|
206
|
+
activeBillingRuntime.mode,
|
|
207
|
+
activeBillingRuntime.offerSetId,
|
|
208
|
+
activeBillingRuntime.revisionId,
|
|
209
|
+
activeBillingRuntime.sourceVersionId,
|
|
210
|
+
unavailablePlanKeys,
|
|
211
|
+
]);
|
|
182
212
|
const initialPlanId = useMemo(
|
|
183
213
|
() =>
|
|
184
214
|
availablePlans.find((plan) => plan.id === activeBillingRuntime.defaultPlanKey)?.id ??
|
|
@@ -227,8 +257,10 @@ function StepPaywallRuntime({
|
|
|
227
257
|
plans: availablePlans,
|
|
228
258
|
couponId: activeCouponId,
|
|
229
259
|
discountPercent: discountState.discountPercent,
|
|
260
|
+
amountOffMinor: discountState.amountOffMinor,
|
|
261
|
+
discountCurrency: discountState.discountCurrency,
|
|
230
262
|
}).map((plan) => localizePaywallPlanLabels(plan, content.catalogLabels)),
|
|
231
|
-
[activeCouponId, availablePlans, discountState.discountPercent, content.catalogLabels],
|
|
263
|
+
[activeCouponId, availablePlans, discountState.discountPercent, discountState.amountOffMinor, discountState.discountCurrency, content.catalogLabels],
|
|
232
264
|
);
|
|
233
265
|
const selectedPlan = useMemo(() => {
|
|
234
266
|
if (availablePlans.length === 0) {
|
|
@@ -294,11 +326,12 @@ function StepPaywallRuntime({
|
|
|
294
326
|
|
|
295
327
|
return Math.max(0, selectedDisplayPlan.baseAmountCents - selectedDisplayPlan.amountCents);
|
|
296
328
|
}, [selectedDisplayPlan]);
|
|
297
|
-
const checkoutDiscountPercent = discountState.
|
|
329
|
+
const checkoutDiscountPercent = discountState.amountOffMinor !== undefined && selectedDisplayPlan?.baseAmountCents
|
|
330
|
+
? Number((appliedDiscountAmountCents / selectedDisplayPlan.baseAmountCents * 100).toFixed(2))
|
|
331
|
+
: discountState.discountPercent ?? 0;
|
|
298
332
|
const checkoutPromoActive =
|
|
299
333
|
Boolean(selectedDisplayPlan?.hasActiveDiscount) &&
|
|
300
334
|
discountState.status === 'active' &&
|
|
301
|
-
checkoutDiscountPercent > 0 &&
|
|
302
335
|
appliedDiscountAmountCents > 0;
|
|
303
336
|
const checkoutAnalyticsMetadata = useMemo(() => {
|
|
304
337
|
if (!selectedDisplayPlan) {
|
|
@@ -311,13 +344,18 @@ function StepPaywallRuntime({
|
|
|
311
344
|
couponId: checkoutPromoActive ? selectedDisplayPlan.couponId : null,
|
|
312
345
|
summaryLabel: checkoutSummaryLabel,
|
|
313
346
|
amountCents: selectedDisplayPlan.amountCents,
|
|
314
|
-
currency:
|
|
347
|
+
...(selectedDisplayPlan.money ? { money: selectedDisplayPlan.money, currency: selectedDisplayPlan.money.currency, value: moneyToMajor(selectedDisplayPlan.money) } : {}),
|
|
315
348
|
...(typeof selectedDisplayPlan.analyticsPurchaseValue === 'number'
|
|
316
349
|
? { value: selectedDisplayPlan.analyticsPurchaseValue }
|
|
317
350
|
: {}),
|
|
318
351
|
environment: checkoutMode,
|
|
352
|
+
offerSetId: activeBillingRuntime.offerSetId,
|
|
353
|
+
runtimeConfigRevisionId: activeBillingRuntime.revisionId,
|
|
354
|
+
experimentId: activeBillingRuntime.experimentId,
|
|
355
|
+
variantKey: activeBillingRuntime.variantKey,
|
|
356
|
+
locale: activeBillingRuntime.locale,
|
|
319
357
|
};
|
|
320
|
-
}, [checkoutMode, checkoutPromoActive, checkoutSummaryLabel, selectedDisplayPlan]);
|
|
358
|
+
}, [activeBillingRuntime, checkoutMode, checkoutPromoActive, checkoutSummaryLabel, selectedDisplayPlan]);
|
|
321
359
|
const checkoutAnalyticsContext = useMemo(() => {
|
|
322
360
|
if (!checkoutAnalyticsMetadata) {
|
|
323
361
|
return null;
|
|
@@ -325,7 +363,7 @@ function StepPaywallRuntime({
|
|
|
325
363
|
|
|
326
364
|
return {
|
|
327
365
|
environment: checkoutMode,
|
|
328
|
-
featureFlags,
|
|
366
|
+
featureFlags: activeBillingRuntime.featureFlags,
|
|
329
367
|
metadata: checkoutAnalyticsMetadata,
|
|
330
368
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
331
369
|
stepId: analyticsStepId,
|
|
@@ -339,7 +377,7 @@ function StepPaywallRuntime({
|
|
|
339
377
|
analyticsStepName,
|
|
340
378
|
checkoutAnalyticsMetadata,
|
|
341
379
|
checkoutMode,
|
|
342
|
-
featureFlags,
|
|
380
|
+
activeBillingRuntime.featureFlags,
|
|
343
381
|
]);
|
|
344
382
|
const successReturnUrl = useMemo(() => {
|
|
345
383
|
if (typeof window === 'undefined') {
|
|
@@ -383,7 +421,10 @@ function StepPaywallRuntime({
|
|
|
383
421
|
couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null,
|
|
384
422
|
customerEmail: checkoutEmail || user.email,
|
|
385
423
|
displayPlan: selectedDisplayPlan,
|
|
424
|
+
enabled: checkoutSelectionReady,
|
|
386
425
|
onError: handleStripeCheckoutError,
|
|
426
|
+
onPreparationStart: billingSelection.pin,
|
|
427
|
+
onReset: billingSelection.reset,
|
|
387
428
|
plan: checkoutProvider === 'stripe' ? selectedPlan : null,
|
|
388
429
|
returnUrl: successReturnUrl,
|
|
389
430
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
@@ -404,11 +445,14 @@ function StepPaywallRuntime({
|
|
|
404
445
|
customerEmail: checkoutEmail || user.email,
|
|
405
446
|
customerName: user.name,
|
|
406
447
|
displayPlan: selectedDisplayPlan,
|
|
448
|
+
enabled: checkoutSelectionReady,
|
|
407
449
|
plan: selectedPlan,
|
|
408
450
|
returnUrl: successReturnUrl,
|
|
409
451
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
410
452
|
runtimeConfigRevisionId: activeBillingRuntime.revisionId,
|
|
411
453
|
paymentProfileId: activeBillingRuntime.paymentProfileId,
|
|
454
|
+
onPreparationStart: billingSelection.pin,
|
|
455
|
+
onReset: billingSelection.reset,
|
|
412
456
|
provider: checkoutProvider === 'stripe' ? 'stripe' : null,
|
|
413
457
|
stripePublishableKey: activeBillingRuntime.stripePublishableKey,
|
|
414
458
|
userId: user.id,
|
|
@@ -417,10 +461,13 @@ function StepPaywallRuntime({
|
|
|
417
461
|
checkoutAnalyticsMetadata,
|
|
418
462
|
checkoutEmail,
|
|
419
463
|
checkoutMode,
|
|
464
|
+
checkoutSelectionReady,
|
|
420
465
|
checkoutPromoActive,
|
|
421
466
|
activeBillingRuntime.stripePublishableKey,
|
|
422
467
|
activeBillingRuntime.paymentProfileId,
|
|
423
468
|
activeBillingRuntime.revisionId,
|
|
469
|
+
billingSelection.pin,
|
|
470
|
+
billingSelection.reset,
|
|
424
471
|
activeCheckoutRuntimeConfig,
|
|
425
472
|
checkoutProvider,
|
|
426
473
|
selectedDisplayPlan,
|
|
@@ -436,7 +483,7 @@ function StepPaywallRuntime({
|
|
|
436
483
|
&& activeBillingRuntime.offerSetId.trim()
|
|
437
484
|
&& selectedPlan?.funnelPlanKey?.trim(),
|
|
438
485
|
);
|
|
439
|
-
const checkoutDisabled = !userBootstrapped || !selectedPlan || !selectedDisplayPlan || (
|
|
486
|
+
const checkoutDisabled = !checkoutSelectionReady || !userBootstrapped || !selectedPlan || !selectedDisplayPlan || (
|
|
440
487
|
checkoutProvider === 'solidgate'
|
|
441
488
|
? !solidgateCheckoutConfigured
|
|
442
489
|
: !checkoutSession.configured
|
|
@@ -462,12 +509,12 @@ function StepPaywallRuntime({
|
|
|
462
509
|
String(checkoutDiscountPercent),
|
|
463
510
|
)
|
|
464
511
|
: '';
|
|
465
|
-
const checkoutDiscountAmountLabel = checkoutPromoActive
|
|
466
|
-
? `-${formatCheckoutAmount(appliedDiscountAmountCents)}`
|
|
512
|
+
const checkoutDiscountAmountLabel = checkoutPromoActive && selectedDisplayPlan?.money
|
|
513
|
+
? `-${formatCheckoutAmount(appliedDiscountAmountCents, selectedDisplayPlan?.money)}`
|
|
467
514
|
: '';
|
|
468
|
-
const checkoutSavedLabel = checkoutPromoActive
|
|
515
|
+
const checkoutSavedLabel = checkoutPromoActive && selectedDisplayPlan?.money
|
|
469
516
|
? content.checkout.savedLabelTemplate
|
|
470
|
-
.replace('{amount}', formatCheckoutAmount(appliedDiscountAmountCents))
|
|
517
|
+
.replace('{amount}', formatCheckoutAmount(appliedDiscountAmountCents, selectedDisplayPlan?.money))
|
|
471
518
|
.replace('{percent}', String(checkoutDiscountPercent))
|
|
472
519
|
: '';
|
|
473
520
|
useEffect(() => {
|
|
@@ -708,6 +755,8 @@ function StepPaywallRuntime({
|
|
|
708
755
|
};
|
|
709
756
|
|
|
710
757
|
const closeCheckoutDialog = () => {
|
|
758
|
+
if (checkoutProvider === 'stripe') checkoutSession.reset();
|
|
759
|
+
else billingSelection.reset();
|
|
711
760
|
setCheckoutDialogOpen(false);
|
|
712
761
|
setSolidgateAttemptKey(null);
|
|
713
762
|
setError(null);
|
|
@@ -736,7 +785,10 @@ function StepPaywallRuntime({
|
|
|
736
785
|
);
|
|
737
786
|
};
|
|
738
787
|
|
|
739
|
-
if (
|
|
788
|
+
if (billingSelection.resolution.status === 'error') {
|
|
789
|
+
return <div role='alert'>Payment options are temporarily unavailable.</div>;
|
|
790
|
+
}
|
|
791
|
+
if (!discountStateReady || billingSelection.resolution.status === 'pending') {
|
|
740
792
|
return (
|
|
741
793
|
<>
|
|
742
794
|
<div className='paywall-v2-loading-screen' aria-live='polite'>
|
|
@@ -789,7 +841,7 @@ function StepPaywallRuntime({
|
|
|
789
841
|
</div>
|
|
790
842
|
) : null}
|
|
791
843
|
|
|
792
|
-
{checkoutProvider === 'solidgate' &&
|
|
844
|
+
{checkoutSelectionReady && checkoutProvider === 'solidgate' &&
|
|
793
845
|
checkoutDialogOpen &&
|
|
794
846
|
solidgateAttemptKey &&
|
|
795
847
|
selectedPlan?.funnelPlanKey ? (
|
|
@@ -846,6 +898,7 @@ function StepPaywallRuntime({
|
|
|
846
898
|
onClose={closeCheckoutDialog}
|
|
847
899
|
onCustomerEmailCommit={commitCheckoutEmail}
|
|
848
900
|
onError={setError}
|
|
901
|
+
onPreparationStart={billingSelection.pin}
|
|
849
902
|
onSuccess={handleCheckoutCompleted}
|
|
850
903
|
/>
|
|
851
904
|
) : null}
|
|
@@ -1001,7 +1054,7 @@ function StepPaywallRuntime({
|
|
|
1001
1054
|
</span>
|
|
1002
1055
|
) : null}
|
|
1003
1056
|
<span className='paywall-v2-promo-upgrade-badge-main'>
|
|
1004
|
-
{content.checkout.discountLabelTemplate.replace('{percent}', String(
|
|
1057
|
+
{content.checkout.discountLabelTemplate.replace('{percent}', String(checkoutDiscountPercent))}
|
|
1005
1058
|
</span>
|
|
1006
1059
|
</div>
|
|
1007
1060
|
<p className='paywall-v2-promo-upgrade-label'>{content.promo.upgradedLabel}</p>
|
|
@@ -1125,6 +1178,9 @@ function StepPaywallRuntime({
|
|
|
1125
1178
|
{content.checkout.supportText}{' '}
|
|
1126
1179
|
<a href={`mailto:${supportEmail}`}>{supportEmail}</a>
|
|
1127
1180
|
</p>
|
|
1181
|
+
{availablePlans.length === 0 ? (
|
|
1182
|
+
<p className='paywall-v2-note' role='alert'>{content.plans.plansUnavailableNote}</p>
|
|
1183
|
+
) : null}
|
|
1128
1184
|
{checkoutProvider === 'stripe' && !checkoutSession.configured ? (
|
|
1129
1185
|
<p className='paywall-v2-note'>{content.plans.stripeUnavailableNote}</p>
|
|
1130
1186
|
) : null}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Compatibility: agent funnel implementation spec
|
|
2
|
-
|
|
3
|
-
The canonical agent contract moved to [FunnelsGrove start here](funnelsgrove/START-HERE.md).
|
|
4
|
-
|
|
5
|
-
This compatibility page intentionally contains no copied taxonomy or workflow rules. Update the canonical bundle instead.
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Compatibility: funnel architecture
|
|
2
|
-
|
|
3
|
-
Use the canonical [step metadata contract](funnelsgrove/contracts/step-metadata.md) for step structure and [flow routing contract](funnelsgrove/contracts/flow-routing.md) for graph behavior.
|
|
4
|
-
|
|
5
|
-
This compatibility page intentionally contains no normative contract copy.
|