@funnelsgrove/cli 0.1.222 → 0.1.223

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.
@@ -4,10 +4,10 @@
4
4
  "minimumCliVersion": "0.1.20",
5
5
  "entries": [
6
6
  {
7
- "repositoryCliVersion": "0.1.222",
7
+ "repositoryCliVersion": "0.1.223",
8
8
  "manifest": {
9
9
  "schemaVersion": 1,
10
- "bundleVersion": "2.0.214",
10
+ "bundleVersion": "2.0.215",
11
11
  "stepContractVersion": 3,
12
12
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
13
13
  "managedFiles": [
@@ -45,7 +45,7 @@
45
45
  },
46
46
  {
47
47
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
48
- "sha256": "a0575e85660e8b1d5b51e1527740e02c267fb4b632aa10d9f17fa83324a8ccf7"
48
+ "sha256": "ce781cf22929d6e1ff1c74c04d12ad9c2be06e1b1171e62da09ec7b277d3867f"
49
49
  },
50
50
  {
51
51
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -145,16 +145,16 @@
145
145
  },
146
146
  {
147
147
  "path": "funnel-docs.config.json",
148
- "sha256": "f7ed68bd91966f1fc77fab35315eff8f04987d4d2f841319722cd4f720e458ef"
148
+ "sha256": "2a8e04f9a0e36ea5248dbade1259f8328bbea8e67c7a7e7d33ef1ff9094809fb"
149
149
  }
150
150
  ]
151
151
  }
152
152
  },
153
153
  {
154
- "repositoryCliVersion": "0.1.221",
154
+ "repositoryCliVersion": "0.1.222",
155
155
  "manifest": {
156
156
  "schemaVersion": 1,
157
- "bundleVersion": "2.0.213",
157
+ "bundleVersion": "2.0.214",
158
158
  "stepContractVersion": 3,
159
159
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
160
160
  "managedFiles": [
@@ -192,7 +192,7 @@
192
192
  },
193
193
  {
194
194
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
195
- "sha256": "6d52d046a03cb15f2afa953368a3d866627fb4aafe86dbc5117af5771496e042"
195
+ "sha256": "a0575e85660e8b1d5b51e1527740e02c267fb4b632aa10d9f17fa83324a8ccf7"
196
196
  },
197
197
  {
198
198
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -292,16 +292,16 @@
292
292
  },
293
293
  {
294
294
  "path": "funnel-docs.config.json",
295
- "sha256": "792aaaade69fd128bcfdfd8c518949f2b3eb37daf0fcbbdb7fd668d6dcb57ed3"
295
+ "sha256": "f7ed68bd91966f1fc77fab35315eff8f04987d4d2f841319722cd4f720e458ef"
296
296
  }
297
297
  ]
298
298
  }
299
299
  },
300
300
  {
301
- "repositoryCliVersion": "0.1.220",
301
+ "repositoryCliVersion": "0.1.221",
302
302
  "manifest": {
303
303
  "schemaVersion": 1,
304
- "bundleVersion": "2.0.212",
304
+ "bundleVersion": "2.0.213",
305
305
  "stepContractVersion": 3,
306
306
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
307
307
  "managedFiles": [
@@ -339,7 +339,7 @@
339
339
  },
340
340
  {
341
341
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
342
- "sha256": "2b4c4ec4a30095449ecf0671a26ee947389afab54fcda33e3061e9204bd6975c"
342
+ "sha256": "6d52d046a03cb15f2afa953368a3d866627fb4aafe86dbc5117af5771496e042"
343
343
  },
344
344
  {
345
345
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -439,16 +439,16 @@
439
439
  },
440
440
  {
441
441
  "path": "funnel-docs.config.json",
442
- "sha256": "b70d42f119a2cecfd327e8e401411c17faa5f0287d7e061106530b6e44021bb0"
442
+ "sha256": "792aaaade69fd128bcfdfd8c518949f2b3eb37daf0fcbbdb7fd668d6dcb57ed3"
443
443
  }
444
444
  ]
445
445
  }
