@funnelsgrove/cli 0.1.247 → 0.1.255
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/funnel-contract-compatibility.json +28 -28
- package/package.json +4 -4
- package/template_docs/.funnelsgrove-docs.json +3 -3
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +3 -3
- package/template_scaffold/.funnelsgrove-scaffold.json +23 -13
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/funnel.runtime-capabilities.json +4 -0
- package/template_scaffold/package-lock.json +64 -15
- package/template_scaffold/package.json +5 -3
- package/template_scaffold/src/components/FunnelFlow.tsx +2 -0
- package/template_scaffold/src/components/StripeFraudDetection.tsx +36 -0
- package/template_scaffold/src/config/billing.plans.ts +6 -3
- package/template_scaffold/src/config/billing.test.plans.ts +3 -3
- package/template_scaffold/src/steps/content/step-32-paywall.content.ts +3 -0
- package/template_scaffold/src/steps/editor/step-32-paywall.editor.ts +6 -0
- package/template_scaffold/src/steps/step-32-paywall.tsx +90 -34
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"minimumCliVersion": "0.1.20",
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
|
-
"repositoryCliVersion": "0.1.
|
|
7
|
+
"repositoryCliVersion": "0.1.255",
|
|
8
8
|
"manifest": {
|
|
9
9
|
"schemaVersion": 1,
|
|
10
|
-
"bundleVersion": "2.0.
|
|
10
|
+
"bundleVersion": "2.0.248",
|
|
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": "
|
|
48
|
+
"sha256": "19ea25028aebc54e553c223385ed1bf704a4f2bdef817250e01e566866034673"
|
|
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": "
|
|
148
|
+
"sha256": "07db87377a3d12b6bcf119d01bebffbbbb4702bb3cf83b8059132adbd2486a21"
|
|
149
149
|
}
|
|
150
150
|
]
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
|
-
"repositoryCliVersion": "0.1.
|
|
154
|
+
"repositoryCliVersion": "0.1.254",
|
|
155
155
|
"manifest": {
|
|
156
156
|
"schemaVersion": 1,
|
|
157
|
-
"bundleVersion": "2.0.
|
|
157
|
+
"bundleVersion": "2.0.247",
|
|
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": "
|
|
195
|
+
"sha256": "2aac7c053f6c267c5a6e6116d8d0c2358f490bbc3b40e2c01db2e4150edfa4d1"
|
|
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": "
|
|
295
|
+
"sha256": "0fdce657b56fb7e0a986a4bfb36abc8791203c1b0817655c15edb93883ec22f8"
|
|
296
296
|
}
|
|
297
297
|
]
|
|
298
298
|
}
|
|
299
299
|
},
|
|
300
300
|
{
|
|
301
|
-
"repositoryCliVersion": "0.1.
|
|
301
|
+
"repositoryCliVersion": "0.1.253",
|
|
302
302
|
"manifest": {
|
|
303
303
|
"schemaVersion": 1,
|
|
304
|
-
"bundleVersion": "2.0.
|
|
304
|
+
"bundleVersion": "2.0.246",
|
|
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": "
|
|
342
|
+
"sha256": "ad4da659c3e0ea1f35488d74787d3c8dee3da8f9cffda03887855221797155e9"
|
|
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": "
|
|
442
|
+
"sha256": "e3e3fdaf03b9dd63fae770038c20a7353c506fca85823f53118d014510ffa097"
|
|
443
443
|
}
|
|
444
444
|
]
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
|
-
"repositoryCliVersion": "0.1.
|
|
448
|
+
"repositoryCliVersion": "0.1.252",
|
|
449
449
|
"manifest": {
|
|
450
450
|
"schemaVersion": 1,
|
|
451
|
-
"bundleVersion": "2.0.
|
|
451
|
+
"bundleVersion": "2.0.245",
|
|
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": "
|
|
489
|
+
"sha256": "659e22a9a9518f350aceaa142c4f3ea2e21d6a4565b0946df45fde3222cc759d"
|
|
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": "
|
|
589
|
+
"sha256": "d3e5ed25c5cf968cf55b82f00397898d3b7988dd17106cba34ad901e113f9678"
|
|
590
590
|
}
|
|
591
591
|
]
|
|
592
592
|
}
|
|
593
593
|
},
|
|
594
594
|
{
|
|
595
|
-
"repositoryCliVersion": "0.1.
|
|
595
|
+
"repositoryCliVersion": "0.1.251",
|
|
596
596
|
"manifest": {
|
|
597
597
|
"schemaVersion": 1,
|
|
598
|
-
"bundleVersion": "2.0.
|
|
598
|
+
"bundleVersion": "2.0.244",
|
|
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": "
|
|
636
|
+
"sha256": "19bb286f5de372c3111fd0fa0b2c398643783b099b8e82ea5d38781788a2929b"
|
|
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": "
|
|
736
|
+
"sha256": "98784693ce1f52e6358fc960344bb0010f266d08acd41ebbd794cf8f7ce029ca"
|
|
737
737
|
}
|
|
738
738
|
]
|
|
739
739
|
}
|
|
740
740
|
},
|
|
741
741
|
{
|
|
742
|
-
"repositoryCliVersion": "0.1.
|
|
742
|
+
"repositoryCliVersion": "0.1.250",
|
|
743
743
|
"manifest": {
|
|
744
744
|
"schemaVersion": 1,
|
|
745
|
-
"bundleVersion": "2.0.
|
|
745
|
+
"bundleVersion": "2.0.243",
|
|
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": "
|
|
783
|
+
"sha256": "929819e1f8ccbdef0a748185d8ac08d71334ad98142f2c4f2c3f965b319b9f8e"
|
|
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": "
|
|
883
|
+
"sha256": "7da81e80573f3af5d89e52c5c1fa5e7df5c5cac0de699345c0ecdf4d359f51c9"
|
|
884
884
|
}
|
|
885
885
|
]
|
|
886
886
|
}
|
|
887
887
|
},
|
|
888
888
|
{
|
|
889
|
-
"repositoryCliVersion": "0.1.
|
|
889
|
+
"repositoryCliVersion": "0.1.249",
|
|
890
890
|
"manifest": {
|
|
891
891
|
"schemaVersion": 1,
|
|
892
|
-
"bundleVersion": "2.0.
|
|
892
|
+
"bundleVersion": "2.0.242",
|
|
893
893
|
"stepContractVersion": 3,
|
|
894
894
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
895
895
|
"managedFiles": [
|
|
@@ -927,7 +927,7 @@
|
|
|
927
927
|
},
|
|
928
928
|
{
|
|
929
929
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
930
|
-
"sha256": "
|
|
930
|
+
"sha256": "fd1c2321f5f59bc93a2580c5868b46c57cce679b0055712654e29ca26e636a50"
|
|
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": "
|
|
1030
|
+
"sha256": "7608c4cbd9bd67075d2db5a983134dc1f06d9fc1d779ebd8fe84bf44a015b035"
|
|
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.
|
|
3
|
+
"version": "0.1.255",
|
|
4
4
|
"description": "FunnelsGrove command-line tools for editing, syncing, and publishing funnels",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"test": "vitest run"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@funnelsgrove/runtime": "0.
|
|
37
|
+
"@funnelsgrove/runtime": "0.21.1",
|
|
38
38
|
"commander": "^12.0.0",
|
|
39
39
|
"ignore": "^7.0.8",
|
|
40
40
|
"typescript": "^5.8.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@funnelsgrove/analytics": "0.
|
|
44
|
-
"@funnelsgrove/payments": "0.
|
|
43
|
+
"@funnelsgrove/analytics": "0.4.1",
|
|
44
|
+
"@funnelsgrove/payments": "0.23.2",
|
|
45
45
|
"vitest": "^3.0.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion": "2.0.
|
|
3
|
+
"bundleVersion": "2.0.248",
|
|
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": "
|
|
41
|
+
"sha256": "19ea25028aebc54e553c223385ed1bf704a4f2bdef817250e01e566866034673"
|
|
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": "
|
|
141
|
+
"sha256": "07db87377a3d12b6bcf119d01bebffbbbb4702bb3cf83b8059132adbd2486a21"
|
|
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/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.21.1`, then `@funnelsgrove/analytics` `0.4.1`, then `@funnelsgrove/payments` `0.23.2`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.255`, 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.
|
|
3
|
+
"bundleVersion": "2.0.248",
|
|
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": "
|
|
41
|
+
"sha256": "19ea25028aebc54e553c223385ed1bf704a4f2bdef817250e01e566866034673"
|
|
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": "
|
|
141
|
+
"sha256": "07db87377a3d12b6bcf119d01bebffbbbb4702bb3cf83b8059132adbd2486a21"
|
|
142
142
|
}
|
|
143
143
|
]
|
|
144
144
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sourceTreeHash": "
|
|
3
|
+
"sourceTreeHash": "c3035a50bb6a37cbe8545129ac71167ceb858d612f5c26d488c520d5924415f0",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
|
-
"docsBundleVersion": "2.0.
|
|
5
|
+
"docsBundleVersion": "2.0.248",
|
|
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": "21e4af4b5c474d7d4ec3574da7d34fce72b9d854478de61ea84610fe76231f50",
|
|
20
20
|
"mode": "100644"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
79
|
-
"sha256": "
|
|
79
|
+
"sha256": "19ea25028aebc54e553c223385ed1bf704a4f2bdef817250e01e566866034673",
|
|
80
80
|
"mode": "100644"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
},
|
|
207
207
|
{
|
|
208
208
|
"path": "funnel-docs.config.json",
|
|
209
|
-
"sha256": "
|
|
209
|
+
"sha256": "07db87377a3d12b6bcf119d01bebffbbbb4702bb3cf83b8059132adbd2486a21",
|
|
210
210
|
"mode": "100644"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
@@ -214,6 +214,11 @@
|
|
|
214
214
|
"sha256": "6b5f7891fa9ef74e63d6b06f19a20c3aa3184a61541b76e18da4ce366e5faed8",
|
|
215
215
|
"mode": "100644"
|
|
216
216
|
},
|
|
217
|
+
{
|
|
218
|
+
"path": "funnel.runtime-capabilities.json",
|
|
219
|
+
"sha256": "3d74683170fced7afa9badfdf837706aac9c3d554690708b3ba31b009028a069",
|
|
220
|
+
"mode": "100644"
|
|
221
|
+
},
|
|
217
222
|
{
|
|
218
223
|
"path": "gitignore.template",
|
|
219
224
|
"sha256": "3a92bf7904de32466cfc5521796fe60bcde1301b88f88e1417709060041e2aec",
|
|
@@ -231,12 +236,12 @@
|
|
|
231
236
|
},
|
|
232
237
|
{
|
|
233
238
|
"path": "package-lock.json",
|
|
234
|
-
"sha256": "
|
|
239
|
+
"sha256": "cd9436fe9d8f31d32377ecf74254c8db0779eac12f23e41f5466dc9a8d85c91e",
|
|
235
240
|
"mode": "100644"
|
|
236
241
|
},
|
|
237
242
|
{
|
|
238
243
|
"path": "package.json",
|
|
239
|
-
"sha256": "
|
|
244
|
+
"sha256": "2e2f22b65bba762d82f76993fd318098756c2191a6455760fb044395f2a43d34",
|
|
240
245
|
"mode": "100644"
|
|
241
246
|
},
|
|
242
247
|
{
|
|
@@ -291,7 +296,12 @@
|
|
|
291
296
|
},
|
|
292
297
|
{
|
|
293
298
|
"path": "src/components/FunnelFlow.tsx",
|
|
294
|
-
"sha256": "
|
|
299
|
+
"sha256": "d85147018449917e0770fc00d479305b4f805323b1754a5ee1d0ea4a2e780840",
|
|
300
|
+
"mode": "100644"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"path": "src/components/StripeFraudDetection.tsx",
|
|
304
|
+
"sha256": "caad37065e53a6907dca8a556a08573d24736dccaf7c54d61aa2a74694883536",
|
|
295
305
|
"mode": "100644"
|
|
296
306
|
},
|
|
297
307
|
{
|
|
@@ -311,12 +321,12 @@
|
|
|
311
321
|
},
|
|
312
322
|
{
|
|
313
323
|
"path": "src/config/billing.plans.ts",
|
|
314
|
-
"sha256": "
|
|
324
|
+
"sha256": "a6cc4720de70f47a2c0ffe85be1fc47b53b85eb85d5bb2e750aefe970fbede31",
|
|
315
325
|
"mode": "100644"
|
|
316
326
|
},
|
|
317
327
|
{
|
|
318
328
|
"path": "src/config/billing.test.plans.ts",
|
|
319
|
-
"sha256": "
|
|
329
|
+
"sha256": "d4adecf2de68970886cc6df4d34d26f0db299965e6005ce32a6215a3b48e5f84",
|
|
320
330
|
"mode": "100644"
|
|
321
331
|
},
|
|
322
332
|
{
|
|
@@ -471,7 +481,7 @@
|
|
|
471
481
|
},
|
|
472
482
|
{
|
|
473
483
|
"path": "src/steps/content/step-32-paywall.content.ts",
|
|
474
|
-
"sha256": "
|
|
484
|
+
"sha256": "82d7ddca72c40d29a8509e856fb4a05b96faaab7befecb89c410f8ee43a95e12",
|
|
475
485
|
"mode": "100644"
|
|
476
486
|
},
|
|
477
487
|
{
|
|
@@ -496,7 +506,7 @@
|
|
|
496
506
|
},
|
|
497
507
|
{
|
|
498
508
|
"path": "src/steps/editor/step-32-paywall.editor.ts",
|
|
499
|
-
"sha256": "
|
|
509
|
+
"sha256": "e544665b1e0ae7b121630fab9c1de33b88dfd8f3835bf3f12ac2438f7cbd220a",
|
|
500
510
|
"mode": "100644"
|
|
501
511
|
},
|
|
502
512
|
{
|
|
@@ -541,7 +551,7 @@
|
|
|
541
551
|
},
|
|
542
552
|
{
|
|
543
553
|
"path": "src/steps/step-32-paywall.tsx",
|
|
544
|
-
"sha256": "
|
|
554
|
+
"sha256": "bebf58e628bf55a7b90d326dbde2b767b2b7054635d4354dc4b6202f96825219",
|
|
545
555
|
"mode": "100644"
|
|
546
556
|
},
|
|
547
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/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.21.1`, then `@funnelsgrove/analytics` `0.4.1`, then `@funnelsgrove/payments` `0.23.2`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.255`, 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
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"name": "funnel-template",
|
|
9
9
|
"version": "0.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@funnelsgrove/analytics": "^0.
|
|
12
|
-
"@funnelsgrove/payments": "^0.
|
|
13
|
-
"@funnelsgrove/runtime": "^0.
|
|
11
|
+
"@funnelsgrove/analytics": "^0.4.1",
|
|
12
|
+
"@funnelsgrove/payments": "^0.23.2",
|
|
13
|
+
"@funnelsgrove/runtime": "^0.21.1",
|
|
14
14
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
15
15
|
"@stripe/stripe-js": "^8.7.0",
|
|
16
16
|
"lucide-react": "^0.553.0",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"@types/node": "^20",
|
|
25
25
|
"@types/react": "^19",
|
|
26
26
|
"@types/react-dom": "^19",
|
|
27
|
+
"esbuild": "0.28.1",
|
|
27
28
|
"eslint": "^9",
|
|
28
29
|
"eslint-config-next": "16.1.6",
|
|
30
|
+
"playwright": "1.58.2",
|
|
29
31
|
"typescript": "^5",
|
|
30
32
|
"vite-node": "3.2.4",
|
|
31
33
|
"vitest": "^3.2.4"
|
|
@@ -938,17 +940,17 @@
|
|
|
938
940
|
}
|
|
939
941
|
},
|
|
940
942
|
"node_modules/@funnelsgrove/analytics": {
|
|
941
|
-
"version": "0.
|
|
942
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.
|
|
943
|
-
"integrity": "sha512-
|
|
943
|
+
"version": "0.4.1",
|
|
944
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.4.1.tgz",
|
|
945
|
+
"integrity": "sha512-9bj0d6fcmGoBlWmCGBqfi3o4jJFHT6TgufquyMPwpichVylhqzgGpSaJ8PPa6MCglHzyQbQfjvDAOhxrl4dZaw==",
|
|
944
946
|
"peerDependencies": {
|
|
945
|
-
"@funnelsgrove/runtime": "^0.
|
|
947
|
+
"@funnelsgrove/runtime": "^0.21.1"
|
|
946
948
|
}
|
|
947
949
|
},
|
|
948
950
|
"node_modules/@funnelsgrove/payments": {
|
|
949
|
-
"version": "0.
|
|
950
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.
|
|
951
|
-
"integrity": "sha512-
|
|
951
|
+
"version": "0.23.2",
|
|
952
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.23.2.tgz",
|
|
953
|
+
"integrity": "sha512-p+tjqo5Bl7g3bR02GOn5CCvFg/FGJwzZkbNUhHGLZjNQnSvBVQr8OluNEMCOvNmIC9B6OeutCZjcLvyoBnjltQ==",
|
|
952
954
|
"dependencies": {
|
|
953
955
|
"@solidgate/react-sdk": "1.34.0",
|
|
954
956
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
@@ -958,14 +960,14 @@
|
|
|
958
960
|
"stripe": "^20.3.1"
|
|
959
961
|
},
|
|
960
962
|
"peerDependencies": {
|
|
961
|
-
"@funnelsgrove/analytics": "^0.1
|
|
962
|
-
"@funnelsgrove/runtime": "^0.
|
|
963
|
+
"@funnelsgrove/analytics": "^0.4.1",
|
|
964
|
+
"@funnelsgrove/runtime": "^0.21.1"
|
|
963
965
|
}
|
|
964
966
|
},
|
|
965
967
|
"node_modules/@funnelsgrove/runtime": {
|
|
966
|
-
"version": "0.
|
|
967
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.
|
|
968
|
-
"integrity": "sha512-
|
|
968
|
+
"version": "0.21.1",
|
|
969
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.21.1.tgz",
|
|
970
|
+
"integrity": "sha512-RsPBT8rf0Ms7oU6dJehAY7cCYt7tCw1IjziiaVatguqt9Yusn3Mo2X7DyfIM1N0It6Klg/nlo1o7vwopRT28rg==",
|
|
969
971
|
"dependencies": {
|
|
970
972
|
"@funnelsgrove/sdk": "0.4.0",
|
|
971
973
|
"posthog-js": "^1.369.2",
|
|
@@ -6177,6 +6179,53 @@
|
|
|
6177
6179
|
"url": "https://github.com/sponsors/jonschlinkert"
|
|
6178
6180
|
}
|
|
6179
6181
|
},
|
|
6182
|
+
"node_modules/playwright": {
|
|
6183
|
+
"version": "1.58.2",
|
|
6184
|
+
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
|
6185
|
+
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
|
6186
|
+
"dev": true,
|
|
6187
|
+
"license": "Apache-2.0",
|
|
6188
|
+
"dependencies": {
|
|
6189
|
+
"playwright-core": "1.58.2"
|
|
6190
|
+
},
|
|
6191
|
+
"bin": {
|
|
6192
|
+
"playwright": "cli.js"
|
|
6193
|
+
},
|
|
6194
|
+
"engines": {
|
|
6195
|
+
"node": ">=18"
|
|
6196
|
+
},
|
|
6197
|
+
"optionalDependencies": {
|
|
6198
|
+
"fsevents": "2.3.2"
|
|
6199
|
+
}
|
|
6200
|
+
},
|
|
6201
|
+
"node_modules/playwright-core": {
|
|
6202
|
+
"version": "1.58.2",
|
|
6203
|
+
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
|
6204
|
+
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
|
6205
|
+
"dev": true,
|
|
6206
|
+
"license": "Apache-2.0",
|
|
6207
|
+
"bin": {
|
|
6208
|
+
"playwright-core": "cli.js"
|
|
6209
|
+
},
|
|
6210
|
+
"engines": {
|
|
6211
|
+
"node": ">=18"
|
|
6212
|
+
}
|
|
6213
|
+
},
|
|
6214
|
+
"node_modules/playwright/node_modules/fsevents": {
|
|
6215
|
+
"version": "2.3.2",
|
|
6216
|
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
|
6217
|
+
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
6218
|
+
"dev": true,
|
|
6219
|
+
"hasInstallScript": true,
|
|
6220
|
+
"license": "MIT",
|
|
6221
|
+
"optional": true,
|
|
6222
|
+
"os": [
|
|
6223
|
+
"darwin"
|
|
6224
|
+
],
|
|
6225
|
+
"engines": {
|
|
6226
|
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
6227
|
+
}
|
|
6228
|
+
},
|
|
6180
6229
|
"node_modules/possible-typed-array-names": {
|
|
6181
6230
|
"version": "1.1.0",
|
|
6182
6231
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
|
@@ -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.
|
|
16
|
-
"@funnelsgrove/payments": "^0.
|
|
17
|
-
"@funnelsgrove/runtime": "^0.
|
|
15
|
+
"@funnelsgrove/analytics": "^0.4.1",
|
|
16
|
+
"@funnelsgrove/payments": "^0.23.2",
|
|
17
|
+
"@funnelsgrove/runtime": "^0.21.1",
|
|
18
18
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
19
19
|
"@stripe/stripe-js": "^8.7.0",
|
|
20
20
|
"lucide-react": "^0.553.0",
|
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
"@types/node": "^20",
|
|
29
29
|
"@types/react": "^19",
|
|
30
30
|
"@types/react-dom": "^19",
|
|
31
|
+
"esbuild": "0.28.1",
|
|
31
32
|
"eslint": "^9",
|
|
32
33
|
"eslint-config-next": "16.1.6",
|
|
34
|
+
"playwright": "1.58.2",
|
|
33
35
|
"typescript": "^5",
|
|
34
36
|
"vite-node": "3.2.4",
|
|
35
37
|
"vitest": "^3.2.4"
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
usePreviewDefinitionOverrideRevision,
|
|
12
12
|
usePreviewStepLocalizedContent,
|
|
13
13
|
} from '@funnelsgrove/runtime';
|
|
14
|
+
import { StripeFraudDetection } from './StripeFraudDetection';
|
|
14
15
|
import { FunnelEditorPanel, FunnelEditorPanelStyles } from './FunnelEditorPanel';
|
|
15
16
|
import { runtimeContent } from '@/steps/content/runtime.content';
|
|
16
17
|
import { getStepContentLocale } from '@/runtime/step-content-context';
|
|
@@ -112,6 +113,7 @@ function FunnelFlowRuntime({
|
|
|
112
113
|
|
|
113
114
|
return (
|
|
114
115
|
<FunnelProvider value={controller.contextValue}>
|
|
116
|
+
<StripeFraudDetection featureFlags={controller.contextValue.featureFlags} />
|
|
115
117
|
<section
|
|
116
118
|
className="funnel-shell"
|
|
117
119
|
suppressHydrationWarning
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
resolvePublishedOfferSetSelection,
|
|
6
|
+
resolvePublishedRuntimeExperiments,
|
|
7
|
+
useBrowserLocationSearch,
|
|
8
|
+
useFunnelRuntimeConfig,
|
|
9
|
+
useRuntimeMode,
|
|
10
|
+
} from '@funnelsgrove/runtime';
|
|
11
|
+
|
|
12
|
+
/** Mount once in the funnel shell so lazy checkout screens still collect risk signals. */
|
|
13
|
+
export function StripeFraudDetection({ featureFlags }: {
|
|
14
|
+
featureFlags: Readonly<Record<string, string>>;
|
|
15
|
+
}) {
|
|
16
|
+
const { config } = useFunnelRuntimeConfig();
|
|
17
|
+
const [mode] = useRuntimeMode();
|
|
18
|
+
const search = useBrowserLocationSearch();
|
|
19
|
+
const { paymentProvidersByMode } = resolvePublishedOfferSetSelection({
|
|
20
|
+
offerSets: config?.offerSets ?? [],
|
|
21
|
+
pricingExperiments: resolvePublishedRuntimeExperiments(config).pricingExperiments,
|
|
22
|
+
featureFlags,
|
|
23
|
+
search,
|
|
24
|
+
defaultOfferSetId: config?.defaultOfferSetId ?? undefined,
|
|
25
|
+
});
|
|
26
|
+
const isStripe = paymentProvidersByMode[mode] === 'stripe';
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!isStripe) return;
|
|
30
|
+
// The official side-effect import loads Stripe.js with advanced signals enabled.
|
|
31
|
+
// Collection must never gate the quiz; checkout retains its own error handling.
|
|
32
|
+
void import('@stripe/stripe-js').catch(() => {});
|
|
33
|
+
}, [isStripe]);
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
@@ -21,7 +21,7 @@ const starterLivePlans = {
|
|
|
21
21
|
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
22
22
|
projectPlanId: '32b31f69-5907-4736-89c7-76d3acb38813',
|
|
23
23
|
title: stepPaywallContent.locales.en.catalogLabels["1-Week Plan"],
|
|
24
|
-
providerPlanId: '
|
|
24
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_LIVE_WEEKLY',
|
|
25
25
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$7.99/week"],
|
|
26
26
|
perDayAmount: '$1.14',
|
|
27
27
|
amountCents: 799,
|
|
@@ -32,7 +32,7 @@ const starterLivePlans = {
|
|
|
32
32
|
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
33
33
|
projectPlanId: 'ce0a0022-7bf0-41f2-a707-edc5e6113a07',
|
|
34
34
|
title: stepPaywallContent.locales.en.catalogLabels["1-Month Plan"],
|
|
35
|
-
providerPlanId: '
|
|
35
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_LIVE_MONTHLY',
|
|
36
36
|
featuredTag: stepPaywallContent.locales.en.catalogLabels["🔥 Most Popular Choice"],
|
|
37
37
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$29.99/month"],
|
|
38
38
|
perDayAmount: '$1.00',
|
|
@@ -45,7 +45,7 @@ const starterLivePlans = {
|
|
|
45
45
|
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
46
46
|
projectPlanId: 'fb623ebc-c2fd-4e6a-8eb6-b8ed92db1b21',
|
|
47
47
|
title: stepPaywallContent.locales.en.catalogLabels["1-Year Plan"],
|
|
48
|
-
providerPlanId: '
|
|
48
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_LIVE_YEARLY',
|
|
49
49
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$59.99/year"],
|
|
50
50
|
perDayAmount: '$0.16',
|
|
51
51
|
amountCents: 5999,
|
|
@@ -79,6 +79,7 @@ export const starterDefaultOfferSet = {
|
|
|
79
79
|
id: STARTER_OFFER_SET_ID,
|
|
80
80
|
key: STARTER_OFFER_SET_KEY,
|
|
81
81
|
isActive: true,
|
|
82
|
+
locales: null,
|
|
82
83
|
metadata: {
|
|
83
84
|
discounts: starterDiscounts.map((discount, position) => ({
|
|
84
85
|
key: discount.stage,
|
|
@@ -105,7 +106,9 @@ export const starterBillingRuntimeFallback = {
|
|
|
105
106
|
sourceDeploymentId: 'starter-template',
|
|
106
107
|
sourceVersionId: 'starter-template',
|
|
107
108
|
publishedAt: '1970-01-01T00:00:00.000Z',
|
|
109
|
+
localeContractVersion: 1,
|
|
108
110
|
sessionReplay: { enabled: false, sampleRate: 10 },
|
|
111
|
+
defaultOfferSetId: starterDefaultOfferSet.id,
|
|
109
112
|
offerSets: [starterDefaultOfferSet],
|
|
110
113
|
experiments: [],
|
|
111
114
|
} satisfies ResolvedFunnelRuntimeConfig;
|
|
@@ -5,7 +5,7 @@ export const billingTestPlans = {
|
|
|
5
5
|
primary: {
|
|
6
6
|
projectPlanId: '32b31f69-5907-4736-89c7-76d3acb38813',
|
|
7
7
|
title: stepPaywallContent.locales.en.catalogLabels["1-Week Plan"],
|
|
8
|
-
providerPlanId: '
|
|
8
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_TEST_WEEKLY',
|
|
9
9
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$9.99/week"],
|
|
10
10
|
perDayAmount: '$1.43',
|
|
11
11
|
amountCents: 999,
|
|
@@ -14,7 +14,7 @@ export const billingTestPlans = {
|
|
|
14
14
|
secondary: {
|
|
15
15
|
projectPlanId: 'ce0a0022-7bf0-41f2-a707-edc5e6113a07',
|
|
16
16
|
title: stepPaywallContent.locales.en.catalogLabels["1-Month Plan"],
|
|
17
|
-
providerPlanId: '
|
|
17
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_TEST_MONTHLY',
|
|
18
18
|
featuredTag: stepPaywallContent.locales.en.catalogLabels["🔥 Most Popular Choice"],
|
|
19
19
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$29.99/month"],
|
|
20
20
|
perDayAmount: '$1.00',
|
|
@@ -24,7 +24,7 @@ export const billingTestPlans = {
|
|
|
24
24
|
tertiary: {
|
|
25
25
|
projectPlanId: 'fb623ebc-c2fd-4e6a-8eb6-b8ed92db1b21',
|
|
26
26
|
title: stepPaywallContent.locales.en.catalogLabels["1-Year Plan"],
|
|
27
|
-
providerPlanId: '
|
|
27
|
+
providerPlanId: 'price_REPLACE_WITH_YOUR_STRIPE_TEST_YEARLY',
|
|
28
28
|
priceLabel: stepPaywallContent.locales.en.catalogLabels["$59.99/year"],
|
|
29
29
|
perDayAmount: '$0.16',
|
|
30
30
|
amountCents: 5999,
|
|
@@ -34,6 +34,7 @@ export type StepPaywallLocaleContent = {
|
|
|
34
34
|
cardButtonLabel: string;
|
|
35
35
|
metaLabels: readonly string[];
|
|
36
36
|
stripeUnavailableNote: string;
|
|
37
|
+
plansUnavailableNote: string;
|
|
37
38
|
perDayLabel: string;
|
|
38
39
|
};
|
|
39
40
|
checkout: {
|
|
@@ -157,6 +158,8 @@ export const stepPaywallContent = {
|
|
|
157
158
|
metaLabels: ['Money-back guarantee', 'Cancel anytime'],
|
|
158
159
|
stripeUnavailableNote:
|
|
159
160
|
'Stripe publishable key is not available in this app runtime. Update the project payment environment variables or make sure your SDK publishable key maps to a funnel with Stripe settings.',
|
|
161
|
+
plansUnavailableNote:
|
|
162
|
+
'Plans are unavailable right now. Please try again in a moment or contact support.',
|
|
160
163
|
},
|
|
161
164
|
checkout: {
|
|
162
165
|
title: 'Checkout',
|
|
@@ -125,6 +125,12 @@ export const stepPaywallEditor: readonly StepEditorSection[] = [
|
|
|
125
125
|
kind: 'textarea',
|
|
126
126
|
path: 'plans.stripeUnavailableNote',
|
|
127
127
|
},
|
|
128
|
+
{
|
|
129
|
+
id: 'plans.plansUnavailableNote',
|
|
130
|
+
label: 'Plans unavailable note',
|
|
131
|
+
kind: 'textarea',
|
|
132
|
+
path: 'plans.plansUnavailableNote',
|
|
133
|
+
},
|
|
128
134
|
],
|
|
129
135
|
},
|
|
130
136
|
{
|
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
import { stepPaywallContent } from '@/steps/content/step-32-paywall.content';
|
|
15
15
|
import {
|
|
16
16
|
PaymentRuntimeBoundary,
|
|
17
|
+
formatProviderMoney,
|
|
18
|
+
moneyToMajor,
|
|
19
|
+
type ProviderMoney,
|
|
17
20
|
apiService,
|
|
18
21
|
collectCurrentFunnelAttribution,
|
|
19
22
|
readPaywallStateValue,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
updatePaywallStateValue,
|
|
22
25
|
useFunnel,
|
|
23
26
|
useFunnelRuntimeConfig,
|
|
27
|
+
useBrowserOfferLocale,
|
|
24
28
|
usePreviewStepLocalizedContent,
|
|
25
29
|
useRuntimeMode,
|
|
26
30
|
type FunnelStepMeta,
|
|
@@ -38,7 +42,9 @@ import {
|
|
|
38
42
|
getDefaultPlanId,
|
|
39
43
|
getPaywallPromoDisplayName,
|
|
40
44
|
resolvePaywallDiscountState,
|
|
41
|
-
|
|
45
|
+
EMPTY_PUBLISHED_BILLING_RUNTIME,
|
|
46
|
+
resolvePublishedBillingRuntimeState,
|
|
47
|
+
usePinnedPublishedBillingRuntime,
|
|
42
48
|
serializePaywallDiscountState,
|
|
43
49
|
useStripeSubscriptionCheckoutSession,
|
|
44
50
|
type PaywallDiscountState,
|
|
@@ -93,12 +99,7 @@ const deferPaywallStateSync = (syncState: () => void): (() => void) => {
|
|
|
93
99
|
const timeoutId = window.setTimeout(syncState, 0);
|
|
94
100
|
return () => window.clearTimeout(timeoutId);
|
|
95
101
|
};
|
|
96
|
-
|
|
97
|
-
style: 'currency',
|
|
98
|
-
currency: 'USD',
|
|
99
|
-
minimumFractionDigits: 2,
|
|
100
|
-
maximumFractionDigits: 2,
|
|
101
|
-
});
|
|
102
|
+
|
|
102
103
|
const inactiveBillingDiscounts = buildBillingDiscountCatalog([
|
|
103
104
|
{
|
|
104
105
|
stage: 'first',
|
|
@@ -116,8 +117,8 @@ const inactiveBillingDiscounts = buildBillingDiscountCatalog([
|
|
|
116
117
|
},
|
|
117
118
|
]);
|
|
118
119
|
|
|
119
|
-
function formatCheckoutAmount(
|
|
120
|
-
return
|
|
120
|
+
function formatCheckoutAmount(amountMinor: number, money?: ProviderMoney): string {
|
|
121
|
+
return money ? formatProviderMoney({ ...money, amountMinor }, 'en-US') : '';
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
type StepPaywallProps = {
|
|
@@ -141,6 +142,7 @@ function StepPaywallRuntime({
|
|
|
141
142
|
const checkoutMode = runtimeMode;
|
|
142
143
|
const { user, setUser, attributes, featureFlags, completeStep, userBootstrapped } = useFunnel();
|
|
143
144
|
const publishedRuntime = useFunnelRuntimeConfig();
|
|
145
|
+
const browserLocale = useBrowserOfferLocale();
|
|
144
146
|
const content = usePreviewStepLocalizedContent(
|
|
145
147
|
stepPaywallId,
|
|
146
148
|
stepPaywallContent,
|
|
@@ -148,27 +150,33 @@ function StepPaywallRuntime({
|
|
|
148
150
|
);
|
|
149
151
|
const supportEmail = runtimePublicConfig.supportEmail;
|
|
150
152
|
const paywallPreviewAsset = funnelManifest.assets.paywallPreview;
|
|
151
|
-
const
|
|
152
|
-
() =>
|
|
153
|
-
config: publishedRuntime.config
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
const billingResolution = useMemo(
|
|
154
|
+
() => resolvePublishedBillingRuntimeState({
|
|
155
|
+
config: publishedRuntime.config ?? (
|
|
156
|
+
publishedRuntime.status === 'fallback' && !publishedRuntime.error
|
|
157
|
+
? starterBillingRuntimeFallback
|
|
158
|
+
: null
|
|
159
|
+
),
|
|
160
|
+
featureFlags: publishedRuntime.featureFlags ?? featureFlags,
|
|
157
161
|
mode: checkoutMode,
|
|
158
162
|
search: typeof window === 'undefined' ? '' : window.location.search,
|
|
159
163
|
baseCatalogsByMode: starterPlansByMode,
|
|
160
164
|
fallbackDiscounts: billingDiscountList,
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
locale: browserLocale.status === 'resolved' ? browserLocale.locale : undefined,
|
|
166
|
+
}, browserLocale.status === 'resolved' && publishedRuntime.status !== 'loading'),
|
|
167
|
+
[browserLocale, checkoutMode, featureFlags, publishedRuntime.config, publishedRuntime.featureFlags, publishedRuntime.status, publishedRuntime.error],
|
|
163
168
|
);
|
|
169
|
+
const billingSelection = usePinnedPublishedBillingRuntime(billingResolution);
|
|
170
|
+
const activeBillingRuntime = billingSelection.runtime ?? EMPTY_PUBLISHED_BILLING_RUNTIME;
|
|
171
|
+
const checkoutSelectionReady = billingSelection.resolution.status === 'resolved';
|
|
164
172
|
const activeCheckoutRuntimeConfig = useMemo(() => ({
|
|
165
173
|
...checkoutRuntimeConfig,
|
|
166
|
-
funnelVersionId:
|
|
174
|
+
funnelVersionId: activeBillingRuntime.sourceVersionId,
|
|
167
175
|
paymentsApiVersion: activeBillingRuntime.paymentsApiVersion,
|
|
168
176
|
}), [
|
|
169
177
|
activeBillingRuntime.paymentsApiVersion,
|
|
170
178
|
checkoutRuntimeConfig,
|
|
171
|
-
|
|
179
|
+
activeBillingRuntime.sourceVersionId,
|
|
172
180
|
]);
|
|
173
181
|
const activeBillingDiscounts = useMemo(
|
|
174
182
|
() => activeBillingRuntime.discounts ?? (
|
|
@@ -179,6 +187,28 @@ function StepPaywallRuntime({
|
|
|
179
187
|
[activeBillingRuntime.discounts, activeBillingRuntime.provider],
|
|
180
188
|
);
|
|
181
189
|
const availablePlans = activeBillingRuntime.plans;
|
|
190
|
+
const unavailablePlanKeys = activeBillingRuntime.unavailablePlanKeys;
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (unavailablePlanKeys.length === 0) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Visitors only see the neutral note below; the author opening the funnel needs to know
|
|
197
|
+
// which published plans this payment mode cannot charge.
|
|
198
|
+
console.error('[funnelsgrove] published plans are unavailable in this payment mode', {
|
|
199
|
+
versionId: activeBillingRuntime.sourceVersionId,
|
|
200
|
+
revisionId: activeBillingRuntime.revisionId,
|
|
201
|
+
mode: activeBillingRuntime.mode,
|
|
202
|
+
offerSetId: activeBillingRuntime.offerSetId,
|
|
203
|
+
unavailablePlanKeys,
|
|
204
|
+
});
|
|
205
|
+
}, [
|
|
206
|
+
activeBillingRuntime.mode,
|
|
207
|
+
activeBillingRuntime.offerSetId,
|
|
208
|
+
activeBillingRuntime.revisionId,
|
|
209
|
+
activeBillingRuntime.sourceVersionId,
|
|
210
|
+
unavailablePlanKeys,
|
|
211
|
+
]);
|
|
182
212
|
const initialPlanId = useMemo(
|
|
183
213
|
() =>
|
|
184
214
|
availablePlans.find((plan) => plan.id === activeBillingRuntime.defaultPlanKey)?.id ??
|
|
@@ -227,8 +257,10 @@ function StepPaywallRuntime({
|
|
|
227
257
|
plans: availablePlans,
|
|
228
258
|
couponId: activeCouponId,
|
|
229
259
|
discountPercent: discountState.discountPercent,
|
|
260
|
+
amountOffMinor: discountState.amountOffMinor,
|
|
261
|
+
discountCurrency: discountState.discountCurrency,
|
|
230
262
|
}).map((plan) => localizePaywallPlanLabels(plan, content.catalogLabels)),
|
|
231
|
-
[activeCouponId, availablePlans, discountState.discountPercent, content.catalogLabels],
|
|
263
|
+
[activeCouponId, availablePlans, discountState.discountPercent, discountState.amountOffMinor, discountState.discountCurrency, content.catalogLabels],
|
|
232
264
|
);
|
|
233
265
|
const selectedPlan = useMemo(() => {
|
|
234
266
|
if (availablePlans.length === 0) {
|
|
@@ -294,11 +326,12 @@ function StepPaywallRuntime({
|
|
|
294
326
|
|
|
295
327
|
return Math.max(0, selectedDisplayPlan.baseAmountCents - selectedDisplayPlan.amountCents);
|
|
296
328
|
}, [selectedDisplayPlan]);
|
|
297
|
-
const checkoutDiscountPercent = discountState.
|
|
329
|
+
const checkoutDiscountPercent = discountState.amountOffMinor !== undefined && selectedDisplayPlan?.baseAmountCents
|
|
330
|
+
? Number((appliedDiscountAmountCents / selectedDisplayPlan.baseAmountCents * 100).toFixed(2))
|
|
331
|
+
: discountState.discountPercent ?? 0;
|
|
298
332
|
const checkoutPromoActive =
|
|
299
333
|
Boolean(selectedDisplayPlan?.hasActiveDiscount) &&
|
|
300
334
|
discountState.status === 'active' &&
|
|
301
|
-
checkoutDiscountPercent > 0 &&
|
|
302
335
|
appliedDiscountAmountCents > 0;
|
|
303
336
|
const checkoutAnalyticsMetadata = useMemo(() => {
|
|
304
337
|
if (!selectedDisplayPlan) {
|
|
@@ -311,13 +344,18 @@ function StepPaywallRuntime({
|
|
|
311
344
|
couponId: checkoutPromoActive ? selectedDisplayPlan.couponId : null,
|
|
312
345
|
summaryLabel: checkoutSummaryLabel,
|
|
313
346
|
amountCents: selectedDisplayPlan.amountCents,
|
|
314
|
-
currency:
|
|
347
|
+
...(selectedDisplayPlan.money ? { money: selectedDisplayPlan.money, currency: selectedDisplayPlan.money.currency, value: moneyToMajor(selectedDisplayPlan.money) } : {}),
|
|
315
348
|
...(typeof selectedDisplayPlan.analyticsPurchaseValue === 'number'
|
|
316
349
|
? { value: selectedDisplayPlan.analyticsPurchaseValue }
|
|
317
350
|
: {}),
|
|
318
351
|
environment: checkoutMode,
|
|
352
|
+
offerSetId: activeBillingRuntime.offerSetId,
|
|
353
|
+
runtimeConfigRevisionId: activeBillingRuntime.revisionId,
|
|
354
|
+
experimentId: activeBillingRuntime.experimentId,
|
|
355
|
+
variantKey: activeBillingRuntime.variantKey,
|
|
356
|
+
locale: activeBillingRuntime.locale,
|
|
319
357
|
};
|
|
320
|
-
}, [checkoutMode, checkoutPromoActive, checkoutSummaryLabel, selectedDisplayPlan]);
|
|
358
|
+
}, [activeBillingRuntime, checkoutMode, checkoutPromoActive, checkoutSummaryLabel, selectedDisplayPlan]);
|
|
321
359
|
const checkoutAnalyticsContext = useMemo(() => {
|
|
322
360
|
if (!checkoutAnalyticsMetadata) {
|
|
323
361
|
return null;
|
|
@@ -325,7 +363,7 @@ function StepPaywallRuntime({
|
|
|
325
363
|
|
|
326
364
|
return {
|
|
327
365
|
environment: checkoutMode,
|
|
328
|
-
featureFlags,
|
|
366
|
+
featureFlags: activeBillingRuntime.featureFlags,
|
|
329
367
|
metadata: checkoutAnalyticsMetadata,
|
|
330
368
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
331
369
|
stepId: analyticsStepId,
|
|
@@ -339,7 +377,7 @@ function StepPaywallRuntime({
|
|
|
339
377
|
analyticsStepName,
|
|
340
378
|
checkoutAnalyticsMetadata,
|
|
341
379
|
checkoutMode,
|
|
342
|
-
featureFlags,
|
|
380
|
+
activeBillingRuntime.featureFlags,
|
|
343
381
|
]);
|
|
344
382
|
const successReturnUrl = useMemo(() => {
|
|
345
383
|
if (typeof window === 'undefined') {
|
|
@@ -383,7 +421,10 @@ function StepPaywallRuntime({
|
|
|
383
421
|
couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null,
|
|
384
422
|
customerEmail: checkoutEmail || user.email,
|
|
385
423
|
displayPlan: selectedDisplayPlan,
|
|
424
|
+
enabled: checkoutSelectionReady,
|
|
386
425
|
onError: handleStripeCheckoutError,
|
|
426
|
+
onPreparationStart: billingSelection.pin,
|
|
427
|
+
onReset: billingSelection.reset,
|
|
387
428
|
plan: checkoutProvider === 'stripe' ? selectedPlan : null,
|
|
388
429
|
returnUrl: successReturnUrl,
|
|
389
430
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
@@ -404,11 +445,14 @@ function StepPaywallRuntime({
|
|
|
404
445
|
customerEmail: checkoutEmail || user.email,
|
|
405
446
|
customerName: user.name,
|
|
406
447
|
displayPlan: selectedDisplayPlan,
|
|
448
|
+
enabled: checkoutSelectionReady,
|
|
407
449
|
plan: selectedPlan,
|
|
408
450
|
returnUrl: successReturnUrl,
|
|
409
451
|
runtimeConfig: activeCheckoutRuntimeConfig,
|
|
410
452
|
runtimeConfigRevisionId: activeBillingRuntime.revisionId,
|
|
411
453
|
paymentProfileId: activeBillingRuntime.paymentProfileId,
|
|
454
|
+
onPreparationStart: billingSelection.pin,
|
|
455
|
+
onReset: billingSelection.reset,
|
|
412
456
|
provider: checkoutProvider === 'stripe' ? 'stripe' : null,
|
|
413
457
|
stripePublishableKey: activeBillingRuntime.stripePublishableKey,
|
|
414
458
|
userId: user.id,
|
|
@@ -417,10 +461,13 @@ function StepPaywallRuntime({
|
|
|
417
461
|
checkoutAnalyticsMetadata,
|
|
418
462
|
checkoutEmail,
|
|
419
463
|
checkoutMode,
|
|
464
|
+
checkoutSelectionReady,
|
|
420
465
|
checkoutPromoActive,
|
|
421
466
|
activeBillingRuntime.stripePublishableKey,
|
|
422
467
|
activeBillingRuntime.paymentProfileId,
|
|
423
468
|
activeBillingRuntime.revisionId,
|
|
469
|
+
billingSelection.pin,
|
|
470
|
+
billingSelection.reset,
|
|
424
471
|
activeCheckoutRuntimeConfig,
|
|
425
472
|
checkoutProvider,
|
|
426
473
|
selectedDisplayPlan,
|
|
@@ -436,7 +483,7 @@ function StepPaywallRuntime({
|
|
|
436
483
|
&& activeBillingRuntime.offerSetId.trim()
|
|
437
484
|
&& selectedPlan?.funnelPlanKey?.trim(),
|
|
438
485
|
);
|
|
439
|
-
const checkoutDisabled = !userBootstrapped || !selectedPlan || !selectedDisplayPlan || (
|
|
486
|
+
const checkoutDisabled = !checkoutSelectionReady || !userBootstrapped || !selectedPlan || !selectedDisplayPlan || (
|
|
440
487
|
checkoutProvider === 'solidgate'
|
|
441
488
|
? !solidgateCheckoutConfigured
|
|
442
489
|
: !checkoutSession.configured
|
|
@@ -462,12 +509,12 @@ function StepPaywallRuntime({
|
|
|
462
509
|
String(checkoutDiscountPercent),
|
|
463
510
|
)
|
|
464
511
|
: '';
|
|
465
|
-
const checkoutDiscountAmountLabel = checkoutPromoActive
|
|
466
|
-
? `-${formatCheckoutAmount(appliedDiscountAmountCents)}`
|
|
512
|
+
const checkoutDiscountAmountLabel = checkoutPromoActive && selectedDisplayPlan?.money
|
|
513
|
+
? `-${formatCheckoutAmount(appliedDiscountAmountCents, selectedDisplayPlan?.money)}`
|
|
467
514
|
: '';
|
|
468
|
-
const checkoutSavedLabel = checkoutPromoActive
|
|
515
|
+
const checkoutSavedLabel = checkoutPromoActive && selectedDisplayPlan?.money
|
|
469
516
|
? content.checkout.savedLabelTemplate
|
|
470
|
-
.replace('{amount}', formatCheckoutAmount(appliedDiscountAmountCents))
|
|
517
|
+
.replace('{amount}', formatCheckoutAmount(appliedDiscountAmountCents, selectedDisplayPlan?.money))
|
|
471
518
|
.replace('{percent}', String(checkoutDiscountPercent))
|
|
472
519
|
: '';
|
|
473
520
|
useEffect(() => {
|
|
@@ -708,6 +755,8 @@ function StepPaywallRuntime({
|
|
|
708
755
|
};
|
|
709
756
|
|
|
710
757
|
const closeCheckoutDialog = () => {
|
|
758
|
+
if (checkoutProvider === 'stripe') checkoutSession.reset();
|
|
759
|
+
else billingSelection.reset();
|
|
711
760
|
setCheckoutDialogOpen(false);
|
|
712
761
|
setSolidgateAttemptKey(null);
|
|
713
762
|
setError(null);
|
|
@@ -736,7 +785,10 @@ function StepPaywallRuntime({
|
|
|
736
785
|
);
|
|
737
786
|
};
|
|
738
787
|
|
|
739
|
-
if (
|
|
788
|
+
if (billingSelection.resolution.status === 'error') {
|
|
789
|
+
return <div role='alert'>Payment options are temporarily unavailable.</div>;
|
|
790
|
+
}
|
|
791
|
+
if (!discountStateReady || billingSelection.resolution.status === 'pending') {
|
|
740
792
|
return (
|
|
741
793
|
<>
|
|
742
794
|
<div className='paywall-v2-loading-screen' aria-live='polite'>
|
|
@@ -789,7 +841,7 @@ function StepPaywallRuntime({
|
|
|
789
841
|
</div>
|
|
790
842
|
) : null}
|
|
791
843
|
|
|
792
|
-
{checkoutProvider === 'solidgate' &&
|
|
844
|
+
{checkoutSelectionReady && checkoutProvider === 'solidgate' &&
|
|
793
845
|
checkoutDialogOpen &&
|
|
794
846
|
solidgateAttemptKey &&
|
|
795
847
|
selectedPlan?.funnelPlanKey ? (
|
|
@@ -846,6 +898,7 @@ function StepPaywallRuntime({
|
|
|
846
898
|
onClose={closeCheckoutDialog}
|
|
847
899
|
onCustomerEmailCommit={commitCheckoutEmail}
|
|
848
900
|
onError={setError}
|
|
901
|
+
onPreparationStart={billingSelection.pin}
|
|
849
902
|
onSuccess={handleCheckoutCompleted}
|
|
850
903
|
/>
|
|
851
904
|
) : null}
|
|
@@ -1001,7 +1054,7 @@ function StepPaywallRuntime({
|
|
|
1001
1054
|
</span>
|
|
1002
1055
|
) : null}
|
|
1003
1056
|
<span className='paywall-v2-promo-upgrade-badge-main'>
|
|
1004
|
-
{content.checkout.discountLabelTemplate.replace('{percent}', String(
|
|
1057
|
+
{content.checkout.discountLabelTemplate.replace('{percent}', String(checkoutDiscountPercent))}
|
|
1005
1058
|
</span>
|
|
1006
1059
|
</div>
|
|
1007
1060
|
<p className='paywall-v2-promo-upgrade-label'>{content.promo.upgradedLabel}</p>
|
|
@@ -1125,6 +1178,9 @@ function StepPaywallRuntime({
|
|
|
1125
1178
|
{content.checkout.supportText}{' '}
|
|
1126
1179
|
<a href={`mailto:${supportEmail}`}>{supportEmail}</a>
|
|
1127
1180
|
</p>
|
|
1181
|
+
{availablePlans.length === 0 ? (
|
|
1182
|
+
<p className='paywall-v2-note' role='alert'>{content.plans.plansUnavailableNote}</p>
|
|
1183
|
+
) : null}
|
|
1128
1184
|
{checkoutProvider === 'stripe' && !checkoutSession.configured ? (
|
|
1129
1185
|
<p className='paywall-v2-note'>{content.plans.stripeUnavailableNote}</p>
|
|
1130
1186
|
) : null}
|