@agent-cards/checkout 0.18.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 +8 -0
- package/PREFLIGHT.md +4 -0
- package/README.md +91 -7
- 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/cdp.d.ts +4 -1
- package/dist/cdp.js +416 -217
- package/dist/client.d.ts +236 -5
- package/dist/client.js +514 -11
- 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 +15 -1
- package/dist/lifecycle.js +28 -3
- 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-catalog.json +132 -0
- package/dist/preflight-schemas.json +14 -2
- package/dist/preflight.generated.js +15 -1
- package/dist/preparation.d.ts +7 -0
- package/dist/preparation.js +33 -6
- package/dist/prepared-processor.d.ts +36 -3
- package/dist/prepared-processor.js +53 -3
- package/dist/registry.d.ts +45 -0
- package/dist/registry.js +14 -0
- package/dist/stripe-checkout.generated.js +96 -7
- 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
|
@@ -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": {
|
|
@@ -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,6 +19,13 @@ 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;
|
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,7 +82,9 @@ 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
90
|
void this.cancelThenObserve(prepared.id, 'cancelled');
|
|
@@ -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,6 +152,13 @@ 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;
|
|
@@ -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
|
@@ -64,10 +64,55 @@ export interface Recognizer {
|
|
|
64
64
|
clientSideEncrypted?: boolean;
|
|
65
65
|
/** How the card reaches the processor; absent means 'token'. Carried verbatim by syncRegistry. */
|
|
66
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;
|
|
67
100
|
}
|
|
68
101
|
import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
|
|
69
102
|
export { BUILTIN_REGISTRY };
|
|
70
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;
|
|
71
116
|
/**
|
|
72
117
|
* Glob url patterns covering every host in `registry` — what to hand
|
|
73
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
|
//
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
4
|
-
// src/
|
|
3
|
+
// artifact-sha256: 9edf78da59b3bdfe310b4422e06d333cd164497cdb4070db59ffed348492198a
|
|
4
|
+
// src/deep-freeze.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
7
7
|
for (const child of Object.values(value))
|
|
@@ -10,6 +10,7 @@ function deepFreeze(value) {
|
|
|
10
10
|
}
|
|
11
11
|
return value;
|
|
12
12
|
}
|
|
13
|
+
// src/definitions.js
|
|
13
14
|
var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
14
15
|
{
|
|
15
16
|
psp: "spreedly",
|
|
@@ -69,6 +70,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
69
70
|
psp: "braintree",
|
|
70
71
|
match: "payments(\\.sandbox)?\\.braintree-api\\.com/graphql",
|
|
71
72
|
hosts: ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"],
|
|
73
|
+
sandboxHosts: ["^payments\\.sandbox\\.braintree-api\\.com$"],
|
|
72
74
|
encoding: "json",
|
|
73
75
|
passthroughHeaders: ["^authorization$", "^braintree-version$"]
|
|
74
76
|
},
|
|
@@ -86,6 +88,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
86
88
|
"^card-acquisition-gateway\\.checkout\\.com$",
|
|
87
89
|
"^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"
|
|
88
90
|
],
|
|
91
|
+
sandboxHosts: ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"],
|
|
89
92
|
encoding: "json",
|
|
90
93
|
passthroughHeaders: ["^authorization$"]
|
|
91
94
|
},
|
|
@@ -114,6 +117,15 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
114
117
|
// merchants on them were silently never paused.
|
|
115
118
|
match: "(checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com|([a-z0-9-]+\\.)*adyenpayments\\.com)/checkoutshopper/v1/sessions/[A-Za-z0-9_-]+/payments",
|
|
116
119
|
hosts: ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"],
|
|
120
|
+
// The sandbox marker matches ONLY the TEST hostname, a subset of the first
|
|
121
|
+
// host source (which matches checkoutshopper-test and every checkoutshopper-live
|
|
122
|
+
// regional host). checkoutshopper-live* and *.adyenpayments.com are live.
|
|
123
|
+
// Load-bearing for Adyen although Adyen does not pin its environment: the
|
|
124
|
+
// Adyen TEST platform rule (backend.js adyenLivePlatform) reads this marker
|
|
125
|
+
// on the server's authorization create and cardholder legs
|
|
126
|
+
// (services/checkout-cse-adyen.ts in the backend), on the device and in the
|
|
127
|
+
// enclave. Dropping or widening it moves which payments count as Adyen TEST.
|
|
128
|
+
sandboxHosts: ["^checkoutshopper-test\\.adyen\\.com$"],
|
|
117
129
|
encoding: "json",
|
|
118
130
|
passthroughHeaders: [],
|
|
119
131
|
clientSideEncrypted: true,
|
|
@@ -183,6 +195,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
183
195
|
// PAN@ card_data.number. POST /v2/card-nonce (URL always carries ?_=&version=).
|
|
184
196
|
match: "pci-connect\\.squareup(?:sandbox)?\\.com/v2/card-nonce",
|
|
185
197
|
hosts: ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"],
|
|
198
|
+
sandboxHosts: ["^pci-connect\\.squareupsandbox\\.com$"],
|
|
186
199
|
encoding: "json",
|
|
187
200
|
passthroughHeaders: []
|
|
188
201
|
// auth = client_id in body; no auth header
|
|
@@ -203,6 +216,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
203
216
|
// request types on the same URL — the body, not the URL, is what distinguishes them.
|
|
204
217
|
match: "(api2|apitest)\\.authorize\\.net/xml/v1/request\\.api",
|
|
205
218
|
hosts: ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"],
|
|
219
|
+
sandboxHosts: ["^apitest\\.authorize\\.net$"],
|
|
206
220
|
encoding: "json",
|
|
207
221
|
// JSON body; Content-Type is sometimes text/plain (preflight dodge), replayed as-is
|
|
208
222
|
passthroughHeaders: []
|
|
@@ -213,6 +227,8 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
213
227
|
// the two paths need DIFFERENT vendor media types; `application/json` → HTTP 406.
|
|
214
228
|
match: "(try\\.)?access\\.worldpay\\.com/(sessions/card|verifiedTokens/sessions)",
|
|
215
229
|
hosts: ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"],
|
|
230
|
+
sandboxHosts: ["^try\\.access\\.worldpay\\.com$"],
|
|
231
|
+
// try.* is Worldpay's try/test host
|
|
216
232
|
encoding: "json",
|
|
217
233
|
passthroughHeaders: ["^accept$"]
|
|
218
234
|
},
|
|
@@ -229,6 +245,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
229
245
|
// with no branch back to the covered path.
|
|
230
246
|
match: "(secure|ppp-test)\\.safecharge\\.com/ppp/api/v1/(?:cardTokenization|clientPayment|websdk/initPaymentWithCardTokenization)\\.do",
|
|
231
247
|
hosts: ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"],
|
|
248
|
+
sandboxHosts: ["^ppp-test\\.safecharge\\.com$"],
|
|
232
249
|
encoding: "json",
|
|
233
250
|
passthroughHeaders: []
|
|
234
251
|
},
|
|
@@ -263,6 +280,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
263
280
|
"^pci-api\\.airwallex\\.com$",
|
|
264
281
|
"^pci-api\\.sandbox\\.airwallex\\.com$"
|
|
265
282
|
],
|
|
283
|
+
sandboxHosts: ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"],
|
|
266
284
|
encoding: "json",
|
|
267
285
|
passthroughHeaders: ["^client-secret$", "^authorization$", "^x-auth-token$", "^x-on-behalf-of$", "^x-api-version$"]
|
|
268
286
|
},
|
|
@@ -276,6 +294,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
276
294
|
// Checkout Page (signature-authed, server-to-server, no card) — the POST gate is what splits them.
|
|
277
295
|
match: "(api|sandboxapi)\\.rapyd\\.net/v1/(?:hosted/collect/card/[^/?#]+/payment_method|checkout/checkout_[A-Za-z0-9]+)(?:[?#]|$)",
|
|
278
296
|
hosts: ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"],
|
|
297
|
+
sandboxHosts: ["^sandboxapi\\.rapyd\\.net$"],
|
|
279
298
|
encoding: "json",
|
|
280
299
|
passthroughHeaders: []
|
|
281
300
|
// auth rides the opaque hosted-page / checkout token in the URL
|
|
@@ -287,6 +306,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
287
306
|
// multi-tenant surface: `ppmcc` is dlocal-operated, not merchant-registrable.
|
|
288
307
|
match: "ppmcc(-sandbox)?\\.dlocal\\.com/cvault/credit-card/temporal",
|
|
289
308
|
hosts: ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"],
|
|
309
|
+
sandboxHosts: ["^ppmcc-sandbox\\.dlocal\\.com$"],
|
|
290
310
|
encoding: "json",
|
|
291
311
|
passthroughHeaders: ["^x-fields-api-key$", "^x-uow$", "^x-dlocal-infrav2$"]
|
|
292
312
|
},
|
|
@@ -318,6 +338,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
318
338
|
"^api\\.ebanxpay\\.com$",
|
|
319
339
|
"^sandbox\\.ebanxpay\\.com$"
|
|
320
340
|
],
|
|
341
|
+
sandboxHosts: ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"],
|
|
321
342
|
encoding: "json",
|
|
322
343
|
passthroughHeaders: []
|
|
323
344
|
},
|
|
@@ -342,6 +363,8 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
342
363
|
// GPO Secure Forms. PAN@ card.number.
|
|
343
364
|
match: "(secure\\.payu\\.com|(merch-prod|secure)\\.snd\\.payu\\.com)/api/front/tokens",
|
|
344
365
|
hosts: ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"],
|
|
366
|
+
sandboxHosts: ["^(merch-prod|secure)\\.snd\\.payu\\.com$"],
|
|
367
|
+
// .snd. is PayU's sandbox
|
|
345
368
|
encoding: "json",
|
|
346
369
|
// Native checkout attaches a bearer to tokenization; preserve it on replay.
|
|
347
370
|
passthroughHeaders: ["^authorization$"]
|
|
@@ -403,6 +426,7 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
403
426
|
// card/tokenize uses a saved paymentToken, and 3DS-specific paths are unverified.
|
|
404
427
|
match: "(?:api(\\.test)?\\.paysafe\\.com/(?:paymenthub/v1/singleusepaymenthandles|js/api/v1/tokenize)|hosted(\\.test)?\\.paysafe\\.com/checkout/api/v1/tokenize)(?![\\w/-])",
|
|
405
428
|
hosts: ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
429
|
+
sandboxHosts: ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
406
430
|
encoding: "json",
|
|
407
431
|
passthroughHeaders: ["^authorization$", "^x-paysafe-credentials$", "^correlationid$"]
|
|
408
432
|
},
|
|
@@ -446,6 +470,8 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
446
470
|
"^gatewayt\\.moneris\\.com$",
|
|
447
471
|
"^gatewaydev\\.moneris\\.com$"
|
|
448
472
|
],
|
|
473
|
+
// Live: www3, gateway. Test/QA/dev: esqa, gatewayqa, gatewayt, gatewaydev.
|
|
474
|
+
sandboxHosts: ["^esqa\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"],
|
|
449
475
|
encoding: "form",
|
|
450
476
|
passthroughHeaders: []
|
|
451
477
|
},
|
|
@@ -486,8 +512,66 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
486
512
|
"^apis\\.eu\\.globalpay\\.com$",
|
|
487
513
|
"^apis\\.sandbox\\.eu\\.globalpay\\.com$"
|
|
488
514
|
],
|
|
515
|
+
// Heartland/Portico cert.* is the certification (sandbox) host; GP-API sandbox is apis.sandbox[.eu].
|
|
516
|
+
sandboxHosts: ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"],
|
|
489
517
|
encoding: "json",
|
|
490
518
|
passthroughHeaders: ["^authorization$", "^x-gp-version$"]
|
|
519
|
+
},
|
|
520
|
+
// ── Encrypted on the device under a pinned key ────────────────────────────
|
|
521
|
+
{
|
|
522
|
+
psp: "fiserv",
|
|
523
|
+
// Fiserv Commerce Hub card capture from the Secure Data Capture (SDC) frame,
|
|
524
|
+
// the request Home Depot's OrangePay checkout sends (payment-fields.js and
|
|
525
|
+
// sdk-secure 3.8.14). The frame always encrypts: it seals the card with
|
|
526
|
+
// AES-256-GCM under a fresh key, wraps that key with RSA-OAEP-SHA256 under the
|
|
527
|
+
// public key the page passed it, and posts the envelope as
|
|
528
|
+
// source.encryptionData. It picks its endpoint by the page's auth type
|
|
529
|
+
// (iframe.js @85480): an AccessToken (Security Credentials) posts here, to
|
|
530
|
+
// card-capture; a Checkout Sessions JWT posts to /ch/checkouts/v1/payment-link
|
|
531
|
+
// instead. payment-link also carries the card, and it is left out on purpose:
|
|
532
|
+
// `match` names card-capture only, so a Sessions merchant's capture is never
|
|
533
|
+
// paused and fails as it does today. Fastlane (PaymentToken), ACH
|
|
534
|
+
// (PaymentCheck), gift and EBT captures share this URL, carry no Vault card,
|
|
535
|
+
// and the SDK continues them unpaused (fiserv.js isFiservPausableBody).
|
|
536
|
+
// The Wave 0 sandbox gate (2026-09-24, connect-cert, Fiserv simulator cards)
|
|
537
|
+
// settled the mode: card-capture refuses a plaintext source.card (400
|
|
538
|
+
// GATEWAY 302 "Missing or Invalid Encryption Data") and captures the frame's
|
|
539
|
+
// own envelope with no Origin, so the device builds that envelope under a
|
|
540
|
+
// key Agentcard pinned (fiserv-keys.js), the runtime writes its four members
|
|
541
|
+
// over the paused ones, and the capture continues from the agent's browser.
|
|
542
|
+
// The AccessToken the agent can read was refused on charges and tokens at
|
|
543
|
+
// Fiserv's edge in that sandbox. Every other Commerce Hub path is refused
|
|
544
|
+
// here: charges, tokens, security/keys, payment-link, and the non-production
|
|
545
|
+
// hosts (connect-dev, -qa, -uat, -test); resolveRecognizerForUrl also takes
|
|
546
|
+
// only the two endpoints written exactly (fiserv-environment.js). The
|
|
547
|
+
// envelope carries no timestamp or session binding, so it is approved by the
|
|
548
|
+
// cardholder every time: dark until an org is allowlisted, and autopilot
|
|
549
|
+
// refuses fiserv at all three gates.
|
|
550
|
+
capability: "fiserv_card_capture",
|
|
551
|
+
// A Fiserv card is sealed only for a capture the cardholder already approved
|
|
552
|
+
// through a prepare(): the backend refuses an unprepared create (409
|
|
553
|
+
// preparation_required), the internal buy loop never mints a Fiserv round,
|
|
554
|
+
// and the SDK refuses locally before it prompts.
|
|
555
|
+
preparationRequired: true,
|
|
556
|
+
match: "connect(?:-cert)?\\.fiservapis\\.com/ch/payments-vas/v1/card-capture(?![\\w/.-])",
|
|
557
|
+
hosts: ["^connect\\.fiservapis\\.com$", "^connect-cert\\.fiservapis\\.com$"],
|
|
558
|
+
sandboxHosts: ["^connect-cert\\.fiservapis\\.com$"],
|
|
559
|
+
// A live row reaches connect only and a test row connect-cert only, and
|
|
560
|
+
// connect-cert receives only documented test cards (device-replay.js).
|
|
561
|
+
hostEnvironmentPinned: true,
|
|
562
|
+
encoding: "json",
|
|
563
|
+
// The browser continues the capture with its own headers, so only the ones
|
|
564
|
+
// the backend checks against the body and the merchant are kept:
|
|
565
|
+
// Auth-Token-Type, the merchant and terminal ids, and the embedding frame's
|
|
566
|
+
// origin the SDC names (x-integration-origin, self-asserted). The
|
|
567
|
+
// AccessToken, the API key, Client-Request-Id and Timestamp are never stored.
|
|
568
|
+
passthroughHeaders: ["^auth-token-type$", "^x-integration-merchant-id$", "^x-integration-terminal-id$", "^x-integration-origin$"],
|
|
569
|
+
clientSideEncrypted: true,
|
|
570
|
+
mode: "cse",
|
|
571
|
+
cse: {
|
|
572
|
+
at: "source.encryptionData",
|
|
573
|
+
fields: ["keyId", "encryptedKey", "encryptionBlock", "aesIv"]
|
|
574
|
+
}
|
|
491
575
|
}
|
|
492
576
|
]);
|
|
493
577
|
var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, ...definition }) => definition));
|
|
@@ -497,17 +581,22 @@ var PSP_CLIENT_TABLE = deepFreeze(PROCESSOR_DEFINITIONS.map((definition) => ({
|
|
|
497
581
|
hosts: definition.hosts.map((source) => new RegExp(source, "i")),
|
|
498
582
|
encoding: definition.encoding,
|
|
499
583
|
headers: definition.passthroughHeaders.map((source) => new RegExp(source, "i")),
|
|
584
|
+
...definition.sandboxHosts ? { sandboxHosts: definition.sandboxHosts.map((source) => new RegExp(source, "i")) } : {},
|
|
585
|
+
...definition.hostEnvironmentPinned ? { hostEnvironmentPinned: true } : {},
|
|
500
586
|
...definition.mode ? { mode: definition.mode } : {},
|
|
501
587
|
...definition.clientSideEncrypted ? { clientSideEncrypted: true } : {},
|
|
502
588
|
...definition.compose ? { compose: definition.compose } : {},
|
|
503
589
|
...definition.encryptedCardFields ? { cse: { fields: definition.encryptedCardFields } } : {},
|
|
504
590
|
...definition.hostedForm ? { form: definition.hostedForm.card } : {}
|
|
505
591
|
})));
|
|
506
|
-
|
|
507
|
-
...definition
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
592
|
+
function buildBuiltinRegistry(recognizers) {
|
|
593
|
+
return recognizers.filter((definition) => !definition.capability).map(({ cse, hostedForm, ...definition }) => ({
|
|
594
|
+
...definition,
|
|
595
|
+
match: new RegExp(definition.match, "i"),
|
|
596
|
+
passthroughHeaders: definition.passthroughHeaders.map((source) => new RegExp(source, "i"))
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
var BUILTIN_REGISTRY = deepFreeze(buildBuiltinRegistry(PSP_RECOGNIZERS));
|
|
511
600
|
var CHECKOUT_MODES = deepFreeze(["token", "cse", "hosted_form"]);
|
|
512
601
|
function recognizerByPsp(psp) {
|
|
513
602
|
return typeof psp === "string" ? PSP_CLIENT_TABLE.find((entry) => entry.psp === psp) ?? null : null;
|
|
@@ -7,4 +7,5 @@ declare var SubstitutionError: {
|
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
9
|
declare function substituteEncryptedFields(body: any, sub: any): string;
|
|
10
|
-
|
|
10
|
+
declare function substituteMerchantHosted(body: any, sub: any, opts: any): string;
|
|
11
|
+
export { SubstitutionError, substituteEncryptedFields, substituteMerchantHosted };
|