446
446
  },
447
447
  {
448
- "repositoryCliVersion": "0.1.219",
448
+ "repositoryCliVersion": "0.1.220",
449
449
  "manifest": {
450
450
  "schemaVersion": 1,
451
- "bundleVersion": "2.0.211",
451
+ "bundleVersion": "2.0.212",
452
452
  "stepContractVersion": 3,
453
453
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
454
454
  "managedFiles": [
@@ -486,7 +486,7 @@
486
486
  },
487
487
  {
488
488
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
489
- "sha256": "bcbadffb5cafecb2fb5cf8e0efc1ce89e50ad7831cd74fb6e44366e5af304f1a"
489
+ "sha256": "2b4c4ec4a30095449ecf0671a26ee947389afab54fcda33e3061e9204bd6975c"
490
490
  },
491
491
  {
492
492
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -586,16 +586,16 @@
586
586
  },
587
587
  {
588
588
  "path": "funnel-docs.config.json",
589
- "sha256": "a74eb797c82d67a1e2c74ee691cf0314b87082ec3000f20e17423e6f4ee60a90"
589
+ "sha256": "b70d42f119a2cecfd327e8e401411c17faa5f0287d7e061106530b6e44021bb0"
590
590
  }
591
591
  ]
592
592
  }
593
593
  },
594
594
  {
595
- "repositoryCliVersion": "0.1.218",
595
+ "repositoryCliVersion": "0.1.219",
596
596
  "manifest": {
597
597
  "schemaVersion": 1,
598
- "bundleVersion": "2.0.210",
598
+ "bundleVersion": "2.0.211",
599
599
  "stepContractVersion": 3,
600
600
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
601
601
  "managedFiles": [
@@ -633,7 +633,7 @@
633
633
  },
634
634
  {
635
635
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
636
- "sha256": "11c5cdb7ae1da4cae896c452caa410e8857e6a9982c7d9122ea6fdf4fc23d5ad"
636
+ "sha256": "bcbadffb5cafecb2fb5cf8e0efc1ce89e50ad7831cd74fb6e44366e5af304f1a"
637
637
  },
638
638
  {
639
639
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -733,16 +733,16 @@
733
733
  },
734
734
  {
735
735
  "path": "funnel-docs.config.json",
736
- "sha256": "017e44038e149219fa6125df53982b86d26d3936f857c2b35565d11e95a6115d"
736
+ "sha256": "a74eb797c82d67a1e2c74ee691cf0314b87082ec3000f20e17423e6f4ee60a90"
737
737
  }
738
738
  ]
739
739
  }
740
740
  },
741
741
  {
742
- "repositoryCliVersion": "0.1.217",
742
+ "repositoryCliVersion": "0.1.218",
743
743
  "manifest": {
744
744
  "schemaVersion": 1,
745
- "bundleVersion": "2.0.209",
745
+ "bundleVersion": "2.0.210",
746
746
  "stepContractVersion": 3,
747
747
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
748
748
  "managedFiles": [
@@ -780,7 +780,7 @@
780
780
  },
781
781
  {
782
782
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
783
- "sha256": "02b64360dc55d9612312a7939e514218c05351341a2c982d02d3b651c664cd18"
783
+ "sha256": "11c5cdb7ae1da4cae896c452caa410e8857e6a9982c7d9122ea6fdf4fc23d5ad"
784
784
  },
785
785
  {
786
786
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -880,16 +880,16 @@
880
880
  },
881
881
  {
882
882
  "path": "funnel-docs.config.json",
883
- "sha256": "99c2ea35a5ad3a71bcf6991d4c0674ee931920b775d72dc21bcabb80a515ae8e"
883
+ "sha256": "017e44038e149219fa6125df53982b86d26d3936f857c2b35565d11e95a6115d"
884
884
  }
885
885
  ]
886
886
  }
887
887
  },
