@volter/twin-stripe 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/dist/src/index.js +6 -4
- package/dist/src/manifest.js +8 -3
- package/dist/src/screens/checkout.js +20 -6
- package/dist/src/screens/connect-oauth.d.ts +27 -0
- package/dist/src/screens/connect-oauth.js +414 -0
- package/dist/src/screens/connect-settings.d.ts +22 -0
- package/dist/src/screens/connect-settings.js +103 -0
- package/dist/src/screens/portal.js +2 -0
- package/dist/src/semantics/after-payment.d.ts +1 -1
- package/dist/src/semantics/after-payment.js +6 -0
- package/dist/src/semantics/charges.js +10 -2
- package/dist/src/semantics/checkout.js +19 -6
- package/dist/src/semantics/connect.js +20 -3
- package/dist/src/semantics/invoices.js +4 -0
- package/dist/src/semantics/issuing.js +7 -2
- package/dist/src/semantics/ledger.d.ts +11 -6
- package/dist/src/semantics/ledger.js +40 -21
- package/dist/src/semantics/payment-methods.js +2 -0
- package/dist/src/semantics/shared.d.ts +5 -1
- package/dist/src/semantics/shared.js +14 -3
- package/dist/src/semantics/test-cards.d.ts +4 -0
- package/dist/src/semantics/test-cards.js +7 -0
- package/dist/src/semantics/transfers.js +1 -1
- package/dist/src/stripe-capabilities.js +829 -186
- package/dist/src/stripe-conformance.d.ts +2 -0
- package/dist/src/stripe-conformance.js +11 -2
- package/dist/src/stripe-emit.js +2 -2
- package/dist/src/stripe-events.js +14 -10
- package/dist/src/stripe-mirror-ui.js +3 -3
- package/dist/src/stripe-server.js +97 -24
- package/dist/src/stripe-shared.d.ts +3 -0
- package/dist/src/stripe-shared.js +3 -0
- package/dist/src/stripe-twin.js +7 -1
- package/dist/src/stripe-version.d.ts +2 -0
- package/dist/src/stripe-version.js +2 -0
- package/dist/test-fixtures/stripe-known-deviations.json +6 -1
- package/dist/test-fixtures/stripe-schemas.json +85 -12
- package/package.json +4 -4
- package/src/index.ts +6 -4
- package/src/manifest.ts +8 -3
- package/src/screens/checkout.tsx +21 -6
- package/src/screens/connect-oauth.tsx +400 -0
- package/src/screens/connect-settings.tsx +121 -0
- package/src/screens/portal.tsx +2 -0
- package/src/semantics/after-payment.ts +6 -1
- package/src/semantics/charges.ts +11 -2
- package/src/semantics/checkout.ts +19 -6
- package/src/semantics/connect.ts +19 -3
- package/src/semantics/invoices.ts +4 -0
- package/src/semantics/issuing.ts +7 -2
- package/src/semantics/ledger.ts +60 -23
- package/src/semantics/payment-methods.ts +2 -0
- package/src/semantics/shared.ts +14 -3
- package/src/semantics/test-cards.ts +7 -0
- package/src/semantics/transfers.ts +1 -1
- package/src/stripe-capabilities.ts +826 -182
- package/src/stripe-conformance.ts +13 -2
- package/src/stripe-emit.ts +2 -2
- package/src/stripe-events.ts +14 -10
- package/src/stripe-mirror-ui.ts +3 -3
- package/src/stripe-server.ts +85 -24
- package/src/stripe-shared.ts +3 -0
- package/src/stripe-twin.ts +6 -1
- package/src/stripe-version.ts +3 -0
- package/test-fixtures/stripe-known-deviations.json +6 -1
- package/test-fixtures/stripe-schemas.json +85 -12
|
@@ -297,6 +297,10 @@
|
|
|
297
297
|
"status"
|
|
298
298
|
],
|
|
299
299
|
"properties": {
|
|
300
|
+
"allowed_payment_method_types": {
|
|
301
|
+
"type": "array",
|
|
302
|
+
"nullable": true
|
|
303
|
+
},
|
|
300
304
|
"amount": {
|
|
301
305
|
"type": "integer"
|
|
302
306
|
},
|
|
@@ -419,6 +423,9 @@
|
|
|
419
423
|
"payment_method_types": {
|
|
420
424
|
"type": "array"
|
|
421
425
|
},
|
|
426
|
+
"payment_record": {
|
|
427
|
+
"nullable": true
|
|
428
|
+
},
|
|
422
429
|
"presentment_details": {},
|
|
423
430
|
"processing": {
|
|
424
431
|
"nullable": true
|
|
@@ -495,6 +502,13 @@
|
|
|
495
502
|
"currency": {
|
|
496
503
|
"type": "string"
|
|
497
504
|
},
|
|
505
|
+
"customer": {
|
|
506
|
+
"nullable": true
|
|
507
|
+
},
|
|
508
|
+
"customer_account": {
|
|
509
|
+
"type": "string",
|
|
510
|
+
"nullable": true
|
|
511
|
+
},
|
|
498
512
|
"description": {
|
|
499
513
|
"type": "string"
|
|
500
514
|
},
|
|
@@ -523,6 +537,9 @@
|
|
|
523
537
|
"payment_intent": {
|
|
524
538
|
"nullable": true
|
|
525
539
|
},
|
|
540
|
+
"payment_method": {
|
|
541
|
+
"nullable": true
|
|
542
|
+
},
|
|
526
543
|
"pending_reason": {
|
|
527
544
|
"enum": [
|
|
528
545
|
"charge_pending",
|
|
@@ -726,6 +743,7 @@
|
|
|
726
743
|
],
|
|
727
744
|
"type": "string"
|
|
728
745
|
},
|
|
746
|
+
"status_details": {},
|
|
729
747
|
"test_clock": {
|
|
730
748
|
"nullable": true
|
|
731
749
|
},
|
|
@@ -757,6 +775,10 @@
|
|
|
757
775
|
"usage"
|
|
758
776
|
],
|
|
759
777
|
"properties": {
|
|
778
|
+
"allowed_payment_method_types": {
|
|
779
|
+
"type": "array",
|
|
780
|
+
"nullable": true
|
|
781
|
+
},
|
|
760
782
|
"application": {
|
|
761
783
|
"nullable": true
|
|
762
784
|
},
|
|
@@ -953,6 +975,7 @@
|
|
|
953
975
|
"payco": {},
|
|
954
976
|
"paynow": {},
|
|
955
977
|
"paypal": {},
|
|
978
|
+
"paypay": {},
|
|
956
979
|
"payto": {},
|
|
957
980
|
"pix": {},
|
|
958
981
|
"promptpay": {},
|
|
@@ -962,6 +985,7 @@
|
|
|
962
985
|
"satispay": {},
|
|
963
986
|
"scalapay": {},
|
|
964
987
|
"sepa_debit": {},
|
|
988
|
+
"sequra": {},
|
|
965
989
|
"sofort": {},
|
|
966
990
|
"sunbit": {},
|
|
967
991
|
"swish": {},
|
|
@@ -1204,6 +1228,9 @@
|
|
|
1204
1228
|
"tax_code": {
|
|
1205
1229
|
"nullable": true
|
|
1206
1230
|
},
|
|
1231
|
+
"tax_details": {
|
|
1232
|
+
"nullable": true
|
|
1233
|
+
},
|
|
1207
1234
|
"unit_label": {
|
|
1208
1235
|
"type": "string",
|
|
1209
1236
|
"nullable": true
|
|
@@ -1500,6 +1527,7 @@
|
|
|
1500
1527
|
"type": "string",
|
|
1501
1528
|
"nullable": true
|
|
1502
1529
|
},
|
|
1530
|
+
"status_details": {},
|
|
1503
1531
|
"status_transitions": {},
|
|
1504
1532
|
"subtotal": {
|
|
1505
1533
|
"type": "integer"
|
|
@@ -1579,12 +1607,18 @@
|
|
|
1579
1607
|
"type": "array",
|
|
1580
1608
|
"nullable": true
|
|
1581
1609
|
},
|
|
1610
|
+
"frozen_fields": {
|
|
1611
|
+
"type": "array"
|
|
1612
|
+
},
|
|
1582
1613
|
"id": {
|
|
1583
1614
|
"type": "string"
|
|
1584
1615
|
},
|
|
1585
1616
|
"invoice": {
|
|
1586
1617
|
"nullable": true
|
|
1587
1618
|
},
|
|
1619
|
+
"invoicing_rules": {
|
|
1620
|
+
"type": "array"
|
|
1621
|
+
},
|
|
1588
1622
|
"livemode": {
|
|
1589
1623
|
"type": "boolean"
|
|
1590
1624
|
},
|
|
@@ -2045,6 +2079,10 @@
|
|
|
2045
2079
|
"type": "boolean",
|
|
2046
2080
|
"nullable": true
|
|
2047
2081
|
},
|
|
2082
|
+
"allowed_payment_method_types": {
|
|
2083
|
+
"type": "array",
|
|
2084
|
+
"nullable": true
|
|
2085
|
+
},
|
|
2048
2086
|
"amount_subtotal": {
|
|
2049
2087
|
"type": "integer",
|
|
2050
2088
|
"nullable": true
|
|
@@ -2575,7 +2613,12 @@
|
|
|
2575
2613
|
},
|
|
2576
2614
|
"livemode": {
|
|
2577
2615
|
"type": "boolean"
|
|
2578
|
-
}
|
|
2616
|
+
},
|
|
2617
|
+
"company": {},
|
|
2618
|
+
"groups": {
|
|
2619
|
+
"nullable": true
|
|
2620
|
+
},
|
|
2621
|
+
"individual": {}
|
|
2579
2622
|
}
|
|
2580
2623
|
},
|
|
2581
2624
|
"transfer": {
|
|
@@ -2583,7 +2626,6 @@
|
|
|
2583
2626
|
"required": [
|
|
2584
2627
|
"amount",
|
|
2585
2628
|
"amount_reversed",
|
|
2586
|
-
"balance_transaction",
|
|
2587
2629
|
"created",
|
|
2588
2630
|
"currency",
|
|
2589
2631
|
"id",
|
|
@@ -2645,13 +2687,13 @@
|
|
|
2645
2687
|
"tax_rate": {
|
|
2646
2688
|
"type": "object",
|
|
2647
2689
|
"required": [
|
|
2648
|
-
"id",
|
|
2649
|
-
"object",
|
|
2650
2690
|
"active",
|
|
2651
2691
|
"created",
|
|
2652
2692
|
"display_name",
|
|
2693
|
+
"id",
|
|
2653
2694
|
"inclusive",
|
|
2654
2695
|
"livemode",
|
|
2696
|
+
"object",
|
|
2655
2697
|
"percentage"
|
|
2656
2698
|
],
|
|
2657
2699
|
"properties": {
|
|
@@ -2709,20 +2751,36 @@
|
|
|
2709
2751
|
"tax_type": {
|
|
2710
2752
|
"type": "string",
|
|
2711
2753
|
"nullable": true
|
|
2754
|
+
},
|
|
2755
|
+
"effective_percentage": {
|
|
2756
|
+
"type": "number",
|
|
2757
|
+
"nullable": true
|
|
2758
|
+
},
|
|
2759
|
+
"flat_amount": {
|
|
2760
|
+
"nullable": true
|
|
2761
|
+
},
|
|
2762
|
+
"rate_type": {
|
|
2763
|
+
"enum": [
|
|
2764
|
+
"flat_amount",
|
|
2765
|
+
"percentage"
|
|
2766
|
+
],
|
|
2767
|
+
"type": "string",
|
|
2768
|
+
"nullable": true
|
|
2712
2769
|
}
|
|
2713
2770
|
}
|
|
2714
2771
|
},
|
|
2715
2772
|
"tax.calculation": {
|
|
2716
2773
|
"type": "object",
|
|
2717
2774
|
"required": [
|
|
2718
|
-
"object",
|
|
2719
2775
|
"amount_total",
|
|
2720
2776
|
"currency",
|
|
2721
2777
|
"customer_details",
|
|
2722
|
-
"
|
|
2778
|
+
"livemode",
|
|
2779
|
+
"object",
|
|
2723
2780
|
"tax_amount_exclusive",
|
|
2724
2781
|
"tax_amount_inclusive",
|
|
2725
|
-
"tax_breakdown"
|
|
2782
|
+
"tax_breakdown",
|
|
2783
|
+
"tax_date"
|
|
2726
2784
|
],
|
|
2727
2785
|
"properties": {
|
|
2728
2786
|
"id": {
|
|
@@ -2772,18 +2830,24 @@
|
|
|
2772
2830
|
},
|
|
2773
2831
|
"tax_date": {
|
|
2774
2832
|
"type": "integer"
|
|
2833
|
+
},
|
|
2834
|
+
"ship_from_details": {
|
|
2835
|
+
"nullable": true
|
|
2775
2836
|
}
|
|
2776
2837
|
}
|
|
2777
2838
|
},
|
|
2778
2839
|
"tax.calculation_line_item": {
|
|
2779
2840
|
"type": "object",
|
|
2780
2841
|
"required": [
|
|
2781
|
-
"id",
|
|
2782
|
-
"object",
|
|
2783
2842
|
"amount",
|
|
2784
2843
|
"amount_tax",
|
|
2844
|
+
"id",
|
|
2785
2845
|
"livemode",
|
|
2786
|
-
"
|
|
2846
|
+
"object",
|
|
2847
|
+
"quantity",
|
|
2848
|
+
"reference",
|
|
2849
|
+
"tax_behavior",
|
|
2850
|
+
"tax_code"
|
|
2787
2851
|
],
|
|
2788
2852
|
"properties": {
|
|
2789
2853
|
"id": {
|
|
@@ -2827,18 +2891,27 @@
|
|
|
2827
2891
|
},
|
|
2828
2892
|
"tax_code": {
|
|
2829
2893
|
"type": "string"
|
|
2894
|
+
},
|
|
2895
|
+
"metadata": {
|
|
2896
|
+
"type": "object",
|
|
2897
|
+
"nullable": true
|
|
2898
|
+
},
|
|
2899
|
+
"performance_location": {
|
|
2900
|
+
"type": "string",
|
|
2901
|
+
"nullable": true
|
|
2830
2902
|
}
|
|
2831
2903
|
}
|
|
2832
2904
|
},
|
|
2833
2905
|
"tax.registration": {
|
|
2834
2906
|
"type": "object",
|
|
2835
2907
|
"required": [
|
|
2836
|
-
"id",
|
|
2837
|
-
"object",
|
|
2838
2908
|
"active_from",
|
|
2839
2909
|
"country",
|
|
2840
2910
|
"country_options",
|
|
2911
|
+
"created",
|
|
2912
|
+
"id",
|
|
2841
2913
|
"livemode",
|
|
2914
|
+
"object",
|
|
2842
2915
|
"status"
|
|
2843
2916
|
],
|
|
2844
2917
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-stripe",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/world-core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
"postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@volter/world-ui": "0.1.
|
|
59
|
+
"@volter/world-ui": "0.1.2",
|
|
60
60
|
"react": "^19.2.7",
|
|
61
61
|
"react-dom": "^19.2.7"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@volter/world-core": "2.0.
|
|
64
|
+
"@volter/world-core": "2.0.2"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@volter/world-core": "2.0.
|
|
67
|
+
"@volter/world-core": "2.0.2",
|
|
68
68
|
"@volter/world-tooling": "0.1.0",
|
|
69
69
|
"@types/bun": "^1.2.20",
|
|
70
70
|
"@types/node": "^24.0.0",
|
package/src/index.ts
CHANGED
|
@@ -93,7 +93,7 @@ export const pack: TwinPack = {
|
|
|
93
93
|
// one live API host — declared HERE, not in the central maps (descriptor-first exemplar (adding-a-twin.md §3);
|
|
94
94
|
// the pack-facts artifact carries them to covers/inspect-project and inject.cjs).
|
|
95
95
|
// NEXT_PRIVATE_STRIPE_* joined the stem list in the adoption-facts sweep 2026-08-31.
|
|
96
|
-
// `stripeconnect` is deliberately NOT claimed:
|
|
96
|
+
// `stripeconnect` is deliberately NOT claimed: no Connect-only credential stem is modeled, so a repo
|
|
97
97
|
// holding only that credential is build-surface, not coverage this twin can honor.
|
|
98
98
|
adoption: {
|
|
99
99
|
// Stripe's official Python bindings.
|
|
@@ -101,15 +101,17 @@ export const pack: TwinPack = {
|
|
|
101
101
|
sdks: ['stripe'], envStems: ['STRIPE', 'NEXTPRIVATESTRIPE'],
|
|
102
102
|
},
|
|
103
103
|
// The API and the hosted flows (src/screens): checkout.stripe.com's payment page, billing.stripe.com's customer
|
|
104
|
-
// portal, connect.stripe.com's onboarding
|
|
104
|
+
// portal, connect.stripe.com's onboarding and its OAuth endpoints (/oauth/authorize, /oauth/token, /oauth/deauthorize:
|
|
105
|
+
// src/screens/connect-oauth.tsx), verify.stripe.com's identity check and the bank-linking flow Stripe.js
|
|
105
106
|
// opens: an application that sends a person to one of their urls lands in the World. dashboard.stripe.com serves the
|
|
106
|
-
// Dashboard's Public details page (src/screens/public-details.tsx), where the operator sets the name customers see
|
|
107
|
+
// Dashboard's Public details page (src/screens/public-details.tsx), where the operator sets the name customers see,
|
|
108
|
+
// and its Connect OAuth settings (src/screens/connect-settings.tsx: the client_id, OAuth on, the redirect URIs);
|
|
107
109
|
// claiming the host makes every other dashboard.stripe.com path an unclaimed path on a twinned host, refused in a
|
|
108
110
|
// World rather than sent to the real Dashboard. js.stripe.com also serves
|
|
109
111
|
// Stripe.js itself, the client library an application's page loads (stripe-js.ts): /v3/, /v3/stripe.js and /<release train>/stripe.js.
|
|
110
112
|
hosts: [
|
|
111
113
|
{ host: 'api.stripe.com' }, { host: 'checkout.stripe.com', pathPattern: '^/c/pay/' }, { host: 'billing.stripe.com', pathPattern: '^/p/session/' },
|
|
112
|
-
{ host: 'connect.stripe.com', pathPattern: '^/setup/' }, { host: 'dashboard.stripe.com', pathPattern: '^/settings/public
|
|
114
|
+
{ host: 'connect.stripe.com', pathPattern: '^/setup/|^/oauth/(authorize|token|deauthorize)/?$' }, { host: 'dashboard.stripe.com', pathPattern: '^/settings/public/?$|^/(test/)?settings/connect(/onboarding-options/oauth)?/?$' }, { host: 'verify.stripe.com', pathPattern: '^/start/' }, { host: 'js.stripe.com', pathPattern: '^/v3/financial-connections/|^/v3/?$|^/(v3|[a-z]+)/stripe\\.js$' },
|
|
113
115
|
],
|
|
114
116
|
// DELIVER support: signed event synthesis from twin state (`world-stripe emit`).
|
|
115
117
|
emitter: stripeEmitter,
|
package/src/manifest.ts
CHANGED
|
@@ -567,6 +567,11 @@ export const manifest: DerivedManifest = {
|
|
|
567
567
|
demand: '3 of 85 applications onboard connected accounts with Account Links', controls: ['Agree and submit', '← Return to platform'],
|
|
568
568
|
source: 'https://docs.stripe.com/connect/hosted-onboarding',
|
|
569
569
|
},
|
|
570
|
+
{
|
|
571
|
+
id: 'connect-oauth', kind: 'flow', host: 'connect.stripe.com', path: '/oauth/authorize', status: 'done',
|
|
572
|
+
demand: "Cal.com's Stripe app connects each organizer's Standard account through Connect OAuth", controls: ['Skip this form', 'Deny access'],
|
|
573
|
+
source: 'https://docs.stripe.com/connect/oauth-reference',
|
|
574
|
+
},
|
|
570
575
|
{
|
|
571
576
|
id: 'identity-verification', kind: 'flow', host: 'verify.stripe.com', path: '/start/{verification_session}', status: 'done',
|
|
572
577
|
demand: 'a verification report is written only when a person completes this page', controls: ['Verify', 'Fail verification'],
|
|
@@ -578,9 +583,9 @@ export const manifest: DerivedManifest = {
|
|
|
578
583
|
source: 'https://docs.stripe.com/js/financial_connections/collect_financial_connections_accounts',
|
|
579
584
|
},
|
|
580
585
|
{
|
|
581
|
-
id: 'dashboard', kind: 'workspace', host: 'dashboard.stripe.com', path: '/test/{section}; /settings/public', status: 'done',
|
|
582
|
-
demand: 'testers and agents inspect payments, customers and subscriptions; an operator sets the business name Checkout shows (Public details)',
|
|
583
|
-
controls: ['Business name', 'Save'], source: 'https://docs.stripe.com/dashboard/basics',
|
|
586
|
+
id: 'dashboard', kind: 'workspace', host: 'dashboard.stripe.com', path: '/test/{section}; /settings/public; /settings/connect/onboarding-options/oauth', status: 'done',
|
|
587
|
+
demand: 'testers and agents inspect payments, customers and subscriptions; an operator sets the business name Checkout shows (Public details) and a Connect platform its OAuth client_id and redirect URIs',
|
|
588
|
+
controls: ['Business name', 'Save', 'Enable OAuth for Standard accounts', 'Redirect URIs'], source: 'https://docs.stripe.com/dashboard/basics',
|
|
584
589
|
},
|
|
585
590
|
],
|
|
586
591
|
resources: {
|
package/src/screens/checkout.tsx
CHANGED
|
@@ -111,6 +111,15 @@ function afterTrial(session: Row): number {
|
|
|
111
111
|
return subtotal - (coupon && coupon.duration !== 'once' ? applyCouponDiscount(subtotal, coupon) : 0);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const EMAIL = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
115
|
+
|
|
116
|
+
/** The email of the session's customer when it has a valid one: "If the Customer already has a valid email set, the email
|
|
117
|
+
* will be prefilled and not editable in Checkout" (docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer). */
|
|
118
|
+
function customerEmailOf(session: Row): string | undefined {
|
|
119
|
+
const email = (session._customer as Row | undefined)?.email;
|
|
120
|
+
return typeof email === 'string' && EMAIL.test(email) ? email : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
114
123
|
function page(session: Row, id: string, now: number, values: Record<string, string> = {}, error?: string): Response {
|
|
115
124
|
const lines = (((session.line_items as { data?: Row[] } | undefined)?.data) ?? []).map((item) => ({
|
|
116
125
|
name: String(item.description ?? 'Item'),
|
|
@@ -122,9 +131,12 @@ function page(session: Row, id: string, now: number, values: Record<string, stri
|
|
|
122
131
|
const trial = trialOf(session, now);
|
|
123
132
|
const submit = mode === 'setup' ? 'Save card' : mode === 'subscription' ? (trial ? 'Start trial' : 'Subscribe') : 'Pay';
|
|
124
133
|
const trialText = trial ? trialSummary(session, trial) : undefined;
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
const onFile = customerEmailOf(session);
|
|
135
|
+
const contact: PaymentField[] = onFile
|
|
136
|
+
? [{ id: 'email', label: 'Email', type: 'email', autoComplete: 'email', value: onFile, readOnly: true }]
|
|
137
|
+
: typeof session.customer_email === 'string'
|
|
138
|
+
? [{ id: 'email', label: 'Email', type: 'email', autoComplete: 'email', value: session.customer_email }]
|
|
139
|
+
: [{ id: 'email', label: 'Email', type: 'email', autoComplete: 'email', placeholder: 'email@example.com', value: values.email ?? '' }];
|
|
128
140
|
const card: PaymentField[] = [
|
|
129
141
|
{ id: 'cardNumber', label: 'Card number', autoComplete: 'cc-number', placeholder: '1234 1234 1234 1234', value: values.cardNumber ?? '' },
|
|
130
142
|
{ id: 'cardExpiry', label: 'Expiration', autoComplete: 'cc-exp', placeholder: 'MM / YY', value: values.cardExpiry ?? '', format: 'card-expiry' },
|
|
@@ -191,9 +203,10 @@ export function cardAnswer(v: Record<string, string>, nowSeconds: number, charge
|
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
async function pay(ctx: SemanticsContext, id: string, session: Row, v: Record<string, string>): Promise<Response> {
|
|
194
|
-
const
|
|
206
|
+
const onFile = customerEmailOf(session);
|
|
207
|
+
const email = onFile ?? (typeof session.customer_email === 'string' ? session.customer_email : (v.email ?? '').trim());
|
|
195
208
|
if (session.mode !== 'setup' || !session.customer) {
|
|
196
|
-
if (
|
|
209
|
+
if (!EMAIL.test(email)) return page(session, id, Number(ctx.now()), v, 'Your email address is incomplete.');
|
|
197
210
|
}
|
|
198
211
|
// a trial charges nothing now unless a one-time price is on the first invoice
|
|
199
212
|
const oneTimeDue = (((session.line_items as { data?: Row[] } | undefined)?.data) ?? []).some((it, i) => isOneTime(it, inlineOf(session, i)));
|
|
@@ -207,6 +220,8 @@ async function pay(ctx: SemanticsContext, id: string, session: Row, v: Record<st
|
|
|
207
220
|
const customer = await created(ctx, 'customer', { id: cid, email, ...(v.billingName ? { name: v.billingName } : {}) }, { livemode: false, ...newCustomer(cid) });
|
|
208
221
|
existing = { ...session, customer: customer.id };
|
|
209
222
|
}
|
|
223
|
+
// "If the Customer does not have a valid email, Checkout will set the email entered during the session on the Customer" (the same page)
|
|
224
|
+
if (typeof session.customer === 'string' && !onFile && EMAIL.test(email)) await ctx.write('customer', session.customer, { email }, 'customer.update');
|
|
210
225
|
const details: Row = {
|
|
211
226
|
customer_details: { email, name: v.billingName || null, address: { country: v.billingCountry || null, postal_code: v.billingPostalCode || null, city: null, line1: null, line2: null, state: null }, phone: null, tax_exempt: 'none', tax_ids: [] },
|
|
212
227
|
...(existing.customer !== session.customer ? { customer: existing.customer } : {}),
|
|
@@ -246,7 +261,7 @@ export function stripeCheckoutFlow(scope: { root?: string; clock?: () => string
|
|
|
246
261
|
if (session.status !== 'open' && (request.method === 'GET' || ctx.legal(CS, 'status', COMPLETE_OPERATION.id, String(session.status), 'complete', id, 'external'))) return gone();
|
|
247
262
|
const stored = ctx.row(CS, id) ?? {};
|
|
248
263
|
const coupon = (stored._discount as Row | undefined)?.coupon;
|
|
249
|
-
const full = { ...session, _subscription_data: stored._subscription_data, _price_data: stored._price_data, _merchant: merchantOf(ctx, stored), _coupon: typeof coupon === 'string' ? ctx.get('coupon', coupon) : undefined };
|
|
264
|
+
const full = { ...session, _subscription_data: stored._subscription_data, _price_data: stored._price_data, _merchant: merchantOf(ctx, stored), _coupon: typeof coupon === 'string' ? ctx.get('coupon', coupon) : undefined, _customer: typeof session.customer === 'string' ? ctx.get('customer', session.customer) : undefined };
|
|
250
265
|
return request.method === 'GET' ? page(full, id, Number(ctx.now())) : pay(ctx, id, full, values);
|
|
251
266
|
};
|
|
252
267
|
}
|