@agent-cards/sdk 0.23.5 → 0.23.6
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 +2 -0
- package/README.md +22 -8
- package/dist/eprotect.d.ts +2 -2
- package/dist/eprotect.generated.d.ts +54 -15
- package/dist/eprotect.generated.js +256 -23
- package/dist/eprotect.js +5 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Pause a Worldpay eProtect registration whatever Worldpay key its page names. Worldpay rotates its production eProtect key daily (`26457000011` on 2026-10-08), and 0.23.3 refused every registration whose `publicKeyId` was not `26455500012`, the key it shipped, so an attached checkout at any eProtect merchant read `unsupported`. The SDK now takes any key id in Worldpay's digits; the cardholder's device encrypts the card under the key it reads from Worldpay's own `eProtect-api3.js` and writes that key's id into its copy, so no later rotation needs an SDK release. SmartPak Equine (paypageId `zhs8KzuRAryaq4cs` at `https://www.smartpakequine.com`) and Nordstrom (`fL33BPkRvbRqJ29S` at `https://www.nordstrom.com`) are reviewed eProtect merchants: a live checkout there passes `merchant: 'SmartPak Equine'` or `merchant: 'Nordstrom'` exactly, and `prepare()` refuses another name with `merchant_profile_mismatch` before any network call. eProtect stays dark for every org Agentcard has not enabled, and a payment also needs the matching API and Vault releases.
|
|
6
|
+
|
|
5
7
|
- Stop the card your agent typed from reaching CyberSource through Secure Acceptance's Silent Order POST. A merchant that takes the card in its own form, such as Arrow Electronics, has its server sign the order fields, then posts them with the card fields (`card_number`, `card_expiry_date`, `card_cvn`) to a `/silent/` path on `secureacceptance.cybersource.com` (`testsecureacceptance.cybersource.com` in CyberSource's test environment, `secureacceptance.in.cybersource.com` in India); Arrow submits that form into a hidden iframe. No Agentcard payment takes that post over, and earlier releases let it through, so CyberSource received the dummy card. Both adapters now stop it for every company, whatever `syncRegistry()` served: a form the page submits as a page load, in the page or in a frame, gets the page that says Agentcard stopped the payment before the card reached CyberSource, and a fetch or an XHR fails. A `card_number` in the URL's query, as a form with method GET sends it, stops the request the same way. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds, like every `unsupported` reason: to pay another way on the same page, attach again. A post with no `card_number`, or an empty one, such as a payment with a stored `payment_token`, continues untouched, and so does Hosted Checkout, the same hosts without `/silent/`, where your agent types the card on CyberSource's own page. The SDK stops a post whose body it cannot read with a `blocked` event, `unreadable_cybersource_secure_acceptance_request`, and does not hold the checkout. TypeScript: the controller's `unsupported()` reasons gain `unsupported_cybersource_secure_acceptance`; code that switches exhaustively over them needs the new case.
|
|
6
8
|
|
|
7
9
|
- Name CyberSource Secure Acceptance's Silent Order POST in a preflight inspection. A form, a frame or a document on a `/silent/` path of `secureacceptance.cybersource.com`, `testsecureacceptance.cybersource.com` or `secureacceptance.in.cybersource.com` now names `cybersource_secure_acceptance`, and the result stays `unknown`: `processor_variant_unverified` for a visible form, and `payment_signal_inactive` for a hidden one such as Arrow's. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
|
package/README.md
CHANGED
|
@@ -1938,7 +1938,8 @@ eProtect traffic exactly as it found it, as it does before a sync.
|
|
|
1938
1938
|
|
|
1939
1939
|
The merchant's script gives up on the registration 10 to 15 seconds after Pay,
|
|
1940
1940
|
and re-sends it to a failover host after 5, which is too soon for a prompt that
|
|
1941
|
-
waits on a person
|
|
1941
|
+
waits on a person (SmartPak Equine's card form sets neither, so its script
|
|
1942
|
+
waits). An eProtect checkout is therefore always prepared:
|
|
1942
1943
|
|
|
1943
1944
|
```ts
|
|
1944
1945
|
const checkout = await attachToPlaywright(page, {
|
|
@@ -1955,8 +1956,10 @@ await page.getByRole('button', { name: 'Place order', exact: true }).click();
|
|
|
1955
1956
|
registration is for, which is also the merchant Worldpay lets charge the card
|
|
1956
1957
|
again. It is fixed before the page sends anything, so a registration naming
|
|
1957
1958
|
another paypageId never spends the approval. The Worldpay iframe names it in
|
|
1958
|
-
its URL (`/eProtect/payframe_<paypageId>_
|
|
1959
|
-
|
|
1959
|
+
its URL (`/eProtect/payframe_<paypageId>_...`, or the `paypageid` query of
|
|
1960
|
+
`/eProtect/eProtect.html`, as at Nordstrom); a JS API merchant carries it in
|
|
1961
|
+
its eProtect configuration (SmartPak Equine's page keeps it in a hidden
|
|
1962
|
+
`PayPageId` input).
|
|
1960
1963
|
- `cardId` is required, and the approval is locked to that card. Worldpay never
|
|
1961
1964
|
receives an expiry: the merchant sends the one typed into its form with the
|
|
1962
1965
|
registration. The agent types a placeholder number of the stored card's
|
|
@@ -1965,9 +1968,12 @@ await page.getByRole('button', { name: 'Place order', exact: true }).click();
|
|
|
1965
1968
|
prepares.
|
|
1966
1969
|
|
|
1967
1970
|
After Pay, the SDK pauses the registration and binds it to the preparation.
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
+
Worldpay rotates its production key daily, so the SDK pauses a registration
|
|
1972
|
+
whatever Worldpay key id its page names. The cardholder's device, still on the
|
|
1973
|
+
approval page, reads Worldpay's key from Worldpay's own `eProtect-api3.js` on
|
|
1974
|
+
the connection that carries the card, encrypts the approved card under it into
|
|
1975
|
+
that same query, writes that key's id into `publicKeyId`, and sends it through
|
|
1976
|
+
Agentcard's encrypted relay. The SDK then answers the paused script as Worldpay would:
|
|
1971
1977
|
the registration in the script's own callback, under Worldpay's script type,
|
|
1972
1978
|
with only the fields the merchant needs (no first six or last four digits).
|
|
1973
1979
|
The SDK reads the authorization every 500 ms for 15 seconds, so the answer
|
|
@@ -2001,8 +2007,16 @@ one the approval shows. The API refuses another name with
|
|
|
2001
2007
|
`merchant_profile_mismatch` and a paypageId with no pin with
|
|
2002
2008
|
`unsupported_checkout`, before any preparation exists; when this release pins
|
|
2003
2009
|
the paypageId too, the SDK refuses another name first, before any network call.
|
|
2004
|
-
|
|
2005
|
-
eProtect checkout
|
|
2010
|
+
This release pins two production merchants, and the API refuses every other
|
|
2011
|
+
live eProtect checkout; the pre-live host needs no pin:
|
|
2012
|
+
|
|
2013
|
+
| `merchant` | `paypageId` | Merchant origin |
|
|
2014
|
+
| --- | --- | --- |
|
|
2015
|
+
| `SmartPak Equine` | `zhs8KzuRAryaq4cs` | `https://www.smartpakequine.com` |
|
|
2016
|
+
| `Nordstrom` | `fL33BPkRvbRqJ29S` | `https://www.nordstrom.com` |
|
|
2017
|
+
|
|
2018
|
+
The merchant origin is the checkout page's own, the top page that frames
|
|
2019
|
+
Worldpay's iframe at Nordstrom. An approved answer that is not a
|
|
2006
2020
|
registration for the paused request raises `PaymentOutcomeUnknownError` with
|
|
2007
2021
|
reason `eprotect_registration_unconfirmed`: the device may have registered the
|
|
2008
2022
|
card, so reconcile the merchant before trying again.
|
package/dist/eprotect.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export interface EprotectMerchantPin {
|
|
|
18
18
|
* (merchant_profile_mismatch) before any preparation exists. When this build pins the paypageId
|
|
19
19
|
* too, it refuses another name first, before any network call, as a Fiserv checkout must name its
|
|
20
20
|
* key pin's merchant. A paypageId this build does not pin is the API's to judge (unsupported_checkout
|
|
21
|
-
* with no pin)
|
|
22
|
-
* cards and needs no pin. `pins` is for tests.
|
|
21
|
+
* with no pin). This build pins SmartPak Equine and Nordstrom, under those exact names. The pre-live
|
|
22
|
+
* host takes only the guide's test cards and needs no pin. `pins` is for tests.
|
|
23
23
|
*/
|
|
24
24
|
export declare function eprotectCheckoutRefusal(input: {
|
|
25
25
|
environment: string;
|
|
@@ -4,22 +4,61 @@ declare var EPROTECT_HOSTS: Readonly<{
|
|
|
4
4
|
sandbox: "request.eprotect.vantivprelive.com";
|
|
5
5
|
}>;
|
|
6
6
|
declare var EPROTECT_SECONDARY_HOST: string;
|
|
7
|
-
declare var
|
|
8
|
-
|
|
9
|
-
keyId: "26455500012";
|
|
10
|
-
exponent: "10001";
|
|
11
|
-
modulus: "b9c7095ff203475347a932a6e11bacbb61cfb66a877158683634e5fb1dbf9923401bd7f82c21b67ffd3c82bdc64797c5e2ba4b57c68b1aaccb7fbe847da7234a21df910dfcc0aebacbbd4d36b35c0c35f7ee27358610d2e2fb2aa87d359deda0b5f4d77e31ac5ab98741da5b6052cb657087da05ad496154b8954939ee6fd463301ff15ebca94f210f4627a255a95ca8833728c1ab80b6cc3a874e4d3dce5d40f66c043ba1b042717bb9cc5d89434fa489a51c2ba9a3843b310df58c9e8a54c36cdd5fd66e76651b6fc88850b60e0739abfcf7d0d0cb44beb7e768da3bcea7e743b8c2d57edef59643f54432ccf60c49d4f5f84410ecf9c5a7af19bc5e792801";
|
|
12
|
-
}>;
|
|
13
|
-
sandbox: Readonly<{
|
|
14
|
-
keyId: "1";
|
|
15
|
-
exponent: "10001";
|
|
16
|
-
modulus: "bc637dd74ba76507dad5af1c7ad6f97dbef5298c3b9f74caea7301347db7b4a8c37f26491863100667246fd45071f3346bf62239f9b117d06fb67861b66ad0d158beeddd2f6f28be93d846f4c8f9ba1bd7e8f186f36cab0e432f22b3d732c221a9ff00a9bfacb88b24503e2695fd7237835d4936477b21289478906a49b164f52503c20eb29f11fcbda2af94deb9a0bfde5a4589276897436315c5d664d60bf10650164f509283aed39747ad5d6cb2bbe54e1b42306e5db37dfd42dcbfcc689e0ddfe3bc9cb22ae7018e5a4a1ff39813584ac7bd6d6d65ca763f0a672da454081ea20e8b1d403316d80b9353ba396bea8962b1a7d2f775c76612d857c1f7594f";
|
|
17
|
-
}>;
|
|
18
|
-
}>;
|
|
7
|
+
declare var EPROTECT_KEY_SOURCES: any;
|
|
8
|
+
declare var EPROTECT_KEY_SCRIPT_MAX_BYTES: number;
|
|
19
9
|
declare var EPROTECT_TEST_PANS: Readonly<Set<unknown>>;
|
|
20
10
|
declare var EPROTECT_REFUSAL_CODES: readonly string[];
|
|
21
11
|
declare function eprotectEnvironment(hostname: any): "production" | "sandbox" | null;
|
|
22
12
|
declare function eprotectPreparationUrl(environment: any): string;
|
|
13
|
+
declare function eprotectKeySourceUrl(environment: any): string;
|
|
14
|
+
declare function eprotectScriptFacts(bytes: any): {
|
|
15
|
+
literals: number;
|
|
16
|
+
keyId?: undefined;
|
|
17
|
+
modulus?: undefined;
|
|
18
|
+
exponent?: undefined;
|
|
19
|
+
modulusSha256?: undefined;
|
|
20
|
+
maskedSha256?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
literals: number;
|
|
23
|
+
keyId: string;
|
|
24
|
+
modulus: string;
|
|
25
|
+
exponent: string;
|
|
26
|
+
modulusSha256: string;
|
|
27
|
+
maskedSha256: string;
|
|
28
|
+
};
|
|
29
|
+
declare function eprotectKeyScriptVerdict(environment: any, bytes: any, sources?: any): {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
reason: string;
|
|
32
|
+
maskedSha256?: undefined;
|
|
33
|
+
keyId?: undefined;
|
|
34
|
+
modulus?: undefined;
|
|
35
|
+
modulusSha256?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
reason: string;
|
|
39
|
+
maskedSha256: string | undefined;
|
|
40
|
+
keyId?: undefined;
|
|
41
|
+
modulus?: undefined;
|
|
42
|
+
modulusSha256?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
keyId: string;
|
|
46
|
+
modulus: string;
|
|
47
|
+
modulusSha256: string;
|
|
48
|
+
maskedSha256: string;
|
|
49
|
+
reason?: undefined;
|
|
50
|
+
};
|
|
51
|
+
declare function readEprotectKeyScript(environment: any, bytes: any, sources?: any): Readonly<{
|
|
52
|
+
environment: any;
|
|
53
|
+
keyId: string | undefined;
|
|
54
|
+
modulus: string | undefined;
|
|
55
|
+
exponent: "10001";
|
|
56
|
+
modulusSha256: string | undefined;
|
|
57
|
+
}>;
|
|
58
|
+
declare function eprotectKeyReport(key: any): Readonly<{
|
|
59
|
+
key_id: any;
|
|
60
|
+
modulus_sha256: any;
|
|
61
|
+
}>;
|
|
23
62
|
declare function isEprotectTokenRequest(url: any, method: any): boolean;
|
|
24
63
|
declare function isEprotectRequest(request: any): boolean;
|
|
25
64
|
declare function eprotectRequestFacts(url: any): {
|
|
@@ -35,7 +74,7 @@ declare function eprotectSecondaryAttempt(url: any): {
|
|
|
35
74
|
paypageId: any;
|
|
36
75
|
id: any;
|
|
37
76
|
} | null;
|
|
38
|
-
declare function isEprotectDeviceRequest(templateUrl: any, deviceUrl: any): boolean;
|
|
77
|
+
declare function isEprotectDeviceRequest(templateUrl: any, deviceUrl: any, sessionKeyId: any): boolean;
|
|
39
78
|
declare function isPreparedEprotectRequest(url: any, method: any, body: any, environment: any, paypageId: any): boolean;
|
|
40
79
|
declare var EPROTECT_MERCHANT_PINS: any;
|
|
41
80
|
declare function eprotectMerchantPin(paypageId: any, merchantOrigin: any, pins?: any): any;
|
|
@@ -43,7 +82,7 @@ declare function eprotectMerchantRefusal(environment: any, paypageId: any, merch
|
|
|
43
82
|
declare function defaultRandom(bytes: any): void;
|
|
44
83
|
declare function eprotectEncrypt(text: any, key: any, random?: typeof defaultRandom): string;
|
|
45
84
|
declare function eprotectCardRefusal(environment: any, cardNumber: any): "unsupported_environment" | "sandbox_host_requires_documented_test_card" | "invalid_card_number" | "live_host_refuses_documented_test_card" | null;
|
|
46
|
-
declare function eprotectDeviceRequest(templateUrl: any, card: any, random?: typeof defaultRandom): {
|
|
85
|
+
declare function eprotectDeviceRequest(templateUrl: any, card: any, sessionKey: any, random?: typeof defaultRandom): {
|
|
47
86
|
url: string;
|
|
48
87
|
method: string;
|
|
49
88
|
headers: {};
|
|
@@ -74,4 +113,4 @@ declare function eprotectGiveUp(url: any): {
|
|
|
74
113
|
code: any;
|
|
75
114
|
} | null;
|
|
76
115
|
declare function eprotectRefusalCode(bodyText: any): any;
|
|
77
|
-
export { EPROTECT_HOSTS,
|
|
116
|
+
export { EPROTECT_HOSTS, EPROTECT_KEY_SCRIPT_MAX_BYTES, EPROTECT_KEY_SOURCES, EPROTECT_MERCHANT_PINS, EPROTECT_PATH, EPROTECT_REFUSAL_CODES, EPROTECT_SECONDARY_HOST, EPROTECT_TEST_PANS, eprotectCardRefusal, eprotectDeviceRequest, eprotectEncrypt, eprotectEnvironment, eprotectGiveUp, eprotectJsonp, eprotectKeyReport, eprotectKeyScriptVerdict, eprotectKeySourceUrl, eprotectMerchantPin, eprotectMerchantRefusal, eprotectOutcome, eprotectPreparationUrl, eprotectRefusalCode, eprotectRequestFacts, eprotectScriptFacts, eprotectSecondaryAttempt, isEprotectDeviceRequest, isEprotectRequest, isEprotectTokenRequest, isPreparedEprotectRequest, readEprotectKeyScript, sanitizeEprotectRegistration };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 9a686322a5e8f0667fa74e6e3f06d6adfa8a242ab79c18467054ded429f9a3af
|
|
4
4
|
// src/deep-freeze.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -10,6 +10,137 @@ function deepFreeze(value) {
|
|
|
10
10
|
}
|
|
11
11
|
return value;
|
|
12
12
|
}
|
|
13
|
+
// src/sha256.js
|
|
14
|
+
var K = /* @__PURE__ */ new Uint32Array([
|
|
15
|
+
1116352408,
|
|
16
|
+
1899447441,
|
|
17
|
+
3049323471,
|
|
18
|
+
3921009573,
|
|
19
|
+
961987163,
|
|
20
|
+
1508970993,
|
|
21
|
+
2453635748,
|
|
22
|
+
2870763221,
|
|
23
|
+
3624381080,
|
|
24
|
+
310598401,
|
|
25
|
+
607225278,
|
|
26
|
+
1426881987,
|
|
27
|
+
1925078388,
|
|
28
|
+
2162078206,
|
|
29
|
+
2614888103,
|
|
30
|
+
3248222580,
|
|
31
|
+
3835390401,
|
|
32
|
+
4022224774,
|
|
33
|
+
264347078,
|
|
34
|
+
604807628,
|
|
35
|
+
770255983,
|
|
36
|
+
1249150122,
|
|
37
|
+
1555081692,
|
|
38
|
+
1996064986,
|
|
39
|
+
2554220882,
|
|
40
|
+
2821834349,
|
|
41
|
+
2952996808,
|
|
42
|
+
3210313671,
|
|
43
|
+
3336571891,
|
|
44
|
+
3584528711,
|
|
45
|
+
113926993,
|
|
46
|
+
338241895,
|
|
47
|
+
666307205,
|
|
48
|
+
773529912,
|
|
49
|
+
1294757372,
|
|
50
|
+
1396182291,
|
|
51
|
+
1695183700,
|
|
52
|
+
1986661051,
|
|
53
|
+
2177026350,
|
|
54
|
+
2456956037,
|
|
55
|
+
2730485921,
|
|
56
|
+
2820302411,
|
|
57
|
+
3259730800,
|
|
58
|
+
3345764771,
|
|
59
|
+
3516065817,
|
|
60
|
+
3600352804,
|
|
61
|
+
4094571909,
|
|
62
|
+
275423344,
|
|
63
|
+
430227734,
|
|
64
|
+
506948616,
|
|
65
|
+
659060556,
|
|
66
|
+
883997877,
|
|
67
|
+
958139571,
|
|
68
|
+
1322822218,
|
|
69
|
+
1537002063,
|
|
70
|
+
1747873779,
|
|
71
|
+
1955562222,
|
|
72
|
+
2024104815,
|
|
73
|
+
2227730452,
|
|
74
|
+
2361852424,
|
|
75
|
+
2428436474,
|
|
76
|
+
2756734187,
|
|
77
|
+
3204031479,
|
|
78
|
+
3329325298
|
|
79
|
+
]);
|
|
80
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
81
|
+
function utf8(text) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const char of text) {
|
|
84
|
+
let cp = char.codePointAt(0);
|
|
85
|
+
if (cp >= 55296 && cp <= 57343)
|
|
86
|
+
cp = 65533;
|
|
87
|
+
if (cp < 128)
|
|
88
|
+
out.push(cp);
|
|
89
|
+
else if (cp < 2048)
|
|
90
|
+
out.push(192 | cp >> 6, 128 | cp & 63);
|
|
91
|
+
else if (cp < 65536)
|
|
92
|
+
out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
93
|
+
else
|
|
94
|
+
out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
95
|
+
}
|
|
96
|
+
return Uint8Array.from(out);
|
|
97
|
+
}
|
|
98
|
+
function sha256Hex(input) {
|
|
99
|
+
const isBytes = Object.prototype.toString.call(input) === "[object Uint8Array]";
|
|
100
|
+
if (typeof input !== "string" && !isBytes)
|
|
101
|
+
throw new TypeError("sha256Hex takes a string or a Uint8Array.");
|
|
102
|
+
const bytes = typeof input === "string" ? utf8(input) : input;
|
|
103
|
+
const length = bytes.length;
|
|
104
|
+
const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
|
|
105
|
+
padded.set(bytes);
|
|
106
|
+
padded[length] = 128;
|
|
107
|
+
const view = new DataView(padded.buffer);
|
|
108
|
+
view.setUint32(padded.length - 8, Math.floor(length / 536870912));
|
|
109
|
+
view.setUint32(padded.length - 4, length << 3 >>> 0);
|
|
110
|
+
const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
|
|
111
|
+
const W = new Uint32Array(64);
|
|
112
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
113
|
+
for (let t = 0; t < 16; t++)
|
|
114
|
+
W[t] = view.getUint32(offset + t * 4);
|
|
115
|
+
for (let t = 16; t < 64; t++) {
|
|
116
|
+
const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
|
|
117
|
+
const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
|
|
118
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
119
|
+
}
|
|
120
|
+
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
|
121
|
+
for (let t = 0; t < 64; t++) {
|
|
122
|
+
const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
|
|
123
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
124
|
+
h = g;
|
|
125
|
+
g = f;
|
|
126
|
+
f = e;
|
|
127
|
+
e = d + t1 >>> 0;
|
|
128
|
+
d = c;
|
|
129
|
+
c = b;
|
|
130
|
+
b = a;
|
|
131
|
+
a = t1 + t2 >>> 0;
|
|
132
|
+
}
|
|
133
|
+
H[0] += a;
|
|
134
|
+
H[1] += b;
|
|
135
|
+
H[2] += c;
|
|
136
|
+
H[3] += d;
|
|
137
|
+
H[4] += e;
|
|
138
|
+
H[5] += f;
|
|
139
|
+
H[6] += g;
|
|
140
|
+
H[7] += h;
|
|
141
|
+
}
|
|
142
|
+
return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
|
|
143
|
+
}
|
|
13
144
|
// src/test-pans.js
|
|
14
145
|
function immutableSet(values, name) {
|
|
15
146
|
const set = new Set(values);
|
|
@@ -33,18 +164,19 @@ var EPROTECT_HOSTS = /* @__PURE__ */ Object.freeze({
|
|
|
33
164
|
sandbox: "request.eprotect.vantivprelive.com"
|
|
34
165
|
});
|
|
35
166
|
var EPROTECT_SECONDARY_HOST = "secondary.eprotect.vantivcnp.com";
|
|
36
|
-
var
|
|
37
|
-
production:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
sandbox:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
167
|
+
var EPROTECT_KEY_SOURCES = /* @__PURE__ */ deepFreeze({
|
|
168
|
+
production: {
|
|
169
|
+
host: "request.eprotect.vantivcnp.com",
|
|
170
|
+
path: "/eProtect/eProtect-api3.js",
|
|
171
|
+
maskedSha256: "e4e55a14c10b25a497db7931124c62c6fb78d3b0c5c2afbc95303b398dd3f439"
|
|
172
|
+
},
|
|
173
|
+
sandbox: {
|
|
174
|
+
host: "request.eprotect.vantivprelive.com",
|
|
175
|
+
path: "/eProtect/eProtect-api3.js",
|
|
176
|
+
maskedSha256: "1fc0d21e51a36900bcfbbb6117a05a1eed7d004aa2decb85e12d4e19236dca15"
|
|
177
|
+
}
|
|
47
178
|
});
|
|
179
|
+
var EPROTECT_KEY_SCRIPT_MAX_BYTES = 65536;
|
|
48
180
|
var EPROTECT_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
49
181
|
"5112010000000003",
|
|
50
182
|
"44570100000000009",
|
|
@@ -107,6 +239,98 @@ function eprotectPreparationUrl(environment) {
|
|
|
107
239
|
throw new Error("unsupported_eprotect_environment");
|
|
108
240
|
return `https://${EPROTECT_HOSTS[environment]}${EPROTECT_PATH}`;
|
|
109
241
|
}
|
|
242
|
+
function eprotectKeySourceUrl(environment) {
|
|
243
|
+
if (environment !== "production" && environment !== "sandbox")
|
|
244
|
+
throw new Error("unsupported_eprotect_environment");
|
|
245
|
+
const source = EPROTECT_KEY_SOURCES[environment];
|
|
246
|
+
return `https://${source.host}${source.path}`;
|
|
247
|
+
}
|
|
248
|
+
var KEY_LITERAL = 'modulus:"([0-9a-f]*)",exponent:"([0-9a-f]*)",keyId:"([0-9]*)"';
|
|
249
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
250
|
+
var SESSION_KEYS = /* @__PURE__ */ new WeakSet();
|
|
251
|
+
function scriptBytes(bytes, limit) {
|
|
252
|
+
if (!ArrayBuffer.isView(bytes) || Object.prototype.toString.call(bytes) !== "[object Uint8Array]")
|
|
253
|
+
return null;
|
|
254
|
+
const view = (
|
|
255
|
+
/** @type {Uint8Array} */
|
|
256
|
+
bytes);
|
|
257
|
+
if (view.length > limit)
|
|
258
|
+
return null;
|
|
259
|
+
const copy = new Uint8Array(view.length);
|
|
260
|
+
copy.set(view);
|
|
261
|
+
return copy;
|
|
262
|
+
}
|
|
263
|
+
function byteText(bytes) {
|
|
264
|
+
let text = "";
|
|
265
|
+
for (let at = 0; at < bytes.length; at += 8192)
|
|
266
|
+
text += String.fromCharCode.apply(null, bytes.subarray(at, at + 8192));
|
|
267
|
+
return text;
|
|
268
|
+
}
|
|
269
|
+
function occurrences(text, label) {
|
|
270
|
+
let count = 0;
|
|
271
|
+
for (let at = text.indexOf(label); at !== -1; at = text.indexOf(label, at + label.length))
|
|
272
|
+
count++;
|
|
273
|
+
return count;
|
|
274
|
+
}
|
|
275
|
+
function keyLiteralFacts(data) {
|
|
276
|
+
const text = byteText(data);
|
|
277
|
+
const literals = [...text.matchAll(new RegExp(KEY_LITERAL, "g"))];
|
|
278
|
+
if (literals.length !== 1 || occurrences(text, "modulus:") !== 1 || occurrences(text, "keyId:") !== 1)
|
|
279
|
+
return { literals: literals.length };
|
|
280
|
+
const [whole, modulus, exponent, keyId] = literals[0];
|
|
281
|
+
const start = literals[0].index;
|
|
282
|
+
const kept = `modulus:"",exponent:"${exponent}",keyId:""`;
|
|
283
|
+
const masked = new Uint8Array(data.length - whole.length + kept.length);
|
|
284
|
+
masked.set(data.subarray(0, start));
|
|
285
|
+
for (let i = 0; i < kept.length; i++)
|
|
286
|
+
masked[start + i] = kept.charCodeAt(i);
|
|
287
|
+
masked.set(data.subarray(start + whole.length), start + kept.length);
|
|
288
|
+
return { literals: 1, keyId, modulus, exponent, modulusSha256: sha256Hex(modulus), maskedSha256: sha256Hex(masked) };
|
|
289
|
+
}
|
|
290
|
+
function eprotectScriptFacts(bytes) {
|
|
291
|
+
const data = scriptBytes(bytes, Infinity);
|
|
292
|
+
return data ? keyLiteralFacts(data) : { literals: 0 };
|
|
293
|
+
}
|
|
294
|
+
function eprotectKeyScriptVerdict(environment, bytes, sources = EPROTECT_KEY_SOURCES) {
|
|
295
|
+
const source = (environment === "production" || environment === "sandbox") && sources && typeof sources === "object" ? sources[environment] : null;
|
|
296
|
+
const pinned = source && typeof source === "object" ? source.maskedSha256 : null;
|
|
297
|
+
if (!exact(SHA256_HEX, pinned))
|
|
298
|
+
return { ok: false, reason: "unsupported_eprotect_environment" };
|
|
299
|
+
const data = scriptBytes(bytes, EPROTECT_KEY_SCRIPT_MAX_BYTES);
|
|
300
|
+
if (!data || data.length === 0)
|
|
301
|
+
return { ok: false, reason: "eprotect_script_unreadable" };
|
|
302
|
+
const facts = keyLiteralFacts(data);
|
|
303
|
+
if (!("maskedSha256" in facts))
|
|
304
|
+
return { ok: false, reason: "eprotect_script_key_literal" };
|
|
305
|
+
const { keyId, modulus, exponent, modulusSha256, maskedSha256 } = facts;
|
|
306
|
+
if (!exact(/^[0-9]{1,32}$/, keyId) || !exact(/^[0-9a-f]{512}$/, modulus))
|
|
307
|
+
return { ok: false, reason: "eprotect_script_key_literal", maskedSha256 };
|
|
308
|
+
if (maskedSha256 !== pinned)
|
|
309
|
+
return { ok: false, reason: "eprotect_script_changed", maskedSha256 };
|
|
310
|
+
if (modulus[0] < "8" || exponent !== "10001")
|
|
311
|
+
return { ok: false, reason: "eprotect_key_weak", maskedSha256 };
|
|
312
|
+
return { ok: true, keyId, modulus, modulusSha256, maskedSha256 };
|
|
313
|
+
}
|
|
314
|
+
function readEprotectKeyScript(environment, bytes, sources = EPROTECT_KEY_SOURCES) {
|
|
315
|
+
const verdict = eprotectKeyScriptVerdict(environment, bytes, sources);
|
|
316
|
+
if (!verdict.ok) {
|
|
317
|
+
throw Object.assign(new Error("Refusing to pay: Worldpay's eProtect script is not the one Agentcard reviewed."), { reason: verdict.reason });
|
|
318
|
+
}
|
|
319
|
+
const key = Object.freeze({
|
|
320
|
+
environment,
|
|
321
|
+
keyId: verdict.keyId,
|
|
322
|
+
modulus: verdict.modulus,
|
|
323
|
+
exponent: "10001",
|
|
324
|
+
modulusSha256: verdict.modulusSha256
|
|
325
|
+
});
|
|
326
|
+
SESSION_KEYS.add(key);
|
|
327
|
+
return key;
|
|
328
|
+
}
|
|
329
|
+
function eprotectKeyReport(key) {
|
|
330
|
+
if (!SESSION_KEYS.has(key))
|
|
331
|
+
throw new Error("Refusing to pay: no Worldpay key was read for this eProtect session.");
|
|
332
|
+
return Object.freeze({ key_id: key.keyId, modulus_sha256: key.modulusSha256 });
|
|
333
|
+
}
|
|
110
334
|
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
111
335
|
function bytesToBase64(bytes) {
|
|
112
336
|
let out = "";
|
|
@@ -141,6 +365,8 @@ var VALUE_RULES = {
|
|
|
141
365
|
// true bypasses Worldpay's Mod10 check and hides the BIN: private-label cards only.
|
|
142
366
|
pciNonSensitive: (value) => value === "false",
|
|
143
367
|
encryptedCvv: ciphertext,
|
|
368
|
+
// Worldpay's key id, digits only. Any key passes: Worldpay rotates its production key daily, and
|
|
369
|
+
// the device writes the key it read from Worldpay into its own copy (eprotectDeviceRequest).
|
|
144
370
|
publicKeyId: (value) => exact(/^[0-9]{1,32}$/, value),
|
|
145
371
|
firstEight: (value) => value === "true",
|
|
146
372
|
requestType: (value) => value === "eProtect",
|
|
@@ -182,8 +408,6 @@ function parseRegistration(url, host) {
|
|
|
182
408
|
}
|
|
183
409
|
if (QUERY_ORDER.some((name) => !OPTIONAL.has(name) && !(name in values)))
|
|
184
410
|
return null;
|
|
185
|
-
if (values.publicKeyId !== EPROTECT_KEYS[environment].keyId)
|
|
186
|
-
return null;
|
|
187
411
|
if (values.targetServer !== (host === "secondary" ? "secondary" : "primary"))
|
|
188
412
|
return null;
|
|
189
413
|
return { environment, origin: parsed.origin, pairs, values };
|
|
@@ -214,12 +438,14 @@ function eprotectSecondaryAttempt(url) {
|
|
|
214
438
|
return parsed ? { paypageId: parsed.values.paypageId, id: parsed.values.id } : null;
|
|
215
439
|
}
|
|
216
440
|
var CIPHERTEXT_KEYS = /* @__PURE__ */ new Set(["encryptedAccount", "encryptedCvv"]);
|
|
217
|
-
function isEprotectDeviceRequest(templateUrl, deviceUrl) {
|
|
441
|
+
function isEprotectDeviceRequest(templateUrl, deviceUrl, sessionKeyId) {
|
|
442
|
+
if (sessionKeyId !== void 0 && !exact(/^[0-9]{1,32}$/, sessionKeyId))
|
|
443
|
+
return false;
|
|
218
444
|
const template = parseRegistration(templateUrl, "primary");
|
|
219
445
|
const device = parseRegistration(deviceUrl, "primary");
|
|
220
446
|
if (!template || !device || device.origin !== template.origin || device.pairs.length !== template.pairs.length)
|
|
221
447
|
return false;
|
|
222
|
-
return device.pairs.every((pair, i) => pair.name === template.pairs[i].name && (CIPHERTEXT_KEYS.has(pair.name) ? pair.raw !== template.pairs[i].raw : pair.raw === template.pairs[i].raw));
|
|
448
|
+
return device.pairs.every((pair, i) => pair.name === template.pairs[i].name && (CIPHERTEXT_KEYS.has(pair.name) ? pair.raw !== template.pairs[i].raw : pair.name === "publicKeyId" && sessionKeyId !== void 0 ? pair.raw === sessionKeyId : pair.raw === template.pairs[i].raw));
|
|
223
449
|
}
|
|
224
450
|
function isPreparedEprotectRequest(url, method, body, environment, paypageId) {
|
|
225
451
|
if (environment !== "production" && environment !== "sandbox")
|
|
@@ -229,7 +455,10 @@ function isPreparedEprotectRequest(url, method, body, environment, paypageId) {
|
|
|
229
455
|
const facts = eprotectRequestFacts(url);
|
|
230
456
|
return !!facts && facts.environment === environment && facts.paypageId === paypageId;
|
|
231
457
|
}
|
|
232
|
-
var EPROTECT_MERCHANT_PINS = /* @__PURE__ */ deepFreeze([
|
|
458
|
+
var EPROTECT_MERCHANT_PINS = /* @__PURE__ */ deepFreeze([
|
|
459
|
+
{ id: "smartpak_equine", merchant: "SmartPak Equine", origins: ["https://www.smartpakequine.com"], paypageIds: ["zhs8KzuRAryaq4cs"] },
|
|
460
|
+
{ id: "nordstrom", merchant: "Nordstrom", origins: ["https://www.nordstrom.com"], paypageIds: ["fL33BPkRvbRqJ29S"] }
|
|
461
|
+
]);
|
|
233
462
|
function eprotectMerchantPin(paypageId, merchantOrigin, pins = EPROTECT_MERCHANT_PINS) {
|
|
234
463
|
if (!exact(PAYPAGE_ID, paypageId) || typeof merchantOrigin !== "string" || !Array.isArray(pins))
|
|
235
464
|
return null;
|
|
@@ -315,10 +544,13 @@ function eprotectCardRefusal(environment, cardNumber) {
|
|
|
315
544
|
return "invalid_card_number";
|
|
316
545
|
return EPROTECT_TEST_PANS.has(pan) || DOCUMENTED_TEST_PANS.has(pan) ? "live_host_refuses_documented_test_card" : null;
|
|
317
546
|
}
|
|
318
|
-
function eprotectDeviceRequest(templateUrl, card, random = defaultRandom) {
|
|
547
|
+
function eprotectDeviceRequest(templateUrl, card, sessionKey, random = defaultRandom) {
|
|
319
548
|
const parsed = parseRegistration(templateUrl, "primary");
|
|
320
549
|
if (!parsed)
|
|
321
550
|
throw new Error("Refusing to pay: unsupported eProtect card request.");
|
|
551
|
+
if (!SESSION_KEYS.has(sessionKey) || sessionKey.environment !== parsed.environment) {
|
|
552
|
+
throw new Error("Refusing to pay: no Worldpay key was read for this eProtect session.");
|
|
553
|
+
}
|
|
322
554
|
const refusal = eprotectCardRefusal(parsed.environment, card && card.number);
|
|
323
555
|
if (refusal === "sandbox_host_requires_documented_test_card") {
|
|
324
556
|
throw new Error("Refusing to pay: a sandbox or test host accepts only the processor's documented test cards, not a real card.");
|
|
@@ -329,13 +561,14 @@ function eprotectDeviceRequest(templateUrl, card, random = defaultRandom) {
|
|
|
329
561
|
if (withCvv && (typeof card.cvc !== "string" || !/^[0-9]{3,4}$/.test(card.cvc))) {
|
|
330
562
|
throw new Error("Could not fill the card fields for Worldpay eProtect. Nothing was sent.");
|
|
331
563
|
}
|
|
332
|
-
const
|
|
333
|
-
const fresh = { encryptedAccount: eprotectEncrypt(String(card.number), key, random) };
|
|
564
|
+
const fresh = { encryptedAccount: eprotectEncrypt(String(card.number), sessionKey, random) };
|
|
334
565
|
if (withCvv)
|
|
335
|
-
fresh.encryptedCvv = eprotectEncrypt(card.cvc,
|
|
566
|
+
fresh.encryptedCvv = eprotectEncrypt(card.cvc, sessionKey, random);
|
|
567
|
+
if (parsed.values.publicKeyId !== sessionKey.keyId)
|
|
568
|
+
fresh.publicKeyId = sessionKey.keyId;
|
|
336
569
|
const query = parsed.pairs.map(({ name, raw }) => `${name}=${name in fresh ? canonicalComponent(fresh[name]) : raw}`).join("&");
|
|
337
570
|
const url = `${parsed.origin}${EPROTECT_PATH}?${query}`;
|
|
338
|
-
if (!isEprotectDeviceRequest(templateUrl, url))
|
|
571
|
+
if (!isEprotectDeviceRequest(templateUrl, url, sessionKey.keyId))
|
|
339
572
|
throw new Error("Refusing to pay: unsupported eProtect card request.");
|
|
340
573
|
return { url, method: "GET", headers: {}, body: "", where: Object.keys(fresh) };
|
|
341
574
|
}
|
|
@@ -495,4 +728,4 @@ function eprotectRefusalCode(bodyText) {
|
|
|
495
728
|
return null;
|
|
496
729
|
return Object.entries(answer).every(([key, value]) => REFUSAL_FIELDS.has(key) && typeof value === "string") ? answer.response : null;
|
|
497
730
|
}
|
|
498
|
-
export { EPROTECT_HOSTS,
|
|
731
|
+
export { EPROTECT_HOSTS, EPROTECT_KEY_SCRIPT_MAX_BYTES, EPROTECT_KEY_SOURCES, EPROTECT_MERCHANT_PINS, EPROTECT_PATH, EPROTECT_REFUSAL_CODES, EPROTECT_SECONDARY_HOST, EPROTECT_TEST_PANS, eprotectCardRefusal, eprotectDeviceRequest, eprotectEncrypt, eprotectEnvironment, eprotectGiveUp, eprotectJsonp, eprotectKeyReport, eprotectKeyScriptVerdict, eprotectKeySourceUrl, eprotectMerchantPin, eprotectMerchantRefusal, eprotectOutcome, eprotectPreparationUrl, eprotectRefusalCode, eprotectRequestFacts, eprotectScriptFacts, eprotectSecondaryAttempt, isEprotectDeviceRequest, isEprotectRequest, isEprotectTokenRequest, isPreparedEprotectRequest, readEprotectKeyScript, sanitizeEprotectRegistration };
|
package/dist/eprotect.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// The vendor's browser script (eProtect-api2/3, litle-api2/3, and the Worldpay iframe, which runs
|
|
4
4
|
// them in a Worldpay-origin frame) encrypts the card in the page and sends it as ONE JSONP
|
|
5
5
|
// <script> GET to /eProtect/paypage; the answer is `<callback>({...})`. The rules that judge that
|
|
6
|
-
// request (the query grammar,
|
|
7
|
-
//
|
|
6
|
+
// request (the query grammar, which takes any key id in Worldpay's digits because Worldpay rotates
|
|
7
|
+
// its production key daily, the failover and give-up reports, the answer allowlist, the reviewed
|
|
8
|
+
// merchants) live in payment-core and reach this SDK as eprotect.generated.ts. This module holds
|
|
8
9
|
// only what an attachment decides from them:
|
|
9
10
|
// - which request on eProtect's hosts is the registration to pause for the vault;
|
|
10
11
|
// - which is the vendor's failover re-send of a registration the page already sent (aborted
|
|
@@ -28,8 +29,8 @@ const EPROTECT_HOSTNAMES = [EPROTECT_HOSTS.production, EPROTECT_HOSTS.sandbox, E
|
|
|
28
29
|
* (merchant_profile_mismatch) before any preparation exists. When this build pins the paypageId
|
|
29
30
|
* too, it refuses another name first, before any network call, as a Fiserv checkout must name its
|
|
30
31
|
* key pin's merchant. A paypageId this build does not pin is the API's to judge (unsupported_checkout
|
|
31
|
-
* with no pin)
|
|
32
|
-
* cards and needs no pin. `pins` is for tests.
|
|
32
|
+
* with no pin). This build pins SmartPak Equine and Nordstrom, under those exact names. The pre-live
|
|
33
|
+
* host takes only the guide's test cards and needs no pin. `pins` is for tests.
|
|
33
34
|
*/
|
|
34
35
|
export function eprotectCheckoutRefusal(input, pins) {
|
|
35
36
|
if (input.environment !== 'production')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/sdk",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.6",
|
|
4
4
|
"description": "Agentcard's SDK: one client, Agentcard, with two modules. checkout pauses a browser purchase and pays it with the person's own card; voice connects a person to your product once and approves a purchase by voice inside it. Card data never enters your process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|