@agent-cards/checkout 0.17.0 → 0.19.0
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/CHANGELOG.md +16 -0
- package/PREFLIGHT.md +4 -0
- package/README.md +106 -14
- package/dist/adyen-merchant-hosted.generated.d.ts +277 -0
- package/dist/adyen-merchant-hosted.generated.js +1902 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-fields.generated.d.ts +3 -0
- package/dist/card-fields.generated.js +46 -0
- package/dist/cdp.d.ts +6 -1
- package/dist/cdp.js +574 -220
- package/dist/client.d.ts +285 -5
- package/dist/client.js +590 -12
- package/dist/cse-body.d.ts +25 -0
- package/dist/cse-body.js +41 -0
- package/dist/fiserv.d.ts +65 -0
- package/dist/fiserv.generated.d.ts +73 -0
- package/dist/fiserv.generated.js +830 -0
- package/dist/fiserv.js +104 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -1
- package/dist/lifecycle.d.ts +38 -1
- package/dist/lifecycle.js +77 -5
- package/dist/merchant-handoff.d.ts +54 -0
- package/dist/merchant-handoff.js +100 -0
- package/dist/merchant-hosted.d.ts +140 -0
- package/dist/merchant-hosted.js +170 -0
- package/dist/merchant-total-watch.d.ts +115 -0
- package/dist/merchant-total-watch.js +268 -0
- package/dist/merchant-total.d.ts +257 -0
- package/dist/merchant-total.js +383 -0
- package/dist/pre-claim.d.ts +123 -0
- package/dist/pre-claim.js +386 -0
- package/dist/preflight-capabilities.generated.d.ts +1 -1
- package/dist/preflight-capabilities.generated.js +1 -1
- package/dist/preflight-catalog.json +133 -1
- package/dist/preflight-schemas.json +14 -2
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +15 -1
- package/dist/preparation.d.ts +13 -0
- package/dist/preparation.js +46 -9
- package/dist/prepared-processor.d.ts +36 -3
- package/dist/prepared-processor.js +53 -3
- package/dist/registry.d.ts +60 -0
- package/dist/registry.js +14 -0
- package/dist/stripe-checkout.generated.js +140 -20
- package/dist/substitutions.generated.d.ts +2 -1
- package/dist/substitutions.generated.js +758 -6
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
|
@@ -2097,6 +2097,72 @@
|
|
|
2097
2097
|
"hostname_pattern": "^www\\.mercadopago\\.com\\.ar$",
|
|
2098
2098
|
"pathname_pattern": "^\\/checkout\\/v1\\/payment\\/redirect\\/[^/]+\\/card-form\\/?$"
|
|
2099
2099
|
},
|
|
2100
|
+
{
|
|
2101
|
+
"id": "fiserv-commercehub-payment-fields",
|
|
2102
|
+
"psp": "fiserv",
|
|
2103
|
+
"kinds": [
|
|
2104
|
+
"script"
|
|
2105
|
+
],
|
|
2106
|
+
"origin": "https://commercehub-checkout.fiservapps.com",
|
|
2107
|
+
"path_pattern": "/sdk/:version/payment-fields.js",
|
|
2108
|
+
"sources": [
|
|
2109
|
+
{
|
|
2110
|
+
"kind": "primary_source",
|
|
2111
|
+
"reference": "https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js",
|
|
2112
|
+
"reviewed_on": "2026-09-23"
|
|
2113
|
+
},
|
|
2114
|
+
{
|
|
2115
|
+
"kind": "primary_source",
|
|
2116
|
+
"reference": "https://orangepay-ecommerce.hdpayments.homedepot.com/env.js",
|
|
2117
|
+
"reviewed_on": "2026-09-23"
|
|
2118
|
+
}
|
|
2119
|
+
],
|
|
2120
|
+
"hostname_pattern": "^commercehub-checkout\\.fiservapps\\.com$",
|
|
2121
|
+
"pathname_pattern": "^\\/sdk\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/payment-fields\\.js$"
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
"id": "fiserv-commercehub-checkout-script",
|
|
2125
|
+
"psp": "fiserv",
|
|
2126
|
+
"kinds": [
|
|
2127
|
+
"script"
|
|
2128
|
+
],
|
|
2129
|
+
"origin": "https://commercehub-checkout.fiservapps.com",
|
|
2130
|
+
"path_pattern": "/sdk/:version/checkout.js",
|
|
2131
|
+
"sources": [
|
|
2132
|
+
{
|
|
2133
|
+
"kind": "primary_source",
|
|
2134
|
+
"reference": "https://developer.fiserv.com/product/CommerceHub/docs/Online-Commerce/Integration-Options/Hosted-Checkout/Hosted-Checkout-Version-Release",
|
|
2135
|
+
"reviewed_on": "2026-09-23"
|
|
2136
|
+
}
|
|
2137
|
+
],
|
|
2138
|
+
"hostname_pattern": "^commercehub-checkout\\.fiservapps\\.com$",
|
|
2139
|
+
"pathname_pattern": "^\\/sdk\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/checkout\\.js$"
|
|
2140
|
+
},
|
|
2141
|
+
{
|
|
2142
|
+
"id": "fiserv-secure-data-capture-frame",
|
|
2143
|
+
"psp": "fiserv",
|
|
2144
|
+
"kinds": [
|
|
2145
|
+
"frame",
|
|
2146
|
+
"iframe",
|
|
2147
|
+
"document"
|
|
2148
|
+
],
|
|
2149
|
+
"origin": "https://commercehub-secure-data-capture.fiservapps.com",
|
|
2150
|
+
"path_pattern": "/sdk-secure/:version/iframe.html",
|
|
2151
|
+
"sources": [
|
|
2152
|
+
{
|
|
2153
|
+
"kind": "primary_source",
|
|
2154
|
+
"reference": "https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js",
|
|
2155
|
+
"reviewed_on": "2026-09-23"
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
"kind": "primary_source",
|
|
2159
|
+
"reference": "https://commercehub-secure-data-capture.fiservapps.com/sdk-secure/3.8.14/iframe.html",
|
|
2160
|
+
"reviewed_on": "2026-09-23"
|
|
2161
|
+
}
|
|
2162
|
+
],
|
|
2163
|
+
"hostname_pattern": "^commercehub-secure-data-capture\\.fiservapps\\.com$",
|
|
2164
|
+
"pathname_pattern": "^\\/sdk-secure\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
|
|
2165
|
+
},
|
|
2100
2166
|
{
|
|
2101
2167
|
"id": "square-web-sdk-sandbox",
|
|
2102
2168
|
"psp": "square",
|
|
@@ -2454,6 +2520,72 @@
|
|
|
2454
2520
|
],
|
|
2455
2521
|
"hostname_pattern": "^checkoutshopper-live-nea\\.adyen\\.com$",
|
|
2456
2522
|
"pathname_pattern": "^\\/checkoutshopper\\/securedfields\\/[^/]+\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/securedFields\\.html$"
|
|
2523
|
+
},
|
|
2524
|
+
{
|
|
2525
|
+
"id": "fiserv-commercehub-payment-fields-cert",
|
|
2526
|
+
"psp": "fiserv",
|
|
2527
|
+
"kinds": [
|
|
2528
|
+
"script"
|
|
2529
|
+
],
|
|
2530
|
+
"origin": "https://commercehub-checkout-cert.fiservapps.com",
|
|
2531
|
+
"path_pattern": "/sdk/:version/payment-fields.js",
|
|
2532
|
+
"sources": [
|
|
2533
|
+
{
|
|
2534
|
+
"kind": "primary_source",
|
|
2535
|
+
"reference": "https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js",
|
|
2536
|
+
"reviewed_on": "2026-09-23"
|
|
2537
|
+
},
|
|
2538
|
+
{
|
|
2539
|
+
"kind": "primary_source",
|
|
2540
|
+
"reference": "https://orangepay-ecommerce.hdpayments.homedepot.com/env.js",
|
|
2541
|
+
"reviewed_on": "2026-09-23"
|
|
2542
|
+
}
|
|
2543
|
+
],
|
|
2544
|
+
"hostname_pattern": "^commercehub-checkout-cert\\.fiservapps\\.com$",
|
|
2545
|
+
"pathname_pattern": "^\\/sdk\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/payment-fields\\.js$"
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
"id": "fiserv-commercehub-checkout-script-cert",
|
|
2549
|
+
"psp": "fiserv",
|
|
2550
|
+
"kinds": [
|
|
2551
|
+
"script"
|
|
2552
|
+
],
|
|
2553
|
+
"origin": "https://commercehub-checkout-cert.fiservapps.com",
|
|
2554
|
+
"path_pattern": "/sdk/:version/checkout.js",
|
|
2555
|
+
"sources": [
|
|
2556
|
+
{
|
|
2557
|
+
"kind": "primary_source",
|
|
2558
|
+
"reference": "https://developer.fiserv.com/product/CommerceHub/docs/Online-Commerce/Integration-Options/Hosted-Checkout/Hosted-Checkout-Version-Release",
|
|
2559
|
+
"reviewed_on": "2026-09-23"
|
|
2560
|
+
}
|
|
2561
|
+
],
|
|
2562
|
+
"hostname_pattern": "^commercehub-checkout-cert\\.fiservapps\\.com$",
|
|
2563
|
+
"pathname_pattern": "^\\/sdk\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/checkout\\.js$"
|
|
2564
|
+
},
|
|
2565
|
+
{
|
|
2566
|
+
"id": "fiserv-secure-data-capture-frame-cert",
|
|
2567
|
+
"psp": "fiserv",
|
|
2568
|
+
"kinds": [
|
|
2569
|
+
"frame",
|
|
2570
|
+
"iframe",
|
|
2571
|
+
"document"
|
|
2572
|
+
],
|
|
2573
|
+
"origin": "https://commercehub-secure-data-capture-cert.fiservapps.com",
|
|
2574
|
+
"path_pattern": "/sdk-secure/:version/iframe.html",
|
|
2575
|
+
"sources": [
|
|
2576
|
+
{
|
|
2577
|
+
"kind": "primary_source",
|
|
2578
|
+
"reference": "https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js",
|
|
2579
|
+
"reviewed_on": "2026-09-23"
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
"kind": "primary_source",
|
|
2583
|
+
"reference": "https://commercehub-secure-data-capture.fiservapps.com/sdk-secure/3.8.14/iframe.html",
|
|
2584
|
+
"reviewed_on": "2026-09-23"
|
|
2585
|
+
}
|
|
2586
|
+
],
|
|
2587
|
+
"hostname_pattern": "^commercehub-secure-data-capture-cert\\.fiservapps\\.com$",
|
|
2588
|
+
"pathname_pattern": "^\\/sdk-secure\\/[0-9]+\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
|
|
2457
2589
|
}
|
|
2458
2590
|
],
|
|
2459
2591
|
"processors": [
|
|
@@ -2640,7 +2772,7 @@
|
|
|
2640
2772
|
},
|
|
2641
2773
|
{
|
|
2642
2774
|
"id": "stripe.tokenization",
|
|
2643
|
-
"path_pattern": "^/v1/(?:payment_methods|tokens)$",
|
|
2775
|
+
"path_pattern": "^/v1/(?:payment_methods|tokens|sources|confirmation_tokens)$",
|
|
2644
2776
|
"effect": "token_or_method_creation",
|
|
2645
2777
|
"amount_binding": "none",
|
|
2646
2778
|
"psp": "stripe",
|
|
@@ -463,6 +463,9 @@
|
|
|
463
463
|
"dlocal-smart-card-fields",
|
|
464
464
|
"mercado-pago-secure-fields-frame",
|
|
465
465
|
"mercado-pago-checkout-pro-card-form",
|
|
466
|
+
"fiserv-commercehub-payment-fields",
|
|
467
|
+
"fiserv-commercehub-checkout-script",
|
|
468
|
+
"fiserv-secure-data-capture-frame",
|
|
466
469
|
"square-web-sdk-sandbox",
|
|
467
470
|
"paysafe-js-test",
|
|
468
471
|
"worldpay-checkout-sdk-try",
|
|
@@ -476,7 +479,10 @@
|
|
|
476
479
|
"adyen-secured-fields-live-au",
|
|
477
480
|
"adyen-secured-fields-live-apse",
|
|
478
481
|
"adyen-secured-fields-live-in",
|
|
479
|
-
"adyen-secured-fields-live-nea"
|
|
482
|
+
"adyen-secured-fields-live-nea",
|
|
483
|
+
"fiserv-commercehub-payment-fields-cert",
|
|
484
|
+
"fiserv-commercehub-checkout-script-cert",
|
|
485
|
+
"fiserv-secure-data-capture-frame-cert"
|
|
480
486
|
]
|
|
481
487
|
},
|
|
482
488
|
"kind": {
|
|
@@ -983,6 +989,9 @@
|
|
|
983
989
|
"dlocal-smart-card-fields",
|
|
984
990
|
"mercado-pago-secure-fields-frame",
|
|
985
991
|
"mercado-pago-checkout-pro-card-form",
|
|
992
|
+
"fiserv-commercehub-payment-fields",
|
|
993
|
+
"fiserv-commercehub-checkout-script",
|
|
994
|
+
"fiserv-secure-data-capture-frame",
|
|
986
995
|
"square-web-sdk-sandbox",
|
|
987
996
|
"paysafe-js-test",
|
|
988
997
|
"worldpay-checkout-sdk-try",
|
|
@@ -996,7 +1005,10 @@
|
|
|
996
1005
|
"adyen-secured-fields-live-au",
|
|
997
1006
|
"adyen-secured-fields-live-apse",
|
|
998
1007
|
"adyen-secured-fields-live-in",
|
|
999
|
-
"adyen-secured-fields-live-nea"
|
|
1008
|
+
"adyen-secured-fields-live-nea",
|
|
1009
|
+
"fiserv-commercehub-payment-fields-cert",
|
|
1010
|
+
"fiserv-commercehub-checkout-script-cert",
|
|
1011
|
+
"fiserv-secure-data-capture-frame-cert"
|
|
1000
1012
|
]
|
|
1001
1013
|
},
|
|
1002
1014
|
"kind": {
|
|
@@ -271,7 +271,7 @@ export declare function getCheckoutPreflightCatalog(): {
|
|
|
271
271
|
readonly requires: "reviewed_final_payment_and_continuation_evidence";
|
|
272
272
|
}, {
|
|
273
273
|
readonly id: "stripe.tokenization";
|
|
274
|
-
readonly path_pattern: "^/v1/(?:payment_methods|tokens)$";
|
|
274
|
+
readonly path_pattern: "^/v1/(?:payment_methods|tokens|sources|confirmation_tokens)$";
|
|
275
275
|
readonly effect: "token_or_method_creation";
|
|
276
276
|
readonly amount_binding: "none";
|
|
277
277
|
readonly psp: "stripe";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: 7c76900599ba4ac29a8d1ca541170591ddae58d35c78560e5d37562e0050534a
|
|
3
3
|
import { CHECKOUT_PREFLIGHT_CAPABILITIES } from './preflight-capabilities.generated.js';
|
|
4
4
|
/** Advisory page discovery. These rules never admit a payment destination. */
|
|
5
5
|
export const CHECKOUT_PREFLIGHT_VERSION = '1';
|
|
@@ -49,6 +49,16 @@ RULES.push({ id: 'tranzila-terminal-hosted-page', psp: 'tranzila', kinds: ['docu
|
|
|
49
49
|
sources: [documented('https://docs.dlocal.com/docs/set-up-smart-fields'), observed('dlocal-smart-fields-6.8.3')] }, { id: 'mercado-pago-secure-fields-frame', psp: 'mercado_pago', kinds: ['frame', 'iframe', 'document'], hostname: /^secure-fields\.mercadopago\.com$/, pathname: /^\/$/, origin: 'https://secure-fields.mercadopago.com', path_pattern: '/',
|
|
50
50
|
sources: [documented('https://sdk.mercadopago.com/js/v2'), observed('mercado-pago-secure-fields-frames')] }, { id: 'mercado-pago-checkout-pro-card-form', psp: 'mercado_pago', kinds: ['document', 'frame', 'iframe'], hostname: /^www\.mercadopago\.com\.ar$/, pathname: /^\/checkout\/v1\/payment\/redirect\/[^/]+\/card-form\/?$/, origin: 'https://www.mercadopago.com.ar', path_pattern: '/checkout/v1/payment/redirect/:preference/card-form/',
|
|
51
51
|
sources: [documented('https://www.mercadopago.com.ar/developers/es/docs/checkout-pro/landing'), observed('mercado-pago-checkout-pro-guest-card-form')] });
|
|
52
|
+
// Fiserv Commerce Hub, read 2026-09-23 from Home Depot's OrangePay frame and Fiserv's
|
|
53
|
+
// docs: payment-fields.js renders one Secure Data Capture iframe per card field, and
|
|
54
|
+
// the Checkout JS SDK is Commerce Hub's other browser entry. Fiserv ships dark (its
|
|
55
|
+
// definition names a capability), so the bundled catalog declares no support for it:
|
|
56
|
+
// these assets identify fiserv and the result stays unknown.
|
|
57
|
+
const fiservSource = (reference) => ({ kind: 'primary_source', reference, reviewed_on: '2026-09-23' });
|
|
58
|
+
RULES.push({ id: 'fiserv-commercehub-payment-fields', psp: 'fiserv', kinds: ['script'], hostname: /^commercehub-checkout\.fiservapps\.com$/, pathname: /^\/sdk\/[0-9]+\.[0-9]+\.[0-9]+\/payment-fields\.js$/, origin: 'https://commercehub-checkout.fiservapps.com', path_pattern: '/sdk/:version/payment-fields.js',
|
|
59
|
+
sources: [fiservSource('https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js'), fiservSource('https://orangepay-ecommerce.hdpayments.homedepot.com/env.js')] }, { id: 'fiserv-commercehub-checkout-script', psp: 'fiserv', kinds: ['script'], hostname: /^commercehub-checkout\.fiservapps\.com$/, pathname: /^\/sdk\/[0-9]+\.[0-9]+\.[0-9]+\/checkout\.js$/, origin: 'https://commercehub-checkout.fiservapps.com', path_pattern: '/sdk/:version/checkout.js',
|
|
60
|
+
sources: [fiservSource('https://developer.fiserv.com/product/CommerceHub/docs/Online-Commerce/Integration-Options/Hosted-Checkout/Hosted-Checkout-Version-Release')] }, { id: 'fiserv-secure-data-capture-frame', psp: 'fiserv', kinds: ['frame', 'iframe', 'document'], hostname: /^commercehub-secure-data-capture\.fiservapps\.com$/, pathname: /^\/sdk-secure\/[0-9]+\.[0-9]+\.[0-9]+\/iframe\.html$/, origin: 'https://commercehub-secure-data-capture.fiservapps.com', path_pattern: '/sdk-secure/:version/iframe.html',
|
|
61
|
+
sources: [fiservSource('https://commercehub-checkout.fiservapps.com/sdk/3.8.14/payment-fields.js'), fiservSource('https://commercehub-secure-data-capture.fiservapps.com/sdk-secure/3.8.14/iframe.html')] });
|
|
52
62
|
// Keep sandbox evidence truthful instead of labelling it with a production origin.
|
|
53
63
|
for (const [baseId, suffix, hostname, origin] of [
|
|
54
64
|
['square-web-sdk', 'sandbox', /^sandbox\.web\.squarecdn\.com$/, 'https://sandbox.web.squarecdn.com'],
|
|
@@ -66,6 +76,10 @@ for (const [baseId, suffix, hostname, origin] of [
|
|
|
66
76
|
['adyen-secured-fields-live', 'apse', /^checkoutshopper-live-apse\.adyen\.com$/, 'https://checkoutshopper-live-apse.adyen.com'],
|
|
67
77
|
['adyen-secured-fields-live', 'in', /^checkoutshopper-live-in\.adyen\.com$/, 'https://checkoutshopper-live-in.adyen.com'],
|
|
68
78
|
['adyen-secured-fields-live', 'nea', /^checkoutshopper-live-nea\.adyen\.com$/, 'https://checkoutshopper-live-nea.adyen.com'],
|
|
79
|
+
// Commerce Hub's "Sandbox and Cert" environment (payment-fields.js 3.8.14 environment table).
|
|
80
|
+
['fiserv-commercehub-payment-fields', 'cert', /^commercehub-checkout-cert\.fiservapps\.com$/, 'https://commercehub-checkout-cert.fiservapps.com'],
|
|
81
|
+
['fiserv-commercehub-checkout-script', 'cert', /^commercehub-checkout-cert\.fiservapps\.com$/, 'https://commercehub-checkout-cert.fiservapps.com'],
|
|
82
|
+
['fiserv-secure-data-capture-frame', 'cert', /^commercehub-secure-data-capture-cert\.fiservapps\.com$/, 'https://commercehub-secure-data-capture-cert.fiservapps.com'],
|
|
69
83
|
]) {
|
|
70
84
|
RULES.push({ ...RULES.find(rule => rule.id === baseId), id: `${baseId}-${suffix}`, hostname, origin });
|
|
71
85
|
}
|
package/dist/preparation.d.ts
CHANGED
|
@@ -19,7 +19,20 @@ export declare class PreparationGate {
|
|
|
19
19
|
assertDocument(): Promise<void>;
|
|
20
20
|
private readDocument;
|
|
21
21
|
isEngaged(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* A card request that only a prepared checkout may pay (an Adyen merchant
|
|
24
|
+
* profile's, or a Fiserv card capture, pre-claim.ts) arrived with no
|
|
25
|
+
* preparation and was refused. Like claim() on an unused gate, a later
|
|
26
|
+
* prepare() is then refused too.
|
|
27
|
+
*/
|
|
28
|
+
observeUnprepared(): void;
|
|
22
29
|
retireUnboundClaim(): void;
|
|
23
30
|
/** A bound native request has its own cancellation/unknown-outcome machinery. */
|
|
24
31
|
invalidate(reason: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Retire the server handle first, then report the form outcome. Neither
|
|
34
|
+
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
35
|
+
* stalled cancel cannot lose the report.
|
|
36
|
+
*/
|
|
37
|
+
private cancelThenObserve;
|
|
25
38
|
}
|
package/dist/preparation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CheckoutPreparationError } from './client.js';
|
|
2
2
|
import { validAmountInput } from './client.js';
|
|
3
|
-
import {
|
|
3
|
+
import { matchesPreparation, validPreparationEnvironment, preparationEndpoint } from './prepared-processor.js';
|
|
4
4
|
/** A local, one-use rendezvous. It never starts or retries a merchant request. */
|
|
5
5
|
export class PreparationGate {
|
|
6
6
|
opts;
|
|
@@ -39,9 +39,25 @@ export class PreparationGate {
|
|
|
39
39
|
try {
|
|
40
40
|
if (!options || !validPreparationEnvironment(options.psp, options.environment))
|
|
41
41
|
throw new CheckoutPreparationError(null, 'unsupported_processor');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// A Fiserv checkout's merchantProfile names a key pin, which prepareCheckout checks: its
|
|
43
|
+
// capture endpoint is checked below like any processor-hosted checkout's.
|
|
44
|
+
if (options.merchantProfile !== undefined && options.psp !== 'fiserv') {
|
|
45
|
+
// An Adyen merchant's own endpoint: a profile this build reviewed, which
|
|
46
|
+
// this attachment pauses, and one Agentcard turned on for this client.
|
|
47
|
+
const profile = typeof this.opts.vault.merchantProfile === 'function' && typeof options.merchantProfile === 'string'
|
|
48
|
+
? this.opts.vault.merchantProfile(options.merchantProfile) : null;
|
|
49
|
+
if (options.psp !== 'adyen')
|
|
50
|
+
throw new CheckoutPreparationError(null, 'unsupported_processor');
|
|
51
|
+
if (!profile)
|
|
52
|
+
throw new CheckoutPreparationError(null, 'processor_interception_unavailable');
|
|
53
|
+
if (profile.status !== 'enabled')
|
|
54
|
+
throw new CheckoutPreparationError(null, 'unsupported_checkout');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const tokenizer = preparationEndpoint(options.psp, options.environment);
|
|
58
|
+
if (!this.opts.vault.isCardRequest(tokenizer, 'POST'))
|
|
59
|
+
throw new CheckoutPreparationError(null, 'processor_interception_unavailable');
|
|
60
|
+
}
|
|
45
61
|
if (!validAmountInput(this.opts.amount) || this.opts.amount === 0 || !/^[a-z]{3}$/i.test(this.opts.currency ?? ''))
|
|
46
62
|
throw new CheckoutPreparationError(null, 'amount_required');
|
|
47
63
|
if (signal.aborted)
|
|
@@ -66,10 +82,12 @@ export class PreparationGate {
|
|
|
66
82
|
},
|
|
67
83
|
});
|
|
68
84
|
this.prepared = prepared;
|
|
69
|
-
|
|
85
|
+
// A Fiserv preparation also names the key pin the cardholder approves (merchantProfile).
|
|
86
|
+
if (prepared.psp !== options.psp || prepared.environment !== options.environment
|
|
87
|
+
|| prepared.merchantProfile !== options.merchantProfile)
|
|
70
88
|
throw new CheckoutPreparationError(prepared.id, 'checkout_changed');
|
|
71
89
|
if (signal.aborted || this.state !== 'preparing') {
|
|
72
|
-
void this.
|
|
90
|
+
void this.cancelThenObserve(prepared.id, 'cancelled');
|
|
73
91
|
throw new CheckoutPreparationError(prepared.id, 'cancelled');
|
|
74
92
|
}
|
|
75
93
|
await this.assertDocument();
|
|
@@ -101,7 +119,9 @@ export class PreparationGate {
|
|
|
101
119
|
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'already_used_or_unavailable');
|
|
102
120
|
}
|
|
103
121
|
const prepared = this.prepared;
|
|
104
|
-
|
|
122
|
+
// A merchant-hosted preparation claims only its profile's reviewed card request.
|
|
123
|
+
// A Fiserv preparation claims only a capture under the key pin it names.
|
|
124
|
+
if (Date.parse(prepared.expiresAt) <= Date.now() || !matchesPreparation(prepared, requestUrl, 'POST', requestBody, requestHeaders)) {
|
|
105
125
|
const reason = Date.parse(prepared.expiresAt) <= Date.now() ? 'expired' : 'checkout_changed';
|
|
106
126
|
this.invalidate(reason);
|
|
107
127
|
throw new CheckoutPreparationError(prepared.id, reason);
|
|
@@ -132,10 +152,17 @@ export class PreparationGate {
|
|
|
132
152
|
}
|
|
133
153
|
}
|
|
134
154
|
isEngaged() { return this.state !== 'unused'; }
|
|
155
|
+
/**
|
|
156
|
+
* A card request that only a prepared checkout may pay (an Adyen merchant
|
|
157
|
+
* profile's, or a Fiserv card capture, pre-claim.ts) arrived with no
|
|
158
|
+
* preparation and was refused. Like claim() on an unused gate, a later
|
|
159
|
+
* prepare() is then refused too.
|
|
160
|
+
*/
|
|
161
|
+
observeUnprepared() { this.observedRequest = true; }
|
|
135
162
|
retireUnboundClaim() {
|
|
136
163
|
if (this.state !== 'consumed' || !this.prepared || this.lifecycle.getState().authorizationId)
|
|
137
164
|
return;
|
|
138
|
-
void this.
|
|
165
|
+
void this.cancelThenObserve(this.prepared.id, 'request_not_bound');
|
|
139
166
|
// No bound ID means the adapter must not leave a spent handle appearing ready.
|
|
140
167
|
if (this.lifecycle.getState().status === 'ready_to_submit')
|
|
141
168
|
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared.id, 'request_not_bound'));
|
|
@@ -148,7 +175,17 @@ export class PreparationGate {
|
|
|
148
175
|
clearTimeout(this.expiryTimer);
|
|
149
176
|
this.stop.abort();
|
|
150
177
|
if (this.prepared)
|
|
151
|
-
void this.
|
|
178
|
+
void this.cancelThenObserve(this.prepared.id, reason);
|
|
152
179
|
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared?.id ?? null, reason));
|
|
153
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Retire the server handle first, then report the form outcome. Neither
|
|
183
|
+
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
184
|
+
* stalled cancel cannot lose the report.
|
|
185
|
+
*/
|
|
186
|
+
async cancelThenObserve(id, reason) {
|
|
187
|
+
const cancellation = this.opts.vault.cancelPreparation(id).catch(() => { });
|
|
188
|
+
const observation = this.opts.vault.observePreparation?.(id, this.observedRequest ? 'presented_not_filled' : 'not_presented', reason);
|
|
189
|
+
await Promise.all([cancellation, observation?.catch(() => { })]);
|
|
190
|
+
}
|
|
154
191
|
}
|
|
@@ -1,10 +1,43 @@
|
|
|
1
|
-
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe';
|
|
1
|
+
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv';
|
|
2
2
|
export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* How the approved card reaches the processor: the device's own request (token), or
|
|
5
|
+
* ciphertext the device produces for this browser to send (cse: Adyen's four encrypted
|
|
6
|
+
* fields, or Fiserv's Secure Data Capture envelope under a key Agentcard pinned).
|
|
7
|
+
*/
|
|
4
8
|
export type PreparationMode = 'token' | 'cse';
|
|
5
9
|
export declare function preparationMode(psp: string): PreparationMode;
|
|
6
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
|
|
12
|
+
* Adyen can: test and live are separate hosts and key prefixes. Fiserv's are separate
|
|
13
|
+
* hosts too: connect (production) and connect-cert (sandbox).
|
|
14
|
+
*/
|
|
7
15
|
export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
|
|
8
16
|
export declare function preparationEndpoint(psp: PreparationProcessor, environment: PreparationEnvironment): string;
|
|
9
17
|
/** Processor identity and environment are part of the device's prior consent. */
|
|
10
18
|
export declare function matchesPreparedRequest(psp: PreparationProcessor, environment: PreparationEnvironment, requestUrl: string, method: string, body?: string | null, headers?: Record<string, string>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The one request a merchant-hosted preparation pays: the reviewed profile's
|
|
21
|
+
* card request itself (classifyMerchantRequest pauses it), at the profile's exact
|
|
22
|
+
* endpoint, as its method, with a body the profile reviewed. A sibling endpoint, a
|
|
23
|
+
* stored card, a store-the-card body or any other shape never consumes consent.
|
|
24
|
+
*/
|
|
25
|
+
export declare function matchesPreparedMerchantRequest(profileId: string, requestUrl: string, method: string, body?: string | null, sandboxDeclaration?: {
|
|
26
|
+
endpoint: string;
|
|
27
|
+
}): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* matchesPreparedRequest for a preparation, through its merchant profile (at its declared
|
|
30
|
+
* endpoint, when it names one) when it names one. A Fiserv preparation also names the key its
|
|
31
|
+
* cardholder approved a payment under (merchantProfile, a pin id): the capture's own pin,
|
|
32
|
+
* the one its envelope's key is pinned as for its endpoint and merchant, must be that
|
|
33
|
+
* pin, as the API's bind requires. Two pins in one environment (a merchant's old and new
|
|
34
|
+
* keys during a rotation) never spend each other's approval. No other processor names one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function matchesPreparation(preparation: {
|
|
37
|
+
psp: PreparationProcessor;
|
|
38
|
+
environment: PreparationEnvironment;
|
|
39
|
+
merchantProfile?: string;
|
|
40
|
+
sandboxDeclaration?: {
|
|
41
|
+
endpoint: string;
|
|
42
|
+
};
|
|
43
|
+
}, requestUrl: string, method: string, body?: string | null, headers?: Record<string, string>): boolean;
|
|
@@ -4,20 +4,28 @@ import { braintreeEnvironment, isPreparedBraintreeRequest, readTokenizationJson
|
|
|
4
4
|
import { isPreparedRecurlyRequest } from './recurly.generated.js';
|
|
5
5
|
import { isPreparedSpreedlyRequest, isSpreedlyTokenRequest, SPREEDLY_TOKEN_ENDPOINT } from './spreedly.generated.js';
|
|
6
6
|
import { checkoutComPreparationUrl, isPreparedCheckoutComRequest } from './checkout-com.generated.js';
|
|
7
|
+
import { classifyDeclaredMerchantRequest, classifyMerchantRequest } from './merchant-hosted.js';
|
|
8
|
+
import { fiservCapturePinId, fiservPreparationUrl, isPreparedFiservRequest } from './fiserv.js';
|
|
7
9
|
export function preparationMode(psp) {
|
|
8
|
-
return psp === 'adyen' ? 'cse' : 'token';
|
|
10
|
+
return psp === 'adyen' || psp === 'fiserv' ? 'cse' : 'token';
|
|
9
11
|
}
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
|
|
14
|
+
* Adyen can: test and live are separate hosts and key prefixes. Fiserv's are separate
|
|
15
|
+
* hosts too: connect (production) and connect-cert (sandbox).
|
|
16
|
+
*/
|
|
11
17
|
export function validPreparationEnvironment(psp, environment) {
|
|
12
18
|
if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
|
|
13
19
|
return environment === 'shared';
|
|
14
|
-
return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
20
|
+
return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
15
21
|
}
|
|
16
22
|
export function preparationEndpoint(psp, environment) {
|
|
17
23
|
if (!validPreparationEnvironment(psp, environment))
|
|
18
24
|
throw new Error('unsupported_preparation_processor');
|
|
19
25
|
if (psp === 'paysafe')
|
|
20
26
|
return paysafePreparationUrl(environment);
|
|
27
|
+
if (psp === 'fiserv')
|
|
28
|
+
return fiservPreparationUrl(environment);
|
|
21
29
|
if (psp === 'checkout_com')
|
|
22
30
|
return checkoutComPreparationUrl(environment);
|
|
23
31
|
if (psp === 'adyen')
|
|
@@ -84,6 +92,11 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
|
|
|
84
92
|
return false;
|
|
85
93
|
if (psp === 'paysafe')
|
|
86
94
|
return isPreparedPaysafeRequest(requestUrl, method, body ?? null, environment, headers);
|
|
95
|
+
// Fiserv: that environment's card-capture endpoint exactly, the capture rules on the whole
|
|
96
|
+
// request (missing headers refuse), and an envelope under a key pinned for that environment
|
|
97
|
+
// and for the merchant the body names (payment-core isPreparedFiservRequest).
|
|
98
|
+
if (psp === 'fiserv')
|
|
99
|
+
return isPreparedFiservRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers);
|
|
87
100
|
if (psp === 'checkout_com')
|
|
88
101
|
return isPreparedCheckoutComRequest(requestUrl, method, body ?? null, environment, headers);
|
|
89
102
|
if (psp === 'adyen')
|
|
@@ -120,3 +133,40 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
|
|
|
120
133
|
return false;
|
|
121
134
|
}
|
|
122
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* The one request a merchant-hosted preparation pays: the reviewed profile's
|
|
138
|
+
* card request itself (classifyMerchantRequest pauses it), at the profile's exact
|
|
139
|
+
* endpoint, as its method, with a body the profile reviewed. A sibling endpoint, a
|
|
140
|
+
* stored card, a store-the-card body or any other shape never consumes consent.
|
|
141
|
+
*/
|
|
142
|
+
export function matchesPreparedMerchantRequest(profileId, requestUrl, method, body, sandboxDeclaration) {
|
|
143
|
+
try {
|
|
144
|
+
const verdict = sandboxDeclaration
|
|
145
|
+
? classifyDeclaredMerchantRequest(profileId, sandboxDeclaration.endpoint, requestUrl, method, body ?? '')
|
|
146
|
+
: classifyMerchantRequest(profileId, requestUrl, method, body ?? '');
|
|
147
|
+
return verdict.verdict === 'pause';
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* matchesPreparedRequest for a preparation, through its merchant profile (at its declared
|
|
155
|
+
* endpoint, when it names one) when it names one. A Fiserv preparation also names the key its
|
|
156
|
+
* cardholder approved a payment under (merchantProfile, a pin id): the capture's own pin,
|
|
157
|
+
* the one its envelope's key is pinned as for its endpoint and merchant, must be that
|
|
158
|
+
* pin, as the API's bind requires. Two pins in one environment (a merchant's old and new
|
|
159
|
+
* keys during a rotation) never spend each other's approval. No other processor names one.
|
|
160
|
+
*/
|
|
161
|
+
export function matchesPreparation(preparation, requestUrl, method, body, headers) {
|
|
162
|
+
if (preparation.psp === 'fiserv') {
|
|
163
|
+
if (!matchesPreparedRequest(preparation.psp, preparation.environment, requestUrl, method, body, headers))
|
|
164
|
+
return false;
|
|
165
|
+
return typeof preparation.merchantProfile === 'string' && fiservCapturePinId(requestUrl, body ?? null) === preparation.merchantProfile;
|
|
166
|
+
}
|
|
167
|
+
if (preparation.merchantProfile !== undefined) {
|
|
168
|
+
return preparation.psp === 'adyen'
|
|
169
|
+
&& matchesPreparedMerchantRequest(preparation.merchantProfile, requestUrl, method, body, preparation.sandboxDeclaration);
|
|
170
|
+
}
|
|
171
|
+
return matchesPreparedRequest(preparation.psp, preparation.environment, requestUrl, method, body, headers);
|
|
172
|
+
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -40,6 +40,21 @@ export interface Recognizer {
|
|
|
40
40
|
* the call (signatures, client tokens). Matched case-insensitively.
|
|
41
41
|
*/
|
|
42
42
|
passthroughHeaders: RegExp[];
|
|
43
|
+
/**
|
|
44
|
+
* Form keys that carry the card number. When present, a request on this
|
|
45
|
+
* recognizer's endpoints is a card request only if its body carries one of
|
|
46
|
+
* them: a Stripe confirmation that pays with a method the page already
|
|
47
|
+
* created (`payment_method=pm_...`) carries none, so there is nothing for
|
|
48
|
+
* the cardholder to approve. Served by the API and carried through
|
|
49
|
+
* syncRegistry() verbatim.
|
|
50
|
+
*/
|
|
51
|
+
cardFields?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* With cardFields: the endpoints (regex source over hostname + pathname,
|
|
54
|
+
* anchored) where a request without a card is routine and never ours, so
|
|
55
|
+
* it continues untouched; a request without a card anywhere else is refused.
|
|
56
|
+
*/
|
|
57
|
+
passWithoutCard?: string;
|
|
43
58
|
/**
|
|
44
59
|
* True when the card is encrypted inside the page before the request leaves.
|
|
45
60
|
* A digit swap is useless here; the vault must re-run the PSP's client-side
|
|
@@ -49,10 +64,55 @@ export interface Recognizer {
|
|
|
49
64
|
clientSideEncrypted?: boolean;
|
|
50
65
|
/** How the card reaches the processor; absent means 'token'. Carried verbatim by syncRegistry. */
|
|
51
66
|
mode?: CheckoutMode;
|
|
67
|
+
/**
|
|
68
|
+
* Dark launch: a capability name the API attaches to an entry it serves only
|
|
69
|
+
* to a client that declared it (?capabilities=). Absent on every entry in
|
|
70
|
+
* BUILTIN_REGISTRY, because a gated entry is left out of the built-in
|
|
71
|
+
* fallback. Carried verbatim by syncRegistry for a client that does declare
|
|
72
|
+
* it, so the SDK never has to act on it.
|
|
73
|
+
*/
|
|
74
|
+
capability?: string;
|
|
75
|
+
/**
|
|
76
|
+
* True when this processor cannot be taken over on an ordinary authorization:
|
|
77
|
+
* the cardholder must have approved a prepare() first. authorize() refuses
|
|
78
|
+
* such a request locally, before it prompts, when no preparation is active
|
|
79
|
+
* (PreparationRequiredError), and the API answers 409 preparation_required.
|
|
80
|
+
* Absent on every BUILTIN_REGISTRY entry today and carried verbatim by
|
|
81
|
+
* syncRegistry for a build that reaches a processor which sets it.
|
|
82
|
+
*/
|
|
83
|
+
preparationRequired?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Anchored regex SOURCES (a subset of `hosts`) marking the vendor's
|
|
86
|
+
* SANDBOX/TEST hostnames, so the backend and the device can tell a test host
|
|
87
|
+
* from a live one. Carried verbatim by syncRegistry; the SDK never matches
|
|
88
|
+
* with them (recognition still uses `hosts`), so it acts on neither this nor
|
|
89
|
+
* the pin below. Present in BUILTIN_REGISTRY for the host-separated processors.
|
|
90
|
+
*/
|
|
91
|
+
sandboxHosts?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* True when a processor's host environment is enforced: the API refuses a
|
|
94
|
+
* create whose host environment disagrees with the row's test/live nature, and
|
|
95
|
+
* the device sends only documented test cards to a sandbox host. Absent on
|
|
96
|
+
* every entry today; carried verbatim by syncRegistry, and the SDK never acts
|
|
97
|
+
* on it.
|
|
98
|
+
*/
|
|
99
|
+
hostEnvironmentPinned?: boolean;
|
|
52
100
|
}
|
|
53
101
|
import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
|
|
54
102
|
export { BUILTIN_REGISTRY };
|
|
55
103
|
export declare function findRecognizer(url: string, registry?: Recognizer[]): Recognizer | null;
|
|
104
|
+
/**
|
|
105
|
+
* True when this processor cannot be taken over on an ordinary authorization:
|
|
106
|
+
* the cardholder must have approved a prepare() first (the `preparationRequired`
|
|
107
|
+
* field above). authorize() applies this before it prompts and refuses with
|
|
108
|
+
* PreparationRequiredError when no preparation is active; the API answers 409
|
|
109
|
+
* preparation_required for the same case. This is the SAME rule payment-core's
|
|
110
|
+
* recognizerRequiresPreparation applies on the server, a strict `=== true`, so a
|
|
111
|
+
* value that rode in over the wire (syncRegistry spreads the served JSON) cannot
|
|
112
|
+
* turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry, so
|
|
113
|
+
* this is false for all 24 today.
|
|
114
|
+
*/
|
|
115
|
+
export declare function recognizerRequiresPreparation(rec: Pick<Recognizer, 'preparationRequired'>): boolean;
|
|
56
116
|
/**
|
|
57
117
|
* Glob url patterns covering every host in `registry` — what to hand
|
|
58
118
|
* `Fetch.enable` so a card request is ever paused. Deliberately a superset of
|
package/dist/registry.js
CHANGED
|
@@ -46,6 +46,20 @@ export function findRecognizer(url, registry = BUILTIN_REGISTRY) {
|
|
|
46
46
|
return new RegExp(`^(?:${r.match.source})$`, 'i').test(hostPath);
|
|
47
47
|
}) ?? null);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* True when this processor cannot be taken over on an ordinary authorization:
|
|
51
|
+
* the cardholder must have approved a prepare() first (the `preparationRequired`
|
|
52
|
+
* field above). authorize() applies this before it prompts and refuses with
|
|
53
|
+
* PreparationRequiredError when no preparation is active; the API answers 409
|
|
54
|
+
* preparation_required for the same case. This is the SAME rule payment-core's
|
|
55
|
+
* recognizerRequiresPreparation applies on the server, a strict `=== true`, so a
|
|
56
|
+
* value that rode in over the wire (syncRegistry spreads the served JSON) cannot
|
|
57
|
+
* turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry, so
|
|
58
|
+
* this is false for all 24 today.
|
|
59
|
+
*/
|
|
60
|
+
export function recognizerRequiresPreparation(rec) {
|
|
61
|
+
return rec.preparationRequired === true;
|
|
62
|
+
}
|
|
49
63
|
// ---------------------------------------------------------------------------
|
|
50
64
|
// Registry -> CDP url patterns
|
|
51
65
|
//
|