888
888
  {
889
- "repositoryCliVersion": "0.1.216",
889
+ "repositoryCliVersion": "0.1.217",
890
890
  "manifest": {
891
891
  "schemaVersion": 1,
892
- "bundleVersion": "2.0.208",
892
+ "bundleVersion": "2.0.209",
893
893
  "stepContractVersion": 3,
894
894
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
895
895
  "managedFiles": [
@@ -919,7 +919,7 @@
919
919
  },
920
920
  {
921
921
  "path": "docs/funnelsgrove/contracts/payments.md",
922
- "sha256": "80572cc42bc4163ba6d992a5de642dc88ca875ef4890570dc50a940dca56df6d"
922
+ "sha256": "67b8a8bbe575019cf8708c1e03f76c6661ce96ca1dc658cafe486256945f6826"
923
923
  },
924
924
  {
925
925
  "path": "docs/funnelsgrove/contracts/step-metadata.md",
@@ -927,7 +927,7 @@
927
927
  },
928
928
  {
929
929
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
930
- "sha256": "d5ea6b1a344b082154ba26cc36a03894e3a69e462a41bdaed7ad9abb40b0a60a"
930
+ "sha256": "02b64360dc55d9612312a7939e514218c05351341a2c982d02d3b651c664cd18"
931
931
  },
932
932
  {
933
933
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -1027,7 +1027,7 @@
1027
1027
  },
1028
1028
  {
1029
1029
  "path": "funnel-docs.config.json",
1030
- "sha256": "0da7798f3b34a25c95b3efc85727647dbb4bdc818f22f4f395f0472175c67a20"
1030
+ "sha256": "99c2ea35a5ad3a71bcf6991d4c0674ee931920b775d72dc21bcabb80a515ae8e"
1031
1031
  }
1032
1032
  ]
1033
1033
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/cli",
3
- "version": "0.1.222",
3
+ "version": "0.1.223",
4
4
  "description": "FunnelsGrove command-line tools for editing, syncing, and publishing funnels",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.214",
3
+ "bundleVersion": "2.0.215",
4
4
  "stepContractVersion": 3,
5
5
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
6
6
  "managedFiles": [
@@ -38,7 +38,7 @@
38
38
  },
39
39
  {
40
40
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
41
- "sha256": "a0575e85660e8b1d5b51e1527740e02c267fb4b632aa10d9f17fa83324a8ccf7"
41
+ "sha256": "ce781cf22929d6e1ff1c74c04d12ad9c2be06e1b1171e62da09ec7b277d3867f"
42
42
  },
43
43
  {
44
44
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "path": "funnel-docs.config.json",
141
- "sha256": "f7ed68bd91966f1fc77fab35315eff8f04987d4d2f841319722cd4f720e458ef"
141
+ "sha256": "2a8e04f9a0e36ea5248dbade1259f8328bbea8e67c7a7e7d33ef1ff9094809fb"
142
142
  }
143
143
  ]
144
144
  }
@@ -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.14.0` first, then `@funnelsgrove/analytics` `0.1.100`, then `@funnelsgrove/payments` `0.15.3`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.222`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. Publishing packages and deploying production remain separately approved operational actions.
20
+ Release `@funnelsgrove/runtime` `0.14.0` first, then `@funnelsgrove/analytics` `0.1.100`, then `@funnelsgrove/payments` `0.15.3`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.223`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.214",
3
+ "bundleVersion": "2.0.215",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.214",
3
+ "bundleVersion": "2.0.215",
4
4
  "stepContractVersion": 3,
5
5
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
6
6
  "managedFiles": [
@@ -38,7 +38,7 @@
38
38
  },
39
39
  {
40
40
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
41
- "sha256": "a0575e85660e8b1d5b51e1527740e02c267fb4b632aa10d9f17fa83324a8ccf7"
41
+ "sha256": "ce781cf22929d6e1ff1c74c04d12ad9c2be06e1b1171e62da09ec7b277d3867f"
42
42
  },
43
43
  {
44
44
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "path": "funnel-docs.config.json",
141
- "sha256": "f7ed68bd91966f1fc77fab35315eff8f04987d4d2f841319722cd4f720e458ef"
141
+ "sha256": "2a8e04f9a0e36ea5248dbade1259f8328bbea8e67c7a7e7d33ef1ff9094809fb"
142
142
  }
143
143
  ]
144
144
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sourceTreeHash": "f77bc83454f96af7db47c471bdcbc2de8595415da74f7437a4031a6845ecb1b6",
3
+ "sourceTreeHash": "fb01fa3ef0d1f67a7a7e70d2d7cc717c0f2366a4ed3f547f409657a2488e7310",
4
4
  "stepContractVersion": 3,
5
- "docsBundleVersion": "2.0.214",
5
+ "docsBundleVersion": "2.0.215",
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": "44f5ec072a1245f479208b885a155d52516423f5921d27cf28d6adf1979fa3db",
19
+ "sha256": "0db2e36b42be59f683accb268c9801e13859454047611fb964979bc57a3fbc42",
20
20
  "mode": "100644"
21
21
  },
22
22
  {
@@ -101,7 +101,7 @@
101
101
  },
102
102
  {
103
103
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
104
- "sha256": "a0575e85660e8b1d5b51e1527740e02c267fb4b632aa10d9f17fa83324a8ccf7",
104
+ "sha256": "ce781cf22929d6e1ff1c74c04d12ad9c2be06e1b1171e62da09ec7b277d3867f",
105
105
  "mode": "100644"
106
106
  },
107
107
  {
@@ -236,7 +236,7 @@
236
236
  },
237
237
  {
238
238
  "path": "funnel-docs.config.json",
239
- "sha256": "f7ed68bd91966f1fc77fab35315eff8f04987d4d2f841319722cd4f720e458ef",
239
+ "sha256": "2a8e04f9a0e36ea5248dbade1259f8328bbea8e67c7a7e7d33ef1ff9094809fb",
240
240
  "mode": "100644"
241
241
  },
242
242
  {
@@ -486,7 +486,7 @@
486
486
  },
487
487
  {
488
488
  "path": "src/steps/content/step-32-paywall.content.ts",
489
- "sha256": "f13a4cd4216465aa2610607a833af5889c31aa9f7e4894f4af578dad481057ed",
489
+ "sha256": "509ae6f98854ff024e14a1c53e04f583c2f29fa4cc409f432eba7fe845e3d320",
490
490
  "mode": "100644"
491
491
  },
492
492
  {
@@ -506,7 +506,7 @@
506
506
  },
507
507
  {
508
508
  "path": "src/steps/editor/step-32-paywall.editor.ts",
509
- "sha256": "4d40f9d4b11d02a0b6aad3c57ec874610535695b13e66b04364b900316cc2108",
509
+ "sha256": "9f6d5d126b456dc59ffe6f9f374b7e3325dd43f80edf8906b6715bb97eae3744",
510
510
  "mode": "100644"
511
511
  },
512
512
  {
@@ -551,7 +551,7 @@
551
551
  },
552
552
  {
553
553
  "path": "src/steps/step-32-paywall.tsx",
554
- "sha256": "53dc726a59f037bfb78a9fbb7961d70daf067a7f53e5fea99ce4c252b138b8c4",
554
+ "sha256": "283a035dacfb9b4d56f269c1176ac84d8519a69b67eb11e0d392ada4535e2f62",
555
555
  "mode": "100644"
556
556
  },
557
557
  {
@@ -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.14.0` first, then `@funnelsgrove/analytics` `0.1.100`, then `@funnelsgrove/payments` `0.15.3`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.222`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. Publishing packages and deploying production remain separately approved operational actions.
20
+ Release `@funnelsgrove/runtime` `0.14.0` first, then `@funnelsgrove/analytics` `0.1.100`, then `@funnelsgrove/payments` `0.15.3`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.223`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.214",
3
+ "bundleVersion": "2.0.215",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -41,6 +41,10 @@ type StepPaywallLocaleContent = {
41
41
  customerEmailLabel: string;
42
42
  customerEmailPlaceholder: string;
43
43
  customerEmailInvalidMessage: string;
44
+ emailPromptTitle: string;
45
+ emailPromptDescription: string;
46
+ emailPromptSubmitLabel: string;
47
+ emailPromptCancelLabel: string;
44
48
  countdownLabel: string;
45
49
  satisfactionPercent: string;
46
50
  satisfactionLabel: string;
@@ -135,6 +139,10 @@ export const stepPaywallContent = {
135
139
  customerEmailLabel: 'Email',
136
140
  customerEmailPlaceholder: 'Enter your email',
137
141
  customerEmailInvalidMessage: 'Enter a valid email address to continue.',
142
+ emailPromptTitle: 'Update your email',
143
+ emailPromptDescription: 'Stripe could not use this email. Enter a different address to continue with payment.',
144
+ emailPromptSubmitLabel: 'Continue',
145
+ emailPromptCancelLabel: 'Cancel',
138
146
  countdownLabel: 'Discount expires in',
139
147
  satisfactionPercent: '91%',
140
148
  satisfactionLabel: 'of users are satisfied with the plan and stay with us after its completion',
@@ -179,6 +179,30 @@ export const stepPaywallEditor: readonly StepEditorSection[] = [
179
179
  kind: 'text',
180
180
  path: 'checkout.customerEmailInvalidMessage',
181
181
  },
182
+ {
183
+ id: 'checkout.emailPromptTitle',
184
+ label: 'Email prompt title',
185
+ kind: 'text',
186
+ path: 'checkout.emailPromptTitle',
187
+ },
188
+ {
189
+ id: 'checkout.emailPromptDescription',
190
+ label: 'Email prompt description',
191
+ kind: 'textarea',
192
+ path: 'checkout.emailPromptDescription',
193
+ },
194
+ {
195
+ id: 'checkout.emailPromptSubmitLabel',
196
+ label: 'Email prompt submit label',
197
+ kind: 'text',
198
+ path: 'checkout.emailPromptSubmitLabel',
199
+ },
200
+ {
201
+ id: 'checkout.emailPromptCancelLabel',
202
+ label: 'Email prompt cancel label',
203
+ kind: 'text',
204
+ path: 'checkout.emailPromptCancelLabel',
205
+ },
182
206
  {
183
207
  id: 'checkout.countdownLabel',
184
208
  label: 'Checkout countdown label',
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
4
4
  import Image from 'next/image';
5
5
  import { checkoutRuntimeConfig } from '@/runtime/checkout-runtime-config';
6
6
  import {
@@ -72,6 +72,7 @@ type StoryOption = {
72
72
  };
73
73
 
74
74
  const PAYWALL_WALLET_SURFACE_ID = 'paywall-primary';
75
+ const STRIPE_INVALID_EMAIL_ERROR = 'Enter a valid email address to continue.';
75
76
 
76
77
  type PaywallWalletAvailabilityState = Partial<Record<string, boolean>>;
77
78
 
@@ -210,6 +211,10 @@ function StepPaywallRuntime({
210
211
  const [activeStoryIndex, setActiveStoryIndex] = useState(0);
211
212
  const [openFaqIds, setOpenFaqIds] = useState<string[]>([]);
212
213
  const [checkoutEmail, setCheckoutEmail] = useState(user.email);
214
+ const [checkoutEmailNeedsReview, setCheckoutEmailNeedsReview] = useState(false);
215
+ const [checkoutEmailPromptOpen, setCheckoutEmailPromptOpen] = useState(false);
216
+ const [checkoutEmailPromptDraft, setCheckoutEmailPromptDraft] = useState('');
217
+ const [checkoutEmailPromptError, setCheckoutEmailPromptError] = useState<string | null>(null);
213
218
  const [paywallWalletAvailability, setPaywallWalletAvailability] =
214
219
  useState<PaywallWalletAvailabilityState>({});
215
220
  const paywallStepRef = useRef<HTMLDivElement | null>(null);
@@ -366,13 +371,29 @@ function StepPaywallRuntime({
366
371
  () => typeof window === 'undefined' ? '' : window.location.href,
367
372
  [],
368
373
  );
374
+ const openCheckoutEmailPrompt = useCallback((showInvalidError: boolean) => {
375
+ setCheckoutEmailPromptDraft(checkoutEmail.trim() || user.email.trim());
376
+ setCheckoutEmailPromptError(
377
+ showInvalidError ? content.checkout.customerEmailInvalidMessage : null,
378
+ );
379
+ setCheckoutEmailPromptOpen(true);
380
+ }, [checkoutEmail, content.checkout.customerEmailInvalidMessage, user.email]);
381
+ const handleStripeCheckoutError = useCallback((message: string | null) => {
382
+ setError(message);
383
+ if (message !== STRIPE_INVALID_EMAIL_ERROR) {
384
+ return;
385
+ }
386
+
387
+ setCheckoutEmailNeedsReview(true);
388
+ openCheckoutEmailPrompt(true);
389
+ }, [openCheckoutEmailPrompt]);
369
390
  const checkoutSession = useStripeSubscriptionCheckoutSession({
370
391
  analyticsMetadata: checkoutAnalyticsMetadata,
371
392
  checkoutMode,
372
393
  couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null,
373
394
  customerEmail: checkoutEmail || user.email,
374
395
  displayPlan: selectedDisplayPlan,
375
- onError: setError,
396
+ onError: handleStripeCheckoutError,
376
397
  plan: checkoutProvider === 'stripe' ? selectedPlan : null,
377
398
  returnUrl: successReturnUrl,
378
399
  runtimeConfig: activeCheckoutRuntimeConfig,
@@ -491,6 +512,7 @@ function StepPaywallRuntime({
491
512
  !discountStateReady ||
492
513
  checkoutProvider !== 'stripe' ||
493
514
  checkoutDisabled ||
515
+ checkoutEmailNeedsReview ||
494
516
  checkoutCardClientSecret ||
495
517
  checkoutCardLoading
496
518
  ) {
@@ -500,6 +522,7 @@ function StepPaywallRuntime({
500
522
  void prepareCardCheckout({ checkoutStartSource: 'checkout_render' });
501
523
  }, [
502
524
  checkoutDisabled,
525
+ checkoutEmailNeedsReview,
503
526
  checkoutProvider,
504
527
  checkoutCardClientSecret,
505
528
  checkoutCardLoading,
@@ -577,6 +600,11 @@ function StepPaywallRuntime({
577
600
  return;
578
601
  }
579
602
 
603
+ if (checkoutProvider === 'stripe' && checkoutEmailNeedsReview) {
604
+ openCheckoutEmailPrompt(true);
605
+ return;
606
+ }
607
+
580
608
  if (checkoutProvider === 'solidgate') {
581
609
  setSolidgateAttemptKey(crypto.randomUUID());
582
610
  setCheckoutDialogOpen(true);
@@ -604,6 +632,11 @@ function StepPaywallRuntime({
604
632
  };
605
633
 
606
634
  const preparePaywallWalletCheckout = async (): Promise<string | null> => {
635
+ if (checkoutEmailNeedsReview) {
636
+ openCheckoutEmailPrompt(true);
637
+ return null;
638
+ }
639
+
607
640
  const normalizedEmail = checkoutEmail.trim() || user.email.trim();
608
641
  if (!normalizedEmail) {
609
642
  setCheckoutDialogOpen(true);
@@ -627,7 +660,13 @@ function StepPaywallRuntime({
627
660
 
628
661
  const commitCheckoutEmail = async (draftEmail: string): Promise<string> => {
629
662
  const normalizedEmail = draftEmail.trim();
630
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
663
+ if (
664
+ !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)
665
+ || (
666
+ checkoutEmailNeedsReview
667
+ && normalizedEmail.toLowerCase() === checkoutEmail.trim().toLowerCase()
668
+ )
669
+ ) {
631
670
  throw new Error(content.checkout.customerEmailInvalidMessage);
632
671
  }
633
672
 
@@ -654,6 +693,30 @@ function StepPaywallRuntime({
654
693
  return syncedEmail;
655
694
  };
656
695
 
696
+ const submitCheckoutEmail = async (event: FormEvent<HTMLFormElement>) => {
697
+ event.preventDefault();
698
+
699
+ try {
700
+ await commitCheckoutEmail(checkoutEmailPromptDraft);
701
+ setCheckoutEmailNeedsReview(false);
702
+ setCheckoutEmailPromptOpen(false);
703
+ setCheckoutEmailPromptError(null);
704
+ checkoutSession.setError(null);
705
+ setError(null);
706
+ } catch (submitError) {
707
+ setCheckoutEmailPromptError(
708
+ submitError instanceof Error && submitError.message
709
+ ? submitError.message
710
+ : content.checkout.customerEmailInvalidMessage,
711
+ );
712
+ }
713
+ };
714
+
715
+ const cancelCheckoutEmailPrompt = () => {
716
+ setCheckoutEmailPromptOpen(false);
717
+ setCheckoutEmailPromptError(null);
718
+ };
719
+
657
720
  const closeCheckoutDialog = () => {
658
721
  setCheckoutDialogOpen(false);
659
722
  setSolidgateAttemptKey(null);
@@ -700,6 +763,42 @@ function StepPaywallRuntime({
700
763
 
701
764
  return (
702
765
  <>
766
+ {checkoutProvider === 'stripe' && checkoutEmailPromptOpen ? (
767
+ <div className='paywall-v2-email-prompt-backdrop' role='presentation'>
768
+ <form
769
+ className='paywall-v2-email-prompt'
770
+ role='dialog'
771
+ aria-modal='true'
772
+ aria-labelledby='paywall-v2-email-prompt-title'
773
+ onSubmit={(event) => void submitCheckoutEmail(event)}
774
+ >
775
+ <h2 id='paywall-v2-email-prompt-title'>{content.checkout.emailPromptTitle}</h2>
776
+ <p>{content.checkout.emailPromptDescription}</p>
777
+ <label>
778
+ <span>{content.checkout.customerEmailLabel}</span>
779
+ <input
780
+ type='email'
781
+ inputMode='email'
782
+ autoComplete='email'
783
+ value={checkoutEmailPromptDraft}
784
+ onChange={(event) => setCheckoutEmailPromptDraft(event.currentTarget.value)}
785
+ placeholder={content.checkout.customerEmailPlaceholder}
786
+ autoFocus
787
+ />
788
+ </label>
789
+ {checkoutEmailPromptError ? (
790
+ <p className='paywall-v2-email-prompt-error'>{checkoutEmailPromptError}</p>
791
+ ) : null}
792
+ <div className='paywall-v2-email-prompt-actions'>
793
+ <button type='button' onClick={cancelCheckoutEmailPrompt}>
794
+ {content.checkout.emailPromptCancelLabel}
795
+ </button>
796
+ <button type='submit'>{content.checkout.emailPromptSubmitLabel}</button>
797
+ </div>
798
+ </form>
799
+ </div>
800
+ ) : null}
801
+
703
802
  {checkoutProvider === 'solidgate' &&
704
803
  checkoutDialogOpen &&
705
804
  solidgateAttemptKey &&
@@ -1012,14 +1111,14 @@ function StepPaywallRuntime({
1012
1111
  onAvailabilityChange={(availability) =>
1013
1112
  handleWalletSurfaceAvailabilityChange(PAYWALL_WALLET_SURFACE_ID, availability)
1014
1113
  }
1015
- onError={setError}
1114
+ onError={handleStripeCheckoutError}
1016
1115
  onSuccess={handleCheckoutCompleted}
1017
1116
  onUnavailableClick={openCardCheckoutFromUnavailableWallet}
1018
1117
  resolveMethodClassName={resolvePaywallWalletMethodClassName}
1019
1118
  resolveMethodWrapperClassName={resolvePaywallWalletMethodWrapperClassName}
1020
1119
  summaryLabel={checkoutSummaryLabel}
1021
1120
  surfaceId={PAYWALL_WALLET_SURFACE_ID}
1022
- suspended={checkoutDialogReady || specialOfferDialogOpen}
1121
+ suspended={checkoutDialogReady || checkoutEmailPromptOpen || specialOfferDialogOpen}
1023
1122
  />
1024
1123
  ) : null}
1025
1124
  <button
@@ -1900,6 +1999,96 @@ const stepPaywallStyles = `
1900
1999
  color: #d33;
1901
2000
  }
1902
2001
 
2002
+ .paywall-v2-email-prompt-backdrop {
2003
+ position: fixed;
2004
+ inset: 0;
2005
+ z-index: 1000;
2006
+ display: flex;
2007
+ align-items: center;
2008
+ justify-content: center;
2009
+ padding: 20px;
2010
+ background: rgb(13 17 32 / 42%);
2011
+ }
2012
+
2013
+ .paywall-v2-email-prompt {
2014
+ box-sizing: border-box;
2015
+ width: min(100%, 360px);
2016
+ border-radius: 20px;
2017
+ background: var(--color-surface, #fff);
2018
+ padding: 22px;
2019
+ box-shadow: 0 18px 48px rgb(19 27 47 / 26%);
2020
+ color: var(--color-text, #111827);
2021
+ }
2022
+
2023
+ .paywall-v2-email-prompt h2,
2024
+ .paywall-v2-email-prompt p {
2025
+ margin: 0;
2026
+ }
2027
+
2028
+ .paywall-v2-email-prompt h2 {
2029
+ font-size: 22px;
2030
+ font-weight: 800;
2031
+ line-height: 1.15;
2032
+ }
2033
+
2034
+ .paywall-v2-email-prompt > p {
2035
+ margin-top: 10px;
2036
+ color: var(--color-text, #4b5563);
2037
+ font-size: 15px;
2038
+ font-weight: 500;
2039
+ line-height: 1.45;
2040
+ }
2041
+
2042
+ .paywall-v2-email-prompt label {
2043
+ display: flex;
2044
+ flex-direction: column;
2045
+ gap: 8px;
2046
+ margin-top: 18px;
2047
+ font-size: 13px;
2048
+ font-weight: 700;
2049
+ }
2050
+
2051
+ .paywall-v2-email-prompt input {
2052
+ box-sizing: border-box;
2053
+ min-height: 48px;
2054
+ border: 1px solid var(--color-border, rgb(31 16 0 / 18%));
2055
+ border-radius: 12px;
2056
+ background: var(--color-surface, #fff);
2057
+ padding: 0 14px;
2058
+ color: var(--color-text, #111827);
2059
+ font: inherit;
2060
+ }
2061
+
2062
+ .paywall-v2-email-prompt-error {
2063
+ margin-top: 10px !important;
2064
+ color: var(--color-danger, #b42318);
2065
+ font-size: 13px;
2066
+ font-weight: 600;
2067
+ line-height: 1.35;
2068
+ }
2069
+
2070
+ .paywall-v2-email-prompt-actions {
2071
+ display: grid;
2072
+ grid-template-columns: 1fr 1fr;
2073
+ gap: 10px;
2074
+ margin-top: 18px;
2075
+ }
2076
+
2077
+ .paywall-v2-email-prompt-actions button {
2078
+ min-height: 44px;
2079
+ border: 0;
2080
+ border-radius: 12px;
2081
+ background: var(--color-bg, #eef1f6);
2082
+ color: var(--color-text, #111827);
2083
+ font-weight: 700;
2084
+ cursor: pointer;
2085
+ }
2086
+
2087
+ .paywall-v2-email-prompt-actions button[type='submit'] {
2088
+ background: var(--color-button);
2089
+ color: var(--color-button-text);
2090
+ }
2091
+
1903
2092
  @media (prefers-reduced-motion: reduce) {
1904
2093
  .paywall-v2-vibe-cta:not(:disabled),
1905
2094
  .paywall-v2-vibe-cta:not(:disabled)::after {