@funnelsgrove/cli 0.1.67 → 0.1.71
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/README.md +6 -3
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +150 -4
- package/dist/localSync.d.ts +37 -0
- package/dist/localSync.js +130 -0
- package/dist/sourceRebase.d.ts +2 -0
- package/dist/sourceRebase.js +66 -0
- package/funnel-contract-compatibility.json +46 -46
- package/package.json +4 -4
- package/template_docs/.funnelsgrove-docs.json +9 -9
- package/template_docs/docs/funnelsgrove/contracts/analytics-events.md +2 -1
- package/template_docs/docs/funnelsgrove/contracts/payments.md +6 -1
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/docs/funnelsgrove/qa/analytics.md +4 -0
- package/template_docs/docs/funnelsgrove/qa/paywall-checkout.md +3 -0
- package/template_docs/docs/funnelsgrove/steps/paywall_offer.md +4 -2
- package/template_docs/docs/funnelsgrove/steps/upsell_offer.md +3 -2
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +9 -9
- package/template_scaffold/.funnelsgrove-scaffold.json +16 -16
- package/template_scaffold/docs/funnelsgrove/contracts/analytics-events.md +2 -1
- package/template_scaffold/docs/funnelsgrove/contracts/payments.md +6 -1
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/docs/funnelsgrove/qa/analytics.md +4 -0
- package/template_scaffold/docs/funnelsgrove/qa/paywall-checkout.md +3 -0
- package/template_scaffold/docs/funnelsgrove/steps/paywall_offer.md +4 -2
- package/template_scaffold/docs/funnelsgrove/steps/upsell_offer.md +3 -2
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/package-lock.json +15 -15
- package/template_scaffold/package.json +3 -3
- package/template_scaffold/src/steps/step-32-paywall.tsx +89 -106
- package/template_scaffold/tests/funnel-agent-docs.test.ts +1 -1
- package/template_scaffold/tests/src/steps/step-32-paywall.test.ts +37 -51
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sourceTreeHash": "
|
|
3
|
+
"sourceTreeHash": "961a24b0df402b27d513fe5a386494e619bf73a49d248a41e53cca2a47d770ff",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
|
-
"docsBundleVersion": "2.0.
|
|
5
|
+
"docsBundleVersion": "2.0.60",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": ".env.example",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"path": ".funnelsgrove-docs.json",
|
|
19
|
-
"sha256": "
|
|
19
|
+
"sha256": "2c4d14855705a91586a404eabddeec0c19a3625f9bc9082362deb62b4a41eae6",
|
|
20
20
|
"mode": "100644"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"path": "docs/funnelsgrove/contracts/analytics-events.md",
|
|
79
|
-
"sha256": "
|
|
79
|
+
"sha256": "4324a89f55f38b148c13ad26cbc65a3efb45b8bd034180881b31b3093622e949",
|
|
80
80
|
"mode": "100644"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
"path": "docs/funnelsgrove/contracts/payments.md",
|
|
94
|
-
"sha256": "
|
|
94
|
+
"sha256": "3e91737d0d9aef3f9e7842d87b5a4e6a922fc68a3f88da7ff4a09538f5f3e36d",
|
|
95
95
|
"mode": "100644"
|
|
96
96
|
},
|
|
97
97
|
{
|
|
@@ -101,12 +101,12 @@
|
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
104
|
-
"sha256": "
|
|
104
|
+
"sha256": "cf02bed62505b7b00ce5f6175eaa2fde99159556b37aad0f304ceba407041173",
|
|
105
105
|
"mode": "100644"
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
109
|
-
"sha256": "
|
|
109
|
+
"sha256": "53d528d318a76b74f57aaeabe73049f2c693b0ab5b3bc4277805ca4fd779b9a8",
|
|
110
110
|
"mode": "100644"
|
|
111
111
|
},
|
|
112
112
|
{
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
"path": "docs/funnelsgrove/qa/paywall-checkout.md",
|
|
119
|
-
"sha256": "
|
|
119
|
+
"sha256": "982f18d6e150606dcabe947cea95b654b643b8200f873a56fe7fe43321359b83",
|
|
120
120
|
"mode": "100644"
|
|
121
121
|
},
|
|
122
122
|
{
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
"path": "docs/funnelsgrove/steps/paywall_offer.md",
|
|
174
|
-
"sha256": "
|
|
174
|
+
"sha256": "0fe7260fa4c22f10cb218193bbb870da5f48753d964b8a9250ecc79158dd5462",
|
|
175
175
|
"mode": "100644"
|
|
176
176
|
},
|
|
177
177
|
{
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
"path": "docs/funnelsgrove/steps/upsell_offer.md",
|
|
219
|
-
"sha256": "
|
|
219
|
+
"sha256": "ba549a9355e9171193c765cc8744f68ae150c279bd9ffa08ef0f6fda0b71847b",
|
|
220
220
|
"mode": "100644"
|
|
221
221
|
},
|
|
222
222
|
{
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
"path": "funnel-docs.config.json",
|
|
239
|
-
"sha256": "
|
|
239
|
+
"sha256": "2cd26fc471e315234da4daca6d5f54bf33107d9c7671e1db1a4ba229bc5c2ba0",
|
|
240
240
|
"mode": "100644"
|
|
241
241
|
},
|
|
242
242
|
{
|
|
@@ -261,12 +261,12 @@
|
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
"path": "package-lock.json",
|
|
264
|
-
"sha256": "
|
|
264
|
+
"sha256": "fb4f9a6bdfbb1f699004b728578b1c24fdd4ee18d618c6814e162688faf711c3",
|
|
265
265
|
"mode": "100644"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"path": "package.json",
|
|
269
|
-
"sha256": "
|
|
269
|
+
"sha256": "c07d07c88f42e81903cd8a8d8877c25ae79a8fc48eb1eb50e50cf185b33f30c6",
|
|
270
270
|
"mode": "100644"
|
|
271
271
|
},
|
|
272
272
|
{
|
|
@@ -841,7 +841,7 @@
|
|
|
841
841
|
},
|
|
842
842
|
{
|
|
843
843
|
"path": "src/steps/step-32-paywall.tsx",
|
|
844
|
-
"sha256": "
|
|
844
|
+
"sha256": "85f2c0774389458546de28cf70ccfe6d00abb16ddf07259abc1bce8e62befcd4",
|
|
845
845
|
"mode": "100644"
|
|
846
846
|
},
|
|
847
847
|
{
|
|
@@ -916,7 +916,7 @@
|
|
|
916
916
|
},
|
|
917
917
|
{
|
|
918
918
|
"path": "tests/funnel-agent-docs.test.ts",
|
|
919
|
-
"sha256": "
|
|
919
|
+
"sha256": "5d577c81b48e910a3a257df838518721432d4fd1b99f5a34d7ab8943ae1517fd",
|
|
920
920
|
"mode": "100644"
|
|
921
921
|
},
|
|
922
922
|
{
|
|
@@ -1031,7 +1031,7 @@
|
|
|
1031
1031
|
},
|
|
1032
1032
|
{
|
|
1033
1033
|
"path": "tests/src/steps/step-32-paywall.test.ts",
|
|
1034
|
-
"sha256": "
|
|
1034
|
+
"sha256": "d41faefa0098207ac50e0f5fdf82d15544d029fd5854341a8d83ebc9d7a6971f",
|
|
1035
1035
|
"mode": "100644"
|
|
1036
1036
|
},
|
|
1037
1037
|
{
|
|
@@ -34,11 +34,12 @@ The `purchase_completed` event is owned by `trusted-server` and is recorded only
|
|
|
34
34
|
- The flow controller emits lifecycle events. Step components do not duplicate them.
|
|
35
35
|
- `step_engaged` is non-automatic and absent by default; only an explicit, centralized, tested policy may emit it through the approved controller/helper boundary.
|
|
36
36
|
- `submitEmailCapture` creates `email_captured` transactionally, then routes only after success.
|
|
37
|
-
-
|
|
37
|
+
- Shared payments components emit checkout semantic events at real payment boundaries when given `checkoutAnalytics`: dialog/wallet intent owns `checkout_started`, actual submit owns `add_payment_info`, and verified success owns `checkout_completed`. Funnel components do not call those SDK methods directly.
|
|
38
38
|
- The terminal helper owns funnel completion.
|
|
39
39
|
- A trusted backend owns `purchase_completed` after verifying an initial subscription or one-time payment; browser code never emits it. This purchase event is independent from the paywall-reach definition of Quiz completed.
|
|
40
40
|
- A separate trusted server claim owns `registration_completed` when the purchased subscription is later linked in the app.
|
|
41
41
|
- Provider adapters and the durable outbox own PostHog and server Meta delivery.
|
|
42
|
+
- Stripe return URLs use `trackStripePaymentCheckoutReturn` only for deduplicated `payment_checkout_succeeded` / `payment_checkout_returned` diagnostics. Browser query parameters never prove a purchase.
|
|
42
43
|
|
|
43
44
|
## Privacy boundary
|
|
44
45
|
|
|
@@ -30,11 +30,16 @@ A paywall presents purchasable plans. A reveal, loading state, calculation, or
|
|
|
30
30
|
## Payment rules
|
|
31
31
|
|
|
32
32
|
- Resolve plans, prices, discounts, currencies, provider IDs, and payment mode from shared configuration.
|
|
33
|
-
- Use
|
|
33
|
+
- Use `SharedStripeCheckoutV2Dialog` for card checkout and one `StripeSubscriptionWalletSurface` per simultaneously visible wallet placement. Never share a `useStripeSubscriptionCheckoutSession` result between card and wallet surfaces or between two visible wallet placements.
|
|
34
|
+
- Pass `checkoutAnalytics` to every shared dialog and wallet surface. The payments package owns `checkout_started`, `add_payment_info`, and verified `checkout_completed`; do not call the corresponding `publicAnalyticsSdk` methods in funnel components.
|
|
35
|
+
- Treat `onSuccess` as the first valid place to call `completeStep` for a purchase flow. Payment-info submission, dialog open, session creation, browser return, and provider-pending state are not lifecycle completion.
|
|
36
|
+
- Use `completeStripeOneClickPayment` for one-click upsells. Do not duplicate the public one-click endpoint, Stripe loader, or `confirmPayment` inside a funnel.
|
|
34
37
|
- Reuse the same event ID when retrying the same logical conversion.
|
|
35
38
|
- Keep secrets and trusted provider calls server-side.
|
|
36
39
|
- Route post-purchase behavior through the manifest and runtime handoff helper.
|
|
37
40
|
|
|
41
|
+
On a Stripe return screen, call `trackStripePaymentCheckoutReturn` for browser diagnostics. Query parameters never prove `checkout_completed` or `purchase_completed`.
|
|
42
|
+
|
|
38
43
|
## Purchase completion separation
|
|
39
44
|
|
|
40
45
|
Quiz completed counts each unique user who reaches a paywall step. `purchase_completed` remains trusted-server-only purchase verification and does not define Quiz completed. Browser terminal reach and `funnel_completed` remain lifecycle-only and are not purchase conversions.
|
|
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
|
|
|
17
17
|
|
|
18
18
|
### Package release order
|
|
19
19
|
|
|
20
|
-
Release `@funnelsgrove/runtime` `0.7.
|
|
20
|
+
Release `@funnelsgrove/runtime` `0.7.4` first, then `@funnelsgrove/analytics` `0.1.53`, then `@funnelsgrove/payments` `0.7.2`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.71`. Publishing packages and deploying production remain separately approved operational actions.
|
|
21
21
|
<!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
|
|
22
22
|
|
|
23
23
|
## Version-last policy
|
|
@@ -25,6 +25,10 @@ Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf
|
|
|
25
25
|
|
|
26
26
|
## Journey assertions
|
|
27
27
|
|
|
28
|
+
- Shared checkout UI receives `checkoutAnalytics`; funnel paywalls do not call `trackCheckoutStarted`, `trackPaymentInfoSubmitted`, or `trackCheckoutCompleted` directly.
|
|
29
|
+
- Card and each visible wallet surface use their own Checkout Session id for semantic-event deduplication.
|
|
30
|
+
- Stripe return query parameters produce only `payment_checkout_succeeded` / `payment_checkout_returned` diagnostics through `trackStripePaymentCheckoutReturn`, never a trusted purchase.
|
|
31
|
+
|
|
28
32
|
- Funnel start occurs once per run.
|
|
29
33
|
- Each visit has one start and exactly one completion or exit.
|
|
30
34
|
- First-step view/click dedupe correctly.
|
|
@@ -48,8 +48,11 @@ _Not a choice type._
|
|
|
48
48
|
|
|
49
49
|
- Verify every plan, price, currency, discount, and eligibility rule.
|
|
50
50
|
- Test card and every enabled wallet.
|
|
51
|
+
- Confirm every visible wallet placement has its own `StripeSubscriptionWalletSurface` session and the card dialog has a separate `useStripeSubscriptionCheckoutSession`.
|
|
51
52
|
- Test validation, decline, retry, cancel, double-submit, and provider delay.
|
|
52
53
|
- Verify named checkout events, complete metadata, one event ID, and retry deduplication.
|
|
54
|
+
- Verify payment-info submission does not call `completeStep`; verified `onSuccess` does.
|
|
55
|
+
- Verify funnel code contains no direct checkout analytics calls, one-click endpoint fetch, or direct Stripe confirmation.
|
|
53
56
|
- Verify success routing and the `purchase_completed` terminal handoff.
|
|
54
57
|
|
|
55
58
|
Run `fgrove validate` after all checks.
|
|
@@ -85,22 +85,24 @@ The controller owns view/start/completion/exit lifecycle. Closing or leaving wit
|
|
|
85
85
|
|
|
86
86
|
## Required semantic conversions
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
Pass `checkoutAnalytics` into `SharedStripeCheckoutV2Dialog` and every `StripeSubscriptionWalletSurface`. Those shared components emit checkout started, payment info, and verified checkout completed with the real Checkout Session identity. Do not call the analytics SDK methods directly.
|
|
89
89
|
|
|
90
90
|
## Approved shared helpers
|
|
91
91
|
|
|
92
|
-
Use shared plan resolution,
|
|
92
|
+
Use shared plan resolution, `useStripeSubscriptionCheckoutSession` for the card dialog, `SharedStripeCheckoutV2Dialog`, and one private-session `StripeSubscriptionWalletSurface` per visible wallet placement. Call `completeStep` only from `onSuccess`.
|
|
93
93
|
|
|
94
94
|
## MUST rules
|
|
95
95
|
|
|
96
96
|
- Keep the primary paywall identity exact; variants use contract-compliant distinct identities.
|
|
97
97
|
- Present at least one purchasable plan.
|
|
98
98
|
- Read prices, discounts, provider IDs, and payment mode from shared configuration.
|
|
99
|
+
- Keep card and wallet Checkout Sessions isolated even when they show the same plan.
|
|
99
100
|
|
|
100
101
|
## NEVER rules
|
|
101
102
|
|
|
102
103
|
- Never classify a reveal/interstitial as a paywall.
|
|
103
104
|
- Never put secrets, provider IDs, amount math, or provider analytics in JSX.
|
|
105
|
+
- Never complete the step on payment-info submission or manually emit checkout semantic events.
|
|
104
106
|
|
|
105
107
|
## Golden template or fixture
|
|
106
108
|
|
|
@@ -83,11 +83,11 @@ The controller owns lifecycle completion/exit; the payment boundary owns semanti
|
|
|
83
83
|
|
|
84
84
|
## Required semantic conversions
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
For embedded checkout, pass `checkoutAnalytics` to the shared dialog/surface and let the payments package own the three checkout semantic events. For an authorized post-purchase charge, use `completeStripeOneClickPayment` so required Stripe confirmation is not reimplemented locally.
|
|
87
87
|
|
|
88
88
|
## Approved shared helpers
|
|
89
89
|
|
|
90
|
-
Use
|
|
90
|
+
Use `completeStripeOneClickPayment` or the shared Checkout Session components, plus manifest routing.
|
|
91
91
|
|
|
92
92
|
## MUST rules
|
|
93
93
|
|
|
@@ -98,6 +98,7 @@ Use named payment helpers and manifest routing.
|
|
|
98
98
|
|
|
99
99
|
- Never emit a completed checkout before provider success.
|
|
100
100
|
- Never call provider analytics directly.
|
|
101
|
+
- Never call the one-click HTTP endpoint or `loadStripe` directly from the offer component.
|
|
101
102
|
|
|
102
103
|
## Golden template or fixture
|
|
103
104
|
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"name": "funnel-template",
|
|
9
9
|
"version": "0.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
12
|
-
"@funnelsgrove/payments": "^0.7.
|
|
13
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
11
|
+
"@funnelsgrove/analytics": "^0.1.53",
|
|
12
|
+
"@funnelsgrove/payments": "^0.7.2",
|
|
13
|
+
"@funnelsgrove/runtime": "^0.7.4",
|
|
14
14
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
15
15
|
"@stripe/stripe-js": "^8.7.0",
|
|
16
16
|
"lucide-react": "^0.553.0",
|
|
@@ -891,20 +891,20 @@
|
|
|
891
891
|
}
|
|
892
892
|
},
|
|
893
893
|
"node_modules/@funnelsgrove/analytics": {
|
|
894
|
-
"version": "0.1.
|
|
895
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.
|
|
896
|
-
"integrity": "sha512-
|
|
894
|
+
"version": "0.1.53",
|
|
895
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.53.tgz",
|
|
896
|
+
"integrity": "sha512-YT/gW+ToUvbcVBSnX+4b0zOFwlWL3MI2I0WAcNjRssm+zcpGlYn5vUsRU5W6f1ZgyoxBssyKdSaV+AItIy+Hiw==",
|
|
897
897
|
"dependencies": {
|
|
898
|
-
"@funnelsgrove/runtime": "0.7.
|
|
898
|
+
"@funnelsgrove/runtime": "0.7.4"
|
|
899
899
|
}
|
|
900
900
|
},
|
|
901
901
|
"node_modules/@funnelsgrove/payments": {
|
|
902
|
-
"version": "0.7.
|
|
903
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.7.
|
|
904
|
-
"integrity": "sha512-
|
|
902
|
+
"version": "0.7.2",
|
|
903
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.7.2.tgz",
|
|
904
|
+
"integrity": "sha512-q0+b1NSvrehn4x7dOI2FMGd08zDRM2KTWXIroYFHByenkF94UV4UJbcpjwlnsEi9xJRFPhaFzQfFt4zmfMdg6A==",
|
|
905
905
|
"dependencies": {
|
|
906
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
907
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
906
|
+
"@funnelsgrove/analytics": "^0.1.52",
|
|
907
|
+
"@funnelsgrove/runtime": "^0.7.3",
|
|
908
908
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
909
909
|
"@stripe/stripe-js": "^8.7.0",
|
|
910
910
|
"react": "19.2.3",
|
|
@@ -913,9 +913,9 @@
|
|
|
913
913
|
}
|
|
914
914
|
},
|
|
915
915
|
"node_modules/@funnelsgrove/runtime": {
|
|
916
|
-
"version": "0.7.
|
|
917
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.
|
|
918
|
-
"integrity": "sha512-
|
|
916
|
+
"version": "0.7.4",
|
|
917
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.4.tgz",
|
|
918
|
+
"integrity": "sha512-82kqvsm1sG0jakKOspkn1XCt7YXnTWhjFSPU5T+661KzLIHtEumKz30hxPQ/9SyIonGAat3hFtlMbKw8XptVGA==",
|
|
919
919
|
"dependencies": {
|
|
920
920
|
"posthog-js": "^1.369.2",
|
|
921
921
|
"react": "19.2.3",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"validate:funnel": "vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
16
|
-
"@funnelsgrove/payments": "^0.7.
|
|
17
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
15
|
+
"@funnelsgrove/analytics": "^0.1.53",
|
|
16
|
+
"@funnelsgrove/payments": "^0.7.2",
|
|
17
|
+
"@funnelsgrove/runtime": "^0.7.4",
|
|
18
18
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
19
19
|
"@stripe/stripe-js": "^8.7.0",
|
|
20
20
|
"lucide-react": "^0.553.0",
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from '@/config/billing.plans';
|
|
15
15
|
import { stepPaywallContent } from '@/steps/content/step-32-paywall.content';
|
|
16
16
|
import { useClaimbeeStepContent } from '@/steps/use-claimbee-step-content';
|
|
17
|
-
import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
|
|
18
17
|
import {
|
|
19
18
|
PaymentRuntimeBoundary,
|
|
20
19
|
apiService,
|
|
@@ -31,10 +30,9 @@ import {
|
|
|
31
30
|
} from '@funnelsgrove/runtime';
|
|
32
31
|
import { getPathForStep } from '@/runtime/funnel-runtime';
|
|
33
32
|
import {
|
|
34
|
-
ApplePaySubscriptionCheckoutSlot,
|
|
35
|
-
GooglePaySubscriptionCheckoutSlot,
|
|
36
33
|
SharedCheckoutSpecialOfferDialog,
|
|
37
34
|
SharedStripeCheckoutV2Dialog,
|
|
35
|
+
StripeSubscriptionWalletSurface,
|
|
38
36
|
activateSecondPaywallDiscount,
|
|
39
37
|
advancePaywallDiscountState,
|
|
40
38
|
buildBillingDiscountCatalog,
|
|
@@ -45,12 +43,13 @@ import {
|
|
|
45
43
|
getPaywallPromoDisplayName,
|
|
46
44
|
resolvePaywallDiscountState,
|
|
47
45
|
serializePaywallDiscountState,
|
|
48
|
-
usePlatformWalletPaymentMethods,
|
|
49
46
|
useStripeSubscriptionCheckoutSession,
|
|
50
47
|
type BillingFallbackPlanConfig,
|
|
51
48
|
type BillingPlanCatalog,
|
|
52
49
|
type PaywallDiscountState,
|
|
53
50
|
type PlatformWalletPaymentMethod,
|
|
51
|
+
type StripeSubscriptionWalletSurfaceAvailability,
|
|
52
|
+
type StripeSubscriptionWalletSurfaceCheckoutInput,
|
|
54
53
|
} from '@funnelsgrove/payments';
|
|
55
54
|
import { stepSubscriptionStartedId } from '@/steps/step-33-subscription-started';
|
|
56
55
|
import { funnelManifest } from '@/config/funnel.manifest';
|
|
@@ -76,9 +75,18 @@ type StoryOption = {
|
|
|
76
75
|
backgroundColor: string;
|
|
77
76
|
};
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const PAYWALL_WALLET_SURFACE_ID = 'paywall-primary';
|
|
79
|
+
|
|
80
|
+
type PaywallWalletAvailabilityState = Partial<Record<string, boolean>>;
|
|
81
|
+
|
|
82
|
+
const paywallWalletMethodClassNames: Record<PlatformWalletPaymentMethod, string> = {
|
|
83
|
+
applePay: 'paywall-v2-apple-pay-button',
|
|
84
|
+
googlePay: 'paywall-v2-google-pay-button',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const paywallWalletMethodWrapperClassNames: Record<PlatformWalletPaymentMethod, string> = {
|
|
88
|
+
applePay: 'paywall-v2-apple-pay-shell',
|
|
89
|
+
googlePay: 'paywall-v2-google-pay-shell',
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
const claimbeeStoryBackgroundColors = ['var(--color-secondary-text)', 'var(--color-primary)', 'var(--color-secondary)'] as const;
|
|
@@ -141,7 +149,6 @@ function StepPaywallRuntime({
|
|
|
141
149
|
const publishedRuntime = useFunnelRuntimeConfig();
|
|
142
150
|
const content = useClaimbeeStepContent(stepPaywallId, stepPaywallContent);
|
|
143
151
|
const supportEmail = runtimePublicConfig.supportEmail;
|
|
144
|
-
const walletPaymentMethods = usePlatformWalletPaymentMethods();
|
|
145
152
|
const publishedOfferSetRuntime = useMemo(
|
|
146
153
|
() => publishedRuntime.config && publishedRuntime.config.offerSets.length > 0
|
|
147
154
|
? resolveGeneratedOfferSetRuntime<BillingFallbackPlanConfig>({
|
|
@@ -208,10 +215,8 @@ function StepPaywallRuntime({
|
|
|
208
215
|
const [activeStoryIndex, setActiveStoryIndex] = useState(0);
|
|
209
216
|
const [openFaqIds, setOpenFaqIds] = useState<string[]>([]);
|
|
210
217
|
const [checkoutEmail, setCheckoutEmail] = useState(user.email);
|
|
211
|
-
const [paywallWalletAvailability, setPaywallWalletAvailability] =
|
|
212
|
-
|
|
213
|
-
methods: {},
|
|
214
|
-
});
|
|
218
|
+
const [paywallWalletAvailability, setPaywallWalletAvailability] =
|
|
219
|
+
useState<PaywallWalletAvailabilityState>({});
|
|
215
220
|
const paywallStepRef = useRef<HTMLDivElement | null>(null);
|
|
216
221
|
|
|
217
222
|
const activeCouponId = discountState.couponId;
|
|
@@ -363,21 +368,40 @@ function StepPaywallRuntime({
|
|
|
363
368
|
runtimeConfig: checkoutRuntimeConfig,
|
|
364
369
|
userId: user.id,
|
|
365
370
|
});
|
|
371
|
+
const walletCheckoutInput = useMemo<StripeSubscriptionWalletSurfaceCheckoutInput>(
|
|
372
|
+
() => ({
|
|
373
|
+
analyticsMetadata: checkoutAnalyticsMetadata,
|
|
374
|
+
checkoutMode,
|
|
375
|
+
couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null,
|
|
376
|
+
customerEmail: checkoutEmail || user.email,
|
|
377
|
+
customerName: user.name,
|
|
378
|
+
displayPlan: selectedDisplayPlan,
|
|
379
|
+
plan: selectedPlan,
|
|
380
|
+
returnUrl: successReturnUrl,
|
|
381
|
+
runtimeConfig: checkoutRuntimeConfig,
|
|
382
|
+
userId: user.id,
|
|
383
|
+
}),
|
|
384
|
+
[
|
|
385
|
+
checkoutAnalyticsMetadata,
|
|
386
|
+
checkoutEmail,
|
|
387
|
+
checkoutMode,
|
|
388
|
+
checkoutPromoActive,
|
|
389
|
+
selectedDisplayPlan,
|
|
390
|
+
selectedPlan,
|
|
391
|
+
successReturnUrl,
|
|
392
|
+
user.email,
|
|
393
|
+
user.id,
|
|
394
|
+
user.name,
|
|
395
|
+
],
|
|
396
|
+
);
|
|
366
397
|
const checkoutDisabled = !checkoutSession.configured || !selectedPlan || !selectedDisplayPlan;
|
|
367
398
|
const checkoutDialogReady =
|
|
368
399
|
checkoutDialogOpen &&
|
|
369
400
|
Boolean(checkoutSession.activeClientSecret) &&
|
|
370
401
|
Boolean(checkoutSession.stripePromise) &&
|
|
371
402
|
Boolean(selectedDisplayPlan);
|
|
372
|
-
const
|
|
373
|
-
paywallWalletAvailability
|
|
374
|
-
? paywallWalletAvailability.methods
|
|
375
|
-
: {};
|
|
376
|
-
const checkoutInitialWalletAvailable = walletPaymentMethods.some(
|
|
377
|
-
(method) => currentPaywallWalletAvailability[method] === true,
|
|
378
|
-
)
|
|
379
|
-
? true
|
|
380
|
-
: null;
|
|
403
|
+
const checkoutInitialWalletAvailable =
|
|
404
|
+
paywallWalletAvailability[PAYWALL_WALLET_SURFACE_ID] ?? null;
|
|
381
405
|
const checkoutTotalValue = useMemo(() => {
|
|
382
406
|
if (!selectedDisplayPlan) {
|
|
383
407
|
return '';
|
|
@@ -468,31 +492,22 @@ function StepPaywallRuntime({
|
|
|
468
492
|
);
|
|
469
493
|
}, [discountStateReady, serializedDiscountSnapshot]);
|
|
470
494
|
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
495
|
+
const handleWalletSurfaceAvailabilityChange = (
|
|
496
|
+
surfaceId: string,
|
|
497
|
+
availability: StripeSubscriptionWalletSurfaceAvailability,
|
|
474
498
|
) => {
|
|
475
|
-
setPaywallWalletAvailability((current) =>
|
|
476
|
-
|
|
477
|
-
current
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return {
|
|
482
|
-
intentKey: checkoutSession.intentKey,
|
|
483
|
-
methods: {
|
|
484
|
-
...currentMethods,
|
|
485
|
-
[method]: available,
|
|
486
|
-
},
|
|
487
|
-
};
|
|
488
|
-
});
|
|
499
|
+
setPaywallWalletAvailability((current) =>
|
|
500
|
+
current[surfaceId] === availability.anyAvailable
|
|
501
|
+
? current
|
|
502
|
+
: { ...current, [surfaceId]: availability.anyAvailable },
|
|
503
|
+
);
|
|
489
504
|
};
|
|
490
505
|
|
|
491
|
-
const
|
|
492
|
-
|
|
506
|
+
const resolvePaywallWalletMethodClassName = (method: PlatformWalletPaymentMethod) =>
|
|
507
|
+
paywallWalletMethodClassNames[method];
|
|
493
508
|
|
|
494
|
-
const
|
|
495
|
-
|
|
509
|
+
const resolvePaywallWalletMethodWrapperClassName = (method: PlatformWalletPaymentMethod) =>
|
|
510
|
+
paywallWalletMethodWrapperClassNames[method];
|
|
496
511
|
|
|
497
512
|
const startCheckout = async () => {
|
|
498
513
|
if (checkoutDisabled || checkoutSession.loading.card) {
|
|
@@ -510,29 +525,13 @@ function StepPaywallRuntime({
|
|
|
510
525
|
}
|
|
511
526
|
};
|
|
512
527
|
|
|
513
|
-
const
|
|
514
|
-
if (!selectedDisplayPlan
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const checkoutSessionId = checkoutSession.checkoutSessionId?.trim();
|
|
519
|
-
if (!checkoutSessionId) {
|
|
528
|
+
const handleCheckoutCompleted = () => {
|
|
529
|
+
if (!selectedDisplayPlan) {
|
|
520
530
|
return;
|
|
521
531
|
}
|
|
522
|
-
|
|
523
532
|
completeStep(analyticsStepId, {
|
|
524
533
|
planId: selectedDisplayPlan.id,
|
|
525
534
|
});
|
|
526
|
-
publicAnalyticsSdk.trackPaymentInfoSubmitted({
|
|
527
|
-
eventId: `add_payment_info:${checkoutSessionId}`,
|
|
528
|
-
stepId: analyticsStepId,
|
|
529
|
-
stepName: analyticsStepName,
|
|
530
|
-
stepType: stepPaywall.type,
|
|
531
|
-
stepContractVersion: funnelManifest.stepContractVersion,
|
|
532
|
-
featureFlags,
|
|
533
|
-
metadata: checkoutAnalyticsMetadata,
|
|
534
|
-
});
|
|
535
|
-
void publicAnalyticsSdk.flush().catch(() => 0);
|
|
536
535
|
};
|
|
537
536
|
|
|
538
537
|
const preparePaywallWalletCheckout = async (): Promise<string | null> => {
|
|
@@ -676,7 +675,7 @@ function StepPaywallRuntime({
|
|
|
676
675
|
onCustomerEmailChange={setCheckoutEmail}
|
|
677
676
|
onCustomerEmailCommit={commitCheckoutEmail}
|
|
678
677
|
onError={checkoutSession.setError}
|
|
679
|
-
|
|
678
|
+
onSuccess={handleCheckoutCompleted}
|
|
680
679
|
paymentMethodLabels={content.checkout.paymentMethodLabels}
|
|
681
680
|
paypalButtonLabel={content.checkout.paypalButtonLabel}
|
|
682
681
|
paypalLabel={content.checkout.paypalLabel}
|
|
@@ -884,51 +883,28 @@ function StepPaywallRuntime({
|
|
|
884
883
|
</section>
|
|
885
884
|
|
|
886
885
|
<section className='paywall-v2-payment-stack'>
|
|
887
|
-
{
|
|
888
|
-
<
|
|
889
|
-
{selectedDisplayPlan
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
) : null}
|
|
910
|
-
{walletPaymentMethods.includes('googlePay') ? (
|
|
911
|
-
<div className='paywall-v2-google-pay-shell'>
|
|
912
|
-
{selectedDisplayPlan ? (
|
|
913
|
-
<GooglePaySubscriptionCheckoutSlot
|
|
914
|
-
amountCents={selectedDisplayPlan.amountCents}
|
|
915
|
-
beforeConfirm={preparePaywallWalletCheckout}
|
|
916
|
-
checkoutAnalytics={checkoutAnalyticsContext}
|
|
917
|
-
className='paywall-v2-google-pay-button'
|
|
918
|
-
customerEmail={checkoutEmail || user.email}
|
|
919
|
-
customerName={user.name}
|
|
920
|
-
disabled={checkoutDisabled}
|
|
921
|
-
onAvailabilityChange={handleGooglePayAvailabilityChange}
|
|
922
|
-
onPaymentInfoSubmitted={trackPaymentInfoSubmitted}
|
|
923
|
-
onUnavailableClick={openCardCheckoutFromUnavailableWallet}
|
|
924
|
-
placeholderLabel={content.checkout.googlePayPlaceholderLabel}
|
|
925
|
-
returnUrl={successReturnUrl}
|
|
926
|
-
session={checkoutSession}
|
|
927
|
-
summaryLabel={checkoutSummaryLabel}
|
|
928
|
-
suspended={checkoutDialogReady || specialOfferDialogOpen}
|
|
929
|
-
/>
|
|
930
|
-
) : null}
|
|
931
|
-
</div>
|
|
886
|
+
{selectedDisplayPlan ? (
|
|
887
|
+
<StripeSubscriptionWalletSurface
|
|
888
|
+
amountCents={selectedDisplayPlan.amountCents}
|
|
889
|
+
applePayPlaceholderLabel={content.checkout.walletPlaceholderLabel}
|
|
890
|
+
beforeConfirm={preparePaywallWalletCheckout}
|
|
891
|
+
checkout={walletCheckoutInput}
|
|
892
|
+
checkoutAnalytics={checkoutAnalyticsContext}
|
|
893
|
+
containerClassName='paywall-v2-wallet-surface'
|
|
894
|
+
disabled={checkoutDisabled}
|
|
895
|
+
googlePayPlaceholderLabel={content.checkout.googlePayPlaceholderLabel}
|
|
896
|
+
onAvailabilityChange={(availability) =>
|
|
897
|
+
handleWalletSurfaceAvailabilityChange(PAYWALL_WALLET_SURFACE_ID, availability)
|
|
898
|
+
}
|
|
899
|
+
onError={setError}
|
|
900
|
+
onSuccess={handleCheckoutCompleted}
|
|
901
|
+
onUnavailableClick={openCardCheckoutFromUnavailableWallet}
|
|
902
|
+
resolveMethodClassName={resolvePaywallWalletMethodClassName}
|
|
903
|
+
resolveMethodWrapperClassName={resolvePaywallWalletMethodWrapperClassName}
|
|
904
|
+
summaryLabel={checkoutSummaryLabel}
|
|
905
|
+
surfaceId={PAYWALL_WALLET_SURFACE_ID}
|
|
906
|
+
suspended={checkoutDialogReady || specialOfferDialogOpen}
|
|
907
|
+
/>
|
|
932
908
|
) : null}
|
|
933
909
|
<button
|
|
934
910
|
type='button'
|
|
@@ -1721,6 +1697,13 @@ const stepPaywallStyles = `
|
|
|
1721
1697
|
width: 100%;
|
|
1722
1698
|
}
|
|
1723
1699
|
|
|
1700
|
+
.paywall-v2-wallet-surface {
|
|
1701
|
+
display: flex;
|
|
1702
|
+
flex-direction: column;
|
|
1703
|
+
gap: 12px;
|
|
1704
|
+
width: 100%;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1724
1707
|
.paywall-v2-card-button {
|
|
1725
1708
|
width: 100%;
|
|
1726
1709
|
min-height: 64px;
|
|
@@ -363,7 +363,7 @@ describe('funnel agent documentation supply', () => {
|
|
|
363
363
|
|
|
364
364
|
expect(manifest).toMatchObject({
|
|
365
365
|
schemaVersion: 1,
|
|
366
|
-
bundleVersion: '2.0.
|
|
366
|
+
bundleVersion: '2.0.60',
|
|
367
367
|
stepContractVersion: contract.stepContractVersion,
|
|
368
368
|
contractHash: contract.contractHash,
|
|
369
369
|
});
|