@agent-cards/sdk 0.23.4 → 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 +6 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +23 -8
- package/dist/card-endpoint-page.d.ts +5 -3
- package/dist/cdp.js +7 -2
- package/dist/cybersource-secure-acceptance.d.ts +78 -0
- package/dist/cybersource-secure-acceptance.js +214 -0
- 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/dist/lifecycle.d.ts +6 -3
- package/dist/lifecycle.js +5 -2
- package/dist/pre-claim.d.ts +1 -0
- package/dist/pre-claim.js +76 -9
- package/dist/preflight-catalog.json +81 -0
- package/dist/preflight-schemas.json +6 -0
- package/dist/preflight.generated.js +21 -1
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
|
@@ -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/dist/lifecycle.d.ts
CHANGED
|
@@ -226,11 +226,14 @@ export declare class CheckoutLifecycle implements CheckoutController {
|
|
|
226
226
|
* (`unsupported_payrails_card_payment` and the card-save refusal codes), a
|
|
227
227
|
* Shiji STS request its gate refuses (shiji_sts_keep_on_file and the other
|
|
228
228
|
* shiji-sts.ts reasons), a post to a processor's own card page that only
|
|
229
|
-
* the cardholder's device can fill (processor_card_page: NMI's),
|
|
229
|
+
* the cardholder's device can fill (processor_card_page: NMI's), a card
|
|
230
230
|
* sent to PayPal's legacy Hosted Fields confirm, which no Vault mode takes
|
|
231
|
-
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts)
|
|
231
|
+
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
|
|
232
|
+
* a merchant's page posts to CyberSource Secure Acceptance's Silent Order
|
|
233
|
+
* POST, which no Vault mode takes over either
|
|
234
|
+
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts).
|
|
232
235
|
*/
|
|
233
|
-
unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields'): void;
|
|
236
|
+
unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields' | 'unsupported_cybersource_secure_acceptance'): void;
|
|
234
237
|
/**
|
|
235
238
|
* A card request that only a prepared checkout pays (an Adyen merchant
|
|
236
239
|
* profile's, or a Fiserv card capture) arrived with no preparation on this
|
package/dist/lifecycle.js
CHANGED
|
@@ -202,9 +202,12 @@ export class CheckoutLifecycle {
|
|
|
202
202
|
* (`unsupported_payrails_card_payment` and the card-save refusal codes), a
|
|
203
203
|
* Shiji STS request its gate refuses (shiji_sts_keep_on_file and the other
|
|
204
204
|
* shiji-sts.ts reasons), a post to a processor's own card page that only
|
|
205
|
-
* the cardholder's device can fill (processor_card_page: NMI's),
|
|
205
|
+
* the cardholder's device can fill (processor_card_page: NMI's), a card
|
|
206
206
|
* sent to PayPal's legacy Hosted Fields confirm, which no Vault mode takes
|
|
207
|
-
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts)
|
|
207
|
+
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
|
|
208
|
+
* a merchant's page posts to CyberSource Secure Acceptance's Silent Order
|
|
209
|
+
* POST, which no Vault mode takes over either
|
|
210
|
+
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts).
|
|
208
211
|
*/
|
|
209
212
|
unsupported(reason = 'unrecognized_payment_endpoint') {
|
|
210
213
|
this.held = true;
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -201,6 +201,7 @@ export declare const verifonePreClaim: PreClaimGate;
|
|
|
201
201
|
export declare const fiservPreClaim: PreClaimGate;
|
|
202
202
|
export declare const ekashuPreClaim: PreClaimGate;
|
|
203
203
|
export declare const paypalHostedFieldsPreClaim: PreClaimGate;
|
|
204
|
+
export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
|
|
204
205
|
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
205
206
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
206
207
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
package/dist/pre-claim.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// card-saving bodies, WSPay's confirm URL, which also carries bodies with
|
|
17
17
|
// no new card, Shiji STS's getTokenId and getCvvId, which also carry
|
|
18
18
|
// requests the vault must never pay, and PayPal's legacy Hosted Fields
|
|
19
|
-
// confirm
|
|
19
|
+
// confirm and CyberSource Secure Acceptance's Silent Order POST, whose card
|
|
20
|
+
// no Vault mode takes over.
|
|
20
21
|
// The first three used to be
|
|
21
22
|
// written inline in Fetch.requestPaused and again in the page.route handler, so
|
|
22
23
|
// a decision could drift between the two adapters and a new family had to be
|
|
@@ -75,6 +76,7 @@ import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isP
|
|
|
75
76
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
76
77
|
import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
77
78
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
79
|
+
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, } from './cybersource-secure-acceptance.js';
|
|
78
80
|
/**
|
|
79
81
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
80
82
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -169,14 +171,16 @@ function cardEndpointPausedBody(ctx) {
|
|
|
169
171
|
}
|
|
170
172
|
/**
|
|
171
173
|
* Answer a paused card form navigation with the page that says Agentcard stopped it before
|
|
172
|
-
* its card left
|
|
173
|
-
*
|
|
174
|
-
*
|
|
174
|
+
* its card left, naming where the card was going: a card endpoint profile's own merchant, or
|
|
175
|
+
* CyberSource for a Secure Acceptance post. While an approval is outstanding, or once a payment
|
|
176
|
+
* for this checkout may already exist, the page says a payment is in progress instead of that
|
|
177
|
+
* nothing was sent (card-endpoint-page.ts refusedNavigationOutcome), and names that payment's
|
|
178
|
+
* authorization.
|
|
175
179
|
*/
|
|
176
|
-
async function stopNavigationBeforeSend(ctx,
|
|
180
|
+
async function stopNavigationBeforeSend(ctx, merchant, reason) {
|
|
177
181
|
const state = ctx.lifecycle.getState();
|
|
178
182
|
const outcome = refusedNavigationOutcome({ awaitingApproval: ctx.awaitingApproval(), status: state.status });
|
|
179
|
-
await ctx.effects.answer(cardEndpointStoppedPage({ outcome, reason, merchant
|
|
183
|
+
await ctx.effects.answer(cardEndpointStoppedPage({ outcome, reason, merchant,
|
|
180
184
|
authorizationId: outcome === 'payment_in_progress' ? state.authorizationId : null }));
|
|
181
185
|
return { kind: 'answer' };
|
|
182
186
|
}
|
|
@@ -216,13 +220,13 @@ async function cardEndpointNavigationPreClaim(ctx, profile) {
|
|
|
216
220
|
}
|
|
217
221
|
if (navigation !== profile.navigation) {
|
|
218
222
|
ctx.onEvent?.({ type: 'card_endpoint_refused', detail: { ...cardEndpointDetail(profile), reason: 'request_unsupported', refusal: `the card post navigates ${navigation === 'top' ? 'the page' : 'an iframe'}` } });
|
|
219
|
-
return stopNavigationBeforeSend(ctx, profile, 'request_unsupported');
|
|
223
|
+
return stopNavigationBeforeSend(ctx, profile.merchant, 'request_unsupported');
|
|
220
224
|
}
|
|
221
225
|
if (!cardEndpointLive(profile)) {
|
|
222
226
|
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
223
227
|
ctx.lifecycle.unsupported('merchant_card_endpoint_not_enabled');
|
|
224
228
|
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { ...cardEndpointDetail(profile), reason: 'merchant_card_endpoint_not_enabled' } });
|
|
225
|
-
return stopNavigationBeforeSend(ctx, profile, 'not_enabled');
|
|
229
|
+
return stopNavigationBeforeSend(ctx, profile.merchant, 'not_enabled');
|
|
226
230
|
}
|
|
227
231
|
return { kind: 'pause', stripeStep: null, cardEndpoint: profile, navigation };
|
|
228
232
|
}
|
|
@@ -1530,6 +1534,68 @@ async function paypalHostedFieldsUnreadable(ctx) {
|
|
|
1530
1534
|
await ctx.effects.abort();
|
|
1531
1535
|
return { kind: 'abort' };
|
|
1532
1536
|
}
|
|
1537
|
+
// CyberSource Secure Acceptance's Silent Order POST (cybersource-secure-acceptance.ts). The merchant's
|
|
1538
|
+
// page posts the card the agent typed, beside the fields its server signed, to a /silent/ path on
|
|
1539
|
+
// CyberSource's Secure Acceptance hosts, which no Vault mode takes over, so every company's adapters
|
|
1540
|
+
// hand each request there to this gate, whether a fetch, an XHR or a form navigation sent it:
|
|
1541
|
+
// - a card_number that holds a value, in the body or in the URL's query, stops the request as an
|
|
1542
|
+
// unsupported checkout, reason `unsupported_cybersource_secure_acceptance`, with no claim, create or
|
|
1543
|
+
// prompt, so CyberSource never receives the card. A form navigation of the page or of a frame (Arrow
|
|
1544
|
+
// submits its form into a hidden iframe) is answered with the stopped page, as a card endpoint's form
|
|
1545
|
+
// is, since an aborted page load renders nothing and invites another click on Pay; any other request
|
|
1546
|
+
// fails. The attachment then reads `unsupported` and holds: the page's next post would carry the
|
|
1547
|
+
// card again;
|
|
1548
|
+
// - a body the adapter could not read stops that request alone, the same way, with a `blocked` event,
|
|
1549
|
+
// `unreadable_cybersource_secure_acceptance_request`, and does not hold the attachment: nothing
|
|
1550
|
+
// proves it carries no card;
|
|
1551
|
+
// - any other request (a payment with a stored payment_token and no card, a GET, a CORS preflight)
|
|
1552
|
+
// defers, and the arbiter continues it untouched.
|
|
1553
|
+
// A request the synced registry recognizes as a card request defers too, so the arbiter pauses it for
|
|
1554
|
+
// that recognizer; no build serves one today. A path outside /silent/ is not this gate's. Synchronous
|
|
1555
|
+
// until it must stop a request, like every gate. Exported for the gate registry's tests.
|
|
1556
|
+
export const cybersourceSecureAcceptancePreClaim = (ctx) => {
|
|
1557
|
+
const { url, method } = ctx.request;
|
|
1558
|
+
if (!isCybersourceSecureAcceptanceUrl(url) || ctx.vault.isCardRequest(url, method))
|
|
1559
|
+
return undefined;
|
|
1560
|
+
// A reader that throws (Playwright's postData() can) reads as a body the adapter could not read.
|
|
1561
|
+
const { body, contentType } = cardEndpointPausedBody(ctx);
|
|
1562
|
+
const carried = cybersourceSecureAcceptanceRequest(url, method, body, contentType);
|
|
1563
|
+
if (carried === 'no_card')
|
|
1564
|
+
return undefined;
|
|
1565
|
+
return carried === 'card' ? cybersourceSecureAcceptanceUnsupported(ctx) : cybersourceSecureAcceptanceUnreadable(ctx);
|
|
1566
|
+
};
|
|
1567
|
+
async function cybersourceSecureAcceptanceUnsupported(ctx) {
|
|
1568
|
+
const { url, method } = ctx.request;
|
|
1569
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
1570
|
+
ctx.lifecycle.unsupported(CYBERSOURCE_SECURE_ACCEPTANCE_REASON);
|
|
1571
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: CYBERSOURCE_SECURE_ACCEPTANCE_REASON } });
|
|
1572
|
+
return cybersourceSecureAcceptanceStop(ctx);
|
|
1573
|
+
}
|
|
1574
|
+
async function cybersourceSecureAcceptanceUnreadable(ctx) {
|
|
1575
|
+
ctx.onEvent?.({ type: 'blocked', detail: 'unreadable_cybersource_secure_acceptance_request' });
|
|
1576
|
+
return cybersourceSecureAcceptanceStop(ctx);
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Stop a Secure Acceptance post: a form navigation gets the stopped page, and if the page cannot be
|
|
1580
|
+
* answered, or the request is not a navigation, it fails. Either way the card never leaves.
|
|
1581
|
+
*/
|
|
1582
|
+
async function cybersourceSecureAcceptanceStop(ctx) {
|
|
1583
|
+
let navigation = false;
|
|
1584
|
+
try {
|
|
1585
|
+
navigation = ctx.request.navigation === true;
|
|
1586
|
+
}
|
|
1587
|
+
catch {
|
|
1588
|
+
navigation = false;
|
|
1589
|
+
}
|
|
1590
|
+
if (navigation) {
|
|
1591
|
+
try {
|
|
1592
|
+
return await stopNavigationBeforeSend(ctx, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, 'request_unsupported');
|
|
1593
|
+
}
|
|
1594
|
+
catch { /* the page could not be answered: fail the request below */ }
|
|
1595
|
+
}
|
|
1596
|
+
await ctx.effects.abort().catch(() => { });
|
|
1597
|
+
return { kind: 'abort' };
|
|
1598
|
+
}
|
|
1533
1599
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
1534
1600
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
1535
1601
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -1563,7 +1629,8 @@ async function unsupportedContinue(ctx) {
|
|
|
1563
1629
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
1564
1630
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
1565
1631
|
* pauses each one itself. */
|
|
1566
|
-
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, paypalHostedFieldsPreClaim
|
|
1632
|
+
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, paypalHostedFieldsPreClaim,
|
|
1633
|
+
cybersourceSecureAcceptancePreClaim];
|
|
1567
1634
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
1568
1635
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
1569
1636
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|