@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.
Files changed (33) hide show
  1. package/README.md +6 -3
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +150 -4
  4. package/dist/localSync.d.ts +37 -0
  5. package/dist/localSync.js +130 -0
  6. package/dist/sourceRebase.d.ts +2 -0
  7. package/dist/sourceRebase.js +66 -0
  8. package/funnel-contract-compatibility.json +46 -46
  9. package/package.json +4 -4
  10. package/template_docs/.funnelsgrove-docs.json +9 -9
  11. package/template_docs/docs/funnelsgrove/contracts/analytics-events.md +2 -1
  12. package/template_docs/docs/funnelsgrove/contracts/payments.md +6 -1
  13. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  14. package/template_docs/docs/funnelsgrove/qa/analytics.md +4 -0
  15. package/template_docs/docs/funnelsgrove/qa/paywall-checkout.md +3 -0
  16. package/template_docs/docs/funnelsgrove/steps/paywall_offer.md +4 -2
  17. package/template_docs/docs/funnelsgrove/steps/upsell_offer.md +3 -2
  18. package/template_docs/funnel-docs.config.json +1 -1
  19. package/template_scaffold/.funnelsgrove-docs.json +9 -9
  20. package/template_scaffold/.funnelsgrove-scaffold.json +16 -16
  21. package/template_scaffold/docs/funnelsgrove/contracts/analytics-events.md +2 -1
  22. package/template_scaffold/docs/funnelsgrove/contracts/payments.md +6 -1
  23. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  24. package/template_scaffold/docs/funnelsgrove/qa/analytics.md +4 -0
  25. package/template_scaffold/docs/funnelsgrove/qa/paywall-checkout.md +3 -0
  26. package/template_scaffold/docs/funnelsgrove/steps/paywall_offer.md +4 -2
  27. package/template_scaffold/docs/funnelsgrove/steps/upsell_offer.md +3 -2
  28. package/template_scaffold/funnel-docs.config.json +1 -1
  29. package/template_scaffold/package-lock.json +15 -15
  30. package/template_scaffold/package.json +3 -3
  31. package/template_scaffold/src/steps/step-32-paywall.tsx +89 -106
  32. package/template_scaffold/tests/funnel-agent-docs.test.ts +1 -1
  33. 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": "da47d4b45148058b3485786ebf529164773f845391a1ca1ce896b38606651bb5",
3
+ "sourceTreeHash": "961a24b0df402b27d513fe5a386494e619bf73a49d248a41e53cca2a47d770ff",
4
4
  "stepContractVersion": 3,
5
- "docsBundleVersion": "2.0.56",
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": "8a7f62f1471af7a5a37e5e1e8123c4f9c64b09a2570ec12f5b8812eeddc145f3",
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": "496ebab7c1fd00b40f9a5b29a5d3f8b6605549bf5927eb4280ccdc3023acd3c9",
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": "3614a2c6e4d48411c9eba21400ce2699a49782d802aa08acc72335ed4ef2421d",
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": "3f89ff9d4d5501c6d3562f4d60d5d2fbc46d0069866353d0fe9a899e4690ac24",
104
+ "sha256": "cf02bed62505b7b00ce5f6175eaa2fde99159556b37aad0f304ceba407041173",
105
105
  "mode": "100644"
106
106
  },
107
107
  {
108
108
  "path": "docs/funnelsgrove/qa/analytics.md",
109
- "sha256": "1a42fcb7fccfb35f41fe2df297aadd67d6ea86fa3a9de2960c861664f32cf9ab",
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": "6fc887772e52ddd99ab70d898d8fd9b89869ca49ce1b49152d232cbe32865aae",
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": "969ee556ca1da3bfa09c21f530726467735693537452fcbf3065f21644b90669",
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": "832ffb5506ccdc72b28c839e1b574e5c9ba0c01755fa6c4be105dbe1136b21d0",
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": "44a40a7630f8ffd287390886d48461cbfb982160daaa9c77a816fbdf446df683",
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": "7b9cd26b58befd01f21ca16928bb3544be09bd7647155ab147f8a8c5764647a3",
264
+ "sha256": "fb4f9a6bdfbb1f699004b728578b1c24fdd4ee18d618c6814e162688faf711c3",
265
265
  "mode": "100644"
266
266
  },
267
267
  {
268
268
  "path": "package.json",
269
- "sha256": "5f5414ce78626c5e28d287d91ee91fc77e7a3ff2cb4d8d4c41e9f3ea443d9568",
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": "86407736b37dbaa88300ba270ea1b51b7ff27bdf2244365bbdb020e5f86004a4",
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": "4245b5e7c07fdc15a099e1c2c758b4960e7d43e90abdfb1ac52b5d423b816317",
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": "0306cbe4e10d1f7027d0ba114eb95c8b5b095dfad57472887a4b985f1482afda",
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
- - Named payment helpers emit checkout semantic events at real payment boundaries.
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 named checkout helpers at actual checkout-started, payment-info, and completed boundaries.
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.2` first, then `@funnelsgrove/analytics` `0.1.51`, then `@funnelsgrove/payments` `0.7.1`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.67`. Publishing packages and deploying production remain separately approved operational actions.
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
- Use named helpers for checkout started, payment info, and checkout completed at their real boundaries with one stable event ID.
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, checkout, wallet, and `trackCheckoutStarted`, `trackPaymentInfoSubmitted`, and `trackCheckoutCompleted` helpers.
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
- Call `trackCheckoutStarted`, `trackPaymentInfoSubmitted`, and `trackCheckoutCompleted` only at their matching real boundaries.
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 named payment helpers and manifest routing.
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.56",
3
+ "bundleVersion": "2.0.60",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -8,9 +8,9 @@
8
8
  "name": "funnel-template",
9
9
  "version": "0.1.0",
10
10
  "dependencies": {
11
- "@funnelsgrove/analytics": "^0.1.51",
12
- "@funnelsgrove/payments": "^0.7.1",
13
- "@funnelsgrove/runtime": "^0.7.2",
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.51",
895
- "resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.51.tgz",
896
- "integrity": "sha512-tDY3p8pit/cqBwKXqbzh4o13Nzuk+nQ8SpqGddvV1kFteF9I/zqXCdrc01Qw00WdDdavTaMiFw6rqcbZT3pQfQ==",
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.2"
898
+ "@funnelsgrove/runtime": "0.7.4"
899
899
  }
900
900
  },
901
901
  "node_modules/@funnelsgrove/payments": {
902
- "version": "0.7.1",
903
- "resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.7.1.tgz",
904
- "integrity": "sha512-BvJNJgs9tXgDq4ICzvvASfrIH3u5JENQEInjTWlRrngW5mSJUQjwow2BuKd1FnI1TXxsNyiDUb6cdIi0MeXRYA==",
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.28",
907
- "@funnelsgrove/runtime": "^0.7.1",
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.2",
917
- "resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.2.tgz",
918
- "integrity": "sha512-NODUpe+2tjP83isXOmGHMCHDrKW/BpsiprkB9p3P4OyQJdCuDbKFoAsPGvoN3Q7nWB/I8ybfTm2FSCizXZCD4g==",
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.51",
16
- "@funnelsgrove/payments": "^0.7.1",
17
- "@funnelsgrove/runtime": "^0.7.2",
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
- type PaywallWalletAvailabilityState = {
80
- intentKey: string;
81
- methods: Partial<Record<PlatformWalletPaymentMethod, boolean>>;
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] = useState<PaywallWalletAvailabilityState>({
212
- intentKey: '',
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 currentPaywallWalletAvailability: PaywallWalletAvailabilityState['methods'] =
373
- paywallWalletAvailability.intentKey === checkoutSession.intentKey
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 updatePaywallWalletAvailability = (
472
- method: PlatformWalletPaymentMethod,
473
- available: boolean,
495
+ const handleWalletSurfaceAvailabilityChange = (
496
+ surfaceId: string,
497
+ availability: StripeSubscriptionWalletSurfaceAvailability,
474
498
  ) => {
475
- setPaywallWalletAvailability((current) => {
476
- const currentMethods =
477
- current.intentKey === checkoutSession.intentKey
478
- ? current.methods
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 handleApplePayAvailabilityChange = (available: boolean) =>
492
- updatePaywallWalletAvailability('applePay', available);
506
+ const resolvePaywallWalletMethodClassName = (method: PlatformWalletPaymentMethod) =>
507
+ paywallWalletMethodClassNames[method];
493
508
 
494
- const handleGooglePayAvailabilityChange = (available: boolean) =>
495
- updatePaywallWalletAvailability('googlePay', available);
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 trackPaymentInfoSubmitted = () => {
514
- if (!selectedDisplayPlan || !checkoutAnalyticsMetadata) {
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
- onPaymentInfoSubmitted={trackPaymentInfoSubmitted}
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
- {walletPaymentMethods.includes('applePay') ? (
888
- <div className='paywall-v2-apple-pay-shell'>
889
- {selectedDisplayPlan ? (
890
- <ApplePaySubscriptionCheckoutSlot
891
- amountCents={selectedDisplayPlan.amountCents}
892
- beforeConfirm={preparePaywallWalletCheckout}
893
- checkoutAnalytics={checkoutAnalyticsContext}
894
- className='paywall-v2-apple-pay-button'
895
- customerEmail={checkoutEmail || user.email}
896
- customerName={user.name}
897
- disabled={checkoutDisabled}
898
- onAvailabilityChange={handleApplePayAvailabilityChange}
899
- onPaymentInfoSubmitted={trackPaymentInfoSubmitted}
900
- onUnavailableClick={openCardCheckoutFromUnavailableWallet}
901
- placeholderLabel={content.checkout.walletPlaceholderLabel}
902
- returnUrl={successReturnUrl}
903
- session={checkoutSession}
904
- summaryLabel={checkoutSummaryLabel}
905
- suspended={checkoutDialogReady || specialOfferDialogOpen}
906
- />
907
- ) : null}
908
- </div>
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.56',
366
+ bundleVersion: '2.0.60',
367
367
  stepContractVersion: contract.stepContractVersion,
368
368
  contractHash: contract.contractHash,
369
369
  });