@agent-cards/sdk 0.23.5 → 0.23.7
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 +9 -1
- package/PREFLIGHT.md +1 -1
- package/README.md +168 -12
- package/dist/amadeus.d.ts +16 -4
- package/dist/amadeus.js +51 -16
- package/dist/cdp.js +183 -26
- package/dist/client.d.ts +22 -0
- package/dist/client.js +165 -13
- package/dist/creditguard.generated.d.ts +1 -1
- package/dist/cybersource-secure-acceptance.d.ts +142 -34
- package/dist/cybersource-secure-acceptance.generated.d.ts +66 -0
- package/dist/cybersource-secure-acceptance.generated.js +955 -0
- package/dist/cybersource-secure-acceptance.js +244 -3
- 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/index.d.ts +1 -0
- package/dist/juspay.generated.d.ts +1 -1
- package/dist/pre-claim.d.ts +3 -1
- package/dist/pre-claim.js +85 -6
- package/dist/preparation.js +15 -2
- package/dist/prepared-processor.d.ts +19 -5
- package/dist/prepared-processor.js +70 -3
- package/dist/registry.js +14 -1
- package/dist/stripe-checkout.generated.js +35 -1
- package/package.json +1 -1
|
@@ -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/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { CONVERGE_REFUSAL_REASONS } from './elavon-converge.js';
|
|
|
9
9
|
export type { ConvergeRefusalReason } from './elavon-converge.js';
|
|
10
10
|
export type { WspayRefusalReason } from './wspay.js';
|
|
11
11
|
export type { ShijiStsUnsupportedReason } from './shiji-sts.js';
|
|
12
|
+
export type { SecureAcceptanceCheckoutRefusal, SecureAcceptanceUnknownReason } from './cybersource-secure-acceptance.js';
|
|
12
13
|
export type { PausedRequest, ReplayResponse, TokenReplay, CseReplay, MerchantHostedReplay, VerifoneReplay, HostedFormReplay, CardEndpointReplay, DeviceEncryptReplay, AmountAuthority, AuthorizeInput, VaultClientOptions, AgentcardOptions, AdyenTestPlatformRefusal, MerchantTotalRefusal, PrepareCheckoutOptions, PrepareCheckoutInput, PreparedCheckout, RazorpayProcessorError, RuntimeCancelReason, ExecutionMetadata, PlaceholderCard, } from './client.js';
|
|
13
14
|
export type { MicroformField, MicroformSidecar } from './cybersource-flex.js';
|
|
14
15
|
export { attachToCdp, attachToPlaywright, corsHeadersFor, corsDecision, withCorsHeaders } from './cdp.js';
|
|
@@ -20,7 +20,7 @@ declare function juspayTokenizeAnswer(token: any): {
|
|
|
20
20
|
};
|
|
21
21
|
body: string;
|
|
22
22
|
};
|
|
23
|
-
declare function juspayTxnsTemplateRefusal(url: any, headers: any, body: any): "card_token" | "format" | "merchant_id" | "card_security_code" | "merchant_mismatch" | "juspay_endpoint" | "
|
|
23
|
+
declare function juspayTxnsTemplateRefusal(url: any, headers: any, body: any): "content_type" | "card_token" | "format" | "merchant_id" | "card_security_code" | "merchant_mismatch" | "juspay_endpoint" | "unreadable_body" | "unsupported_field" | "missing_field" | "order_id" | "routing_id" | "payment_method_type" | "redirect_after_payment" | "save_to_locker" | "tokenize" | "card_nickname" | null;
|
|
24
24
|
declare function assertJuspayTxnsTemplate(url: any, headers: any, body: any): Readonly<{
|
|
25
25
|
environment: "production" | "sandbox" | undefined;
|
|
26
26
|
host: any;
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -35,7 +35,8 @@ export type PreClaimDecision = {
|
|
|
35
35
|
} | {
|
|
36
36
|
kind: 'answer';
|
|
37
37
|
}
|
|
38
|
-
/** `payrails`: the session key a bound Payrails card save carries onto the create.
|
|
38
|
+
/** `payrails`: the session key a bound Payrails card save carries onto the create. `secureAcceptance`: a CyberSource
|
|
39
|
+
* Secure Acceptance token create the lane held, a navigation of a frame, answered with a page whatever its outcome. */
|
|
39
40
|
| {
|
|
40
41
|
kind: 'pause';
|
|
41
42
|
stripeStep: StripeStep;
|
|
@@ -43,6 +44,7 @@ export type PreClaimDecision = {
|
|
|
43
44
|
cardEndpoint?: CardEndpointProfile;
|
|
44
45
|
payrails?: PayrailsSidecar;
|
|
45
46
|
navigation?: 'top' | 'frame';
|
|
47
|
+
secureAcceptance?: true;
|
|
46
48
|
};
|
|
47
49
|
/** The adapter mechanics a gate performs. Each is awaited where the inline code
|
|
48
50
|
* awaited it; none swallow their own transport failure (a gate adds a .catch
|
package/dist/pre-claim.js
CHANGED
|
@@ -76,7 +76,7 @@ import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isP
|
|
|
76
76
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
77
77
|
import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
78
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';
|
|
79
|
+
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
80
80
|
/**
|
|
81
81
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
82
82
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -1550,20 +1550,99 @@ async function paypalHostedFieldsUnreadable(ctx) {
|
|
|
1550
1550
|
// proves it carries no card;
|
|
1551
1551
|
// - any other request (a payment with a stored payment_token and no card, a GET, a CORS preflight)
|
|
1552
1552
|
// defers, and the arbiter continues it untouched.
|
|
1553
|
-
// A request the synced registry recognizes as a card request
|
|
1554
|
-
//
|
|
1555
|
-
//
|
|
1553
|
+
// A request the synced registry recognizes as a card request is the Secure Acceptance lane's
|
|
1554
|
+
// (cybersource-secure-acceptance.ts): the API serves that recognizer only to a build that declares
|
|
1555
|
+
// cybersource_sa_token_create, for a company Agentcard turned the lane on for, and only for the exact token
|
|
1556
|
+
// create on the production or the test host. Before it pauses, the gate holds such a request to the one
|
|
1557
|
+
// shape the lane pays:
|
|
1558
|
+
// - a card post that a merchant's form submits into a frame (a navigation of a frame, as Arrow's hidden
|
|
1559
|
+
// secureAcceptanceiFrame is) pauses for the claim flow, carrying the mark that answers it with a page
|
|
1560
|
+
// whatever its outcome. With no preparation on this attachment it is not paused: it gets the stopped
|
|
1561
|
+
// page, `stopped_before_send`, with a `blocked` event, `preparation_required`, and nothing reaches the
|
|
1562
|
+
// API, since only a prepared checkout pays it;
|
|
1563
|
+
// - a card post as the page's own navigation, from a fetch or from an XHR, or one whose card field the
|
|
1564
|
+
// recognizer reads under no name of its own (another spelling of card_number, which would otherwise
|
|
1565
|
+
// continue as card-free), is stopped as the post to an unrecognized /silent/ path is;
|
|
1566
|
+
// - a body the adapter could not read is stopped alone, as above;
|
|
1567
|
+
// - a body with no card number defers, and continues untouched (the recognizer's passWithoutCard).
|
|
1568
|
+
// A path outside /silent/ is not this gate's. Synchronous until it must read the frame or stop a request,
|
|
1569
|
+
// like every gate. Exported for the gate registry's tests.
|
|
1556
1570
|
export const cybersourceSecureAcceptancePreClaim = (ctx) => {
|
|
1557
1571
|
const { url, method } = ctx.request;
|
|
1558
|
-
if (!isCybersourceSecureAcceptanceUrl(url)
|
|
1572
|
+
if (!isCybersourceSecureAcceptanceUrl(url))
|
|
1559
1573
|
return undefined;
|
|
1574
|
+
const recognized = ctx.vault.isCardRequest(url, method);
|
|
1560
1575
|
// A reader that throws (Playwright's postData() can) reads as a body the adapter could not read.
|
|
1561
1576
|
const { body, contentType } = cardEndpointPausedBody(ctx);
|
|
1562
1577
|
const carried = cybersourceSecureAcceptanceRequest(url, method, body, contentType);
|
|
1563
1578
|
if (carried === 'no_card')
|
|
1564
1579
|
return undefined;
|
|
1565
|
-
|
|
1580
|
+
if (carried === 'unreadable')
|
|
1581
|
+
return cybersourceSecureAcceptanceUnreadable(ctx);
|
|
1582
|
+
if (!recognized)
|
|
1583
|
+
return cybersourceSecureAcceptanceUnsupported(ctx);
|
|
1584
|
+
// The recognizer's own reading must find the card this gate found, or the adapter would continue the
|
|
1585
|
+
// post as card-free (passWithoutCard) with the agent's card in it.
|
|
1586
|
+
if ((ctx.vault.withoutCard?.(url, body) ?? null) !== null)
|
|
1587
|
+
return cybersourceSecureAcceptanceUnsupported(ctx);
|
|
1588
|
+
return secureAcceptanceHold(ctx);
|
|
1566
1589
|
};
|
|
1590
|
+
/**
|
|
1591
|
+
* The lane's hold on a recognized card post: a navigation of a frame pauses, marked, unless no
|
|
1592
|
+
* preparation exists on an idle attachment; anything else is stopped as unsupported.
|
|
1593
|
+
*/
|
|
1594
|
+
async function secureAcceptanceHold(ctx) {
|
|
1595
|
+
let navigation = null;
|
|
1596
|
+
try {
|
|
1597
|
+
navigation = ctx.request.navigation === true ? await boundedNavigation(ctx) : null;
|
|
1598
|
+
}
|
|
1599
|
+
catch {
|
|
1600
|
+
navigation = null;
|
|
1601
|
+
}
|
|
1602
|
+
if (!isSecureAcceptanceFramePost({ url: ctx.request.url, method: ctx.request.method, navigation }))
|
|
1603
|
+
return cybersourceSecureAcceptanceUnsupported(ctx);
|
|
1604
|
+
const idle = ctx.attachmentReady() && !ctx.stripeAttachmentBusy() && !ctx.interceptionAborted() && !ctx.terminal()
|
|
1605
|
+
&& !ctx.lifecycle.isBlocked() && !ctx.awaitingApproval();
|
|
1606
|
+
if (idle && !ctx.preparationGate.isEngaged())
|
|
1607
|
+
return secureAcceptancePreparationRequired(ctx);
|
|
1608
|
+
return { kind: 'pause', stripeStep: ctx.stripeStep, navigation: 'frame', secureAcceptance: true };
|
|
1609
|
+
}
|
|
1610
|
+
/** How long the lane waits for the adapter to say which frame a paused navigation is for. */
|
|
1611
|
+
const SECURE_ACCEPTANCE_NAVIGATION_READ_MS = 2_000;
|
|
1612
|
+
/**
|
|
1613
|
+
* Which frame a paused navigation is for, as the adapter reads it, or null when it cannot say within
|
|
1614
|
+
* SECURE_ACCEPTANCE_NAVIGATION_READ_MS. A read that does not answer (Chromium does not answer a raw CDP
|
|
1615
|
+
* frame tree read on a session whose own top-level navigation is paused) stops the post as not the
|
|
1616
|
+
* reviewed shape, rather than leaving it paused.
|
|
1617
|
+
*/
|
|
1618
|
+
async function boundedNavigation(ctx) {
|
|
1619
|
+
if (typeof ctx.navigation !== 'function')
|
|
1620
|
+
return null;
|
|
1621
|
+
let timer;
|
|
1622
|
+
const late = new Promise((resolve) => { timer = setTimeout(() => resolve(null), SECURE_ACCEPTANCE_NAVIGATION_READ_MS); });
|
|
1623
|
+
try {
|
|
1624
|
+
return (await Promise.race([ctx.navigation().catch(() => null), late])) ?? null;
|
|
1625
|
+
}
|
|
1626
|
+
finally {
|
|
1627
|
+
clearTimeout(timer);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* A held frame post with no preparation on this attachment: refused before any pause, prompt or
|
|
1632
|
+
* authorization, as a Fiserv capture is, and answered with the stopped page naming CyberSource. A later
|
|
1633
|
+
* prepare() is refused too, as after any first card request.
|
|
1634
|
+
*/
|
|
1635
|
+
async function secureAcceptancePreparationRequired(ctx) {
|
|
1636
|
+
ctx.preparationGate.observeUnprepared();
|
|
1637
|
+
ctx.lifecycle.preparationRequired();
|
|
1638
|
+
ctx.onEvent?.({ type: 'blocked', detail: 'preparation_required' });
|
|
1639
|
+
try {
|
|
1640
|
+
return await stopNavigationBeforeSend(ctx, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, 'not_approved');
|
|
1641
|
+
}
|
|
1642
|
+
catch { /* the page could not be answered: fail the request below */ }
|
|
1643
|
+
await ctx.effects.abort().catch(() => { });
|
|
1644
|
+
return { kind: 'abort' };
|
|
1645
|
+
}
|
|
1567
1646
|
async function cybersourceSecureAcceptanceUnsupported(ctx) {
|
|
1568
1647
|
const { url, method } = ctx.request;
|
|
1569
1648
|
ctx.preparationGate.invalidate('unsupported_checkout');
|
package/dist/preparation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CheckoutPreparationError } from './client.js';
|
|
2
2
|
import { amountRefusal, validAmountInput } from './client.js';
|
|
3
3
|
import { matchesPreparation, validPreparationEnvironment, preparationEndpoint, preparationBindsByShape, preparationRequestMethod, preparedRequestRefusal } from './prepared-processor.js';
|
|
4
|
+
import { SECURE_ACCEPTANCE_PSP, secureAcceptanceCheckoutProfile } from './cybersource-secure-acceptance.js';
|
|
4
5
|
/** A local, one-use rendezvous. It never starts or retries a merchant request. */
|
|
5
6
|
export class PreparationGate {
|
|
6
7
|
opts;
|
|
@@ -106,6 +107,13 @@ export class PreparationGate {
|
|
|
106
107
|
const page = new URL(this.documentUrl);
|
|
107
108
|
if (!(page.protocol === 'https:' || (page.protocol === 'http:' && page.hostname === 'localhost')) || page.username || page.password)
|
|
108
109
|
throw new CheckoutPreparationError(null, 'merchant_origin_invalid');
|
|
110
|
+
// A CyberSource Secure Acceptance checkout names the profile id its token create must carry: the one
|
|
111
|
+
// passed, or on the production host the one this build's reviewed entry for the page's origin pins
|
|
112
|
+
// (secureAcceptanceCheckoutProfile). prepareCheckout refuses a checkout that can name none, and the
|
|
113
|
+
// handle it returns must carry exactly this one.
|
|
114
|
+
const secureAcceptanceProfileId = options.psp === SECURE_ACCEPTANCE_PSP
|
|
115
|
+
? secureAcceptanceCheckoutProfile({ environment: options.environment, profileId: options.secureAcceptanceProfileId, merchantOrigin: page.origin }) ?? undefined
|
|
116
|
+
: undefined;
|
|
109
117
|
const prepared = await this.opts.vault.prepareCheckout({
|
|
110
118
|
...options, user: this.opts.user, merchant: this.opts.merchant,
|
|
111
119
|
amount: this.opts.amount, currency: this.opts.currency, cardId: this.opts.cardId,
|
|
@@ -125,8 +133,10 @@ export class PreparationGate {
|
|
|
125
133
|
this.prepared = prepared;
|
|
126
134
|
// A Fiserv preparation also names the key pin the cardholder approves (merchantProfile).
|
|
127
135
|
// One that tokenizes before Pay names the capture context its cardholder approved under.
|
|
136
|
+
// A Secure Acceptance preparation names the profile id its cardholder approved for.
|
|
128
137
|
if (prepared.psp !== options.psp || prepared.environment !== options.environment
|
|
129
138
|
|| prepared.merchantProfile !== options.merchantProfile
|
|
139
|
+
|| prepared.secureAcceptanceProfileId !== secureAcceptanceProfileId
|
|
130
140
|
|| (prepared.cybersourceFlex?.captureContext ?? null) !== (cybersourceFlex?.captureContext ?? null))
|
|
131
141
|
throw new CheckoutPreparationError(prepared.id, 'checkout_changed');
|
|
132
142
|
if (signal.aborted || this.state !== 'preparing') {
|
|
@@ -169,11 +179,14 @@ export class PreparationGate {
|
|
|
169
179
|
// A Fiserv preparation claims only a capture under the key pin it names.
|
|
170
180
|
// A CreditGuard preparation claims only a submit to the page it framed.
|
|
171
181
|
// A Worldpay eProtect preparation claims only its registration GET naming the paypageId it carries.
|
|
182
|
+
// A CyberSource Secure Acceptance preparation claims only its token create naming the profile id it
|
|
183
|
+
// carries, sent from the page it approved (the request's own Origin header names it).
|
|
172
184
|
if (Date.parse(prepared.expiresAt) <= Date.now() || !matchesPreparation(prepared, requestUrl, requestMethod, requestBody, requestHeaders)) {
|
|
173
185
|
// An Amadeus request payment-core refuses names its own reason (preparedRequestRefusal), so
|
|
174
|
-
// Air France's split expiry reads amadeus_merchant_held_expiry rather than a changed checkout
|
|
186
|
+
// Air France's split expiry reads amadeus_merchant_held_expiry rather than a changed checkout,
|
|
187
|
+
// and a Secure Acceptance frame post with no Origin reads secure_acceptance_origin_header.
|
|
175
188
|
const reason = Date.parse(prepared.expiresAt) <= Date.now() ? 'expired'
|
|
176
|
-
: preparedRequestRefusal(prepared.psp, prepared.environment, requestUrl, requestBody, requestHeaders, prepared.merchantOrigin);
|
|
189
|
+
: preparedRequestRefusal(prepared.psp, prepared.environment, requestUrl, requestBody, requestHeaders, prepared.merchantOrigin, prepared.currency);
|
|
177
190
|
this.invalidate(reason);
|
|
178
191
|
throw new CheckoutPreparationError(prepared.id, reason);
|
|
179
192
|
}
|