@agent-cards/sdk 0.23.27 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +137 -99
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
- package/dist/adyen-merchant-hosted.generated.js +459 -21
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +1398 -106
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +231 -175
- package/dist/client.d.ts +104 -22
- package/dist/client.js +215 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/datatrans.generated.js +22 -1
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/juspay-gate.js +2 -2
- package/dist/juspay.generated.d.ts +2 -2
- package/dist/juspay.generated.js +174 -4
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +105 -10
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +27 -7
- package/dist/payrexx.generated.js +176 -2
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +86 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +46 -5
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.d.ts +0 -4
- package/dist/preflight.generated.js +24 -1
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +1418 -120
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +105 -10
- package/dist/stripe-checkout.generated.js +1405 -234
- package/dist/substitutions.generated.js +460 -26
- package/dist/tap-payments.generated.js +105 -10
- package/dist/twoc2p.generated.js +105 -10
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/dist/wspay.generated.d.ts +1 -1
- package/dist/wspay.generated.js +175 -3
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
|
@@ -1,11 +1,46 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 4acbca9604c5eee50f5f73dc3dea5ab0d40c9cba1214ccda0d2074271b3554a5
|
|
4
4
|
// src/json-path.js
|
|
5
5
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
6
6
|
function isPlainObject(v) {
|
|
7
7
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8
8
|
}
|
|
9
|
+
function isTextStep(step) {
|
|
10
|
+
return isPlainObject(step) && Object.keys(step).length === 1 && step.text === "json";
|
|
11
|
+
}
|
|
12
|
+
function spellJson(parsed, form) {
|
|
13
|
+
const text = JSON.stringify(parsed);
|
|
14
|
+
if (form !== "php")
|
|
15
|
+
return text;
|
|
16
|
+
return text.replace(/\//g, "\\/").replace(/[\u0080-\uffff]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
17
|
+
}
|
|
18
|
+
function jsonTextForm(value) {
|
|
19
|
+
if (typeof value !== "string" || !/^[[{]/.test(value))
|
|
20
|
+
return void 0;
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(value);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
if (parsed === null || typeof parsed !== "object")
|
|
29
|
+
return void 0;
|
|
30
|
+
for (const form of ["js", "php"]) {
|
|
31
|
+
try {
|
|
32
|
+
if (spellJson(parsed, form) === value)
|
|
33
|
+
return { parsed, form };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
function jsonText(value) {
|
|
42
|
+
return jsonTextForm(value)?.parsed;
|
|
43
|
+
}
|
|
9
44
|
function jsonAt(root, path) {
|
|
10
45
|
let node = root;
|
|
11
46
|
for (const step of path) {
|
|
@@ -36,7 +71,12 @@ function valuesIgnoringCase(root, path) {
|
|
|
36
71
|
for (const step of path) {
|
|
37
72
|
const next = [];
|
|
38
73
|
for (const node of nodes) {
|
|
39
|
-
if (
|
|
74
|
+
if (isTextStep(step)) {
|
|
75
|
+
const inner = jsonText(node);
|
|
76
|
+
if (inner !== void 0)
|
|
77
|
+
next.push(inner);
|
|
78
|
+
}
|
|
79
|
+
else if (typeof step === "number") {
|
|
40
80
|
if (Array.isArray(node) && Number.isSafeInteger(step) && step >= 0 && step < node.length)
|
|
41
81
|
next.push(node[step]);
|
|
42
82
|
}
|
|
@@ -51,7 +91,7 @@ function valuesIgnoringCase(root, path) {
|
|
|
51
91
|
return nodes;
|
|
52
92
|
}
|
|
53
93
|
function describePath(path) {
|
|
54
|
-
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
94
|
+
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : isTextStep(step) ? "<json>" : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
55
95
|
}
|
|
56
96
|
// src/url-template.js
|
|
57
97
|
var PLACEHOLDER_VALUE = /^[A-Za-z0-9._~@+-]{1,256}$/;
|
|
@@ -157,6 +197,192 @@ var SubstitutionError = class extends Error {
|
|
|
157
197
|
this.name = "SubstitutionError";
|
|
158
198
|
}
|
|
159
199
|
};
|
|
200
|
+
// src/three-ds-step.js
|
|
201
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
202
|
+
var PERCENT_LAYERS = 8;
|
|
203
|
+
function percentDecoded(text) {
|
|
204
|
+
let out = text;
|
|
205
|
+
for (let layer = 0; layer < PERCENT_LAYERS; layer += 1) {
|
|
206
|
+
const next = out.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
207
|
+
if (next === out)
|
|
208
|
+
break;
|
|
209
|
+
out = next;
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
var NAMED = /* @__PURE__ */ Object.freeze({
|
|
214
|
+
amp: "&",
|
|
215
|
+
percnt: "%",
|
|
216
|
+
num: "#",
|
|
217
|
+
quest: "?",
|
|
218
|
+
semi: ";",
|
|
219
|
+
excl: "!",
|
|
220
|
+
dollar: "$",
|
|
221
|
+
lpar: "(",
|
|
222
|
+
rpar: ")",
|
|
223
|
+
ast: "*",
|
|
224
|
+
comma: ",",
|
|
225
|
+
commat: "@",
|
|
226
|
+
lsqb: "[",
|
|
227
|
+
rsqb: "]",
|
|
228
|
+
lcub: "{",
|
|
229
|
+
rcub: "}",
|
|
230
|
+
verbar: "|",
|
|
231
|
+
bsol: "\\",
|
|
232
|
+
hat: "^",
|
|
233
|
+
grave: "`",
|
|
234
|
+
plus: "+",
|
|
235
|
+
sol: "/",
|
|
236
|
+
equals: "=",
|
|
237
|
+
lowbar: "_",
|
|
238
|
+
period: ".",
|
|
239
|
+
colon: ":",
|
|
240
|
+
quot: '"',
|
|
241
|
+
apos: "'",
|
|
242
|
+
lt: "<",
|
|
243
|
+
gt: ">"
|
|
244
|
+
});
|
|
245
|
+
var NUMERIC = "#(?:[xX]([0-9a-fA-F]+)|([0-9]+));?";
|
|
246
|
+
var BARE_NAME = "(sup[123]|frac(?:12|14|34)|[A-Za-z]+);?";
|
|
247
|
+
var referenceText = (hex, decimal, name) => {
|
|
248
|
+
if (name !== void 0)
|
|
249
|
+
return Object.prototype.hasOwnProperty.call(NAMED, name.toLowerCase()) ? NAMED[name.toLowerCase()] : " ";
|
|
250
|
+
const point = hex === void 0 ? parseInt(decimal, 10) : parseInt(hex, 16);
|
|
251
|
+
return point <= 1114111 ? String.fromCodePoint(point) : "";
|
|
252
|
+
};
|
|
253
|
+
function referenceReadings(text) {
|
|
254
|
+
const read = (pattern) => text.replace(new RegExp(`&(?:${NUMERIC}|${pattern})`, "g"), (_, hex, decimal, name) => referenceText(hex, decimal, name));
|
|
255
|
+
return [read("([A-Za-z][A-Za-z0-9]*);"), read(BARE_NAME)];
|
|
256
|
+
}
|
|
257
|
+
function readings(text) {
|
|
258
|
+
const out = /* @__PURE__ */ new Set([text, percentDecoded(text)]);
|
|
259
|
+
for (const reading of [...out])
|
|
260
|
+
for (const decoded of referenceReadings(reading))
|
|
261
|
+
out.add(decoded).add(percentDecoded(decoded));
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
function base64Bytes(run, skip) {
|
|
265
|
+
let bits = 0;
|
|
266
|
+
let value = 0;
|
|
267
|
+
let out = "";
|
|
268
|
+
for (let i = skip; i < run.length; i += 1) {
|
|
269
|
+
const char = run[i];
|
|
270
|
+
value = value << 6 | (char === "-" ? 62 : char === "_" ? 63 : ALPHABET.indexOf(char));
|
|
271
|
+
bits += 6;
|
|
272
|
+
if (bits >= 8) {
|
|
273
|
+
bits -= 8;
|
|
274
|
+
out += String.fromCharCode(value >> bits);
|
|
275
|
+
value &= (1 << bits) - 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
var base64Text = (value) => base64Bytes(value.replace(/=+$/, ""), 0);
|
|
281
|
+
// src/test-pans.js
|
|
282
|
+
function immutableSet(values, name) {
|
|
283
|
+
const set = new Set(values);
|
|
284
|
+
for (const method of ["add", "delete", "clear"]) {
|
|
285
|
+
set[method] = () => {
|
|
286
|
+
throw new TypeError(`${name} is immutable`);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return Object.freeze(set);
|
|
290
|
+
}
|
|
291
|
+
var DOCUMENTED_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
292
|
+
"4242424242424242",
|
|
293
|
+
"4000000000000002",
|
|
294
|
+
"4000002500003155",
|
|
295
|
+
"4111111111111111"
|
|
296
|
+
], "DOCUMENTED_TEST_PANS");
|
|
297
|
+
// src/answer-card-leak.js
|
|
298
|
+
var ANSWER_HOLDS_CARD = "card_in_answer";
|
|
299
|
+
var LAYERS = 2;
|
|
300
|
+
var BASE64_RUN = /[A-Za-z0-9+/_-]{6,}/g;
|
|
301
|
+
var BASE64_PIECE = /[A-Za-z0-9+/_-]{2,}={0,2}/g;
|
|
302
|
+
var VALUE_END = /["'<>&=,;:{}[\]|\r\n\t]/;
|
|
303
|
+
var DECIMAL = /\p{Nd}/u;
|
|
304
|
+
var PIECE = 4;
|
|
305
|
+
function digitValue(char) {
|
|
306
|
+
const point = char.codePointAt(0);
|
|
307
|
+
let first = point;
|
|
308
|
+
while (DECIMAL.test(String.fromCodePoint(first - 1)))
|
|
309
|
+
first -= 1;
|
|
310
|
+
return String((point - first) % 10);
|
|
311
|
+
}
|
|
312
|
+
var ENCLOSED = /* @__PURE__ */ Object.freeze([[9451, 10, 11], [9461, 10, 1], [9471, 1, 0], [10102, 30, 1], [127243, 2, 0]]);
|
|
313
|
+
var ENCLOSED_CHAR = /[\u24eb-\u24ff\u2776-\u2793\u{1f10b}\u{1f10c}]/gu;
|
|
314
|
+
function enclosedValue(char) {
|
|
315
|
+
const point = char.codePointAt(0);
|
|
316
|
+
const [first, , start] = ENCLOSED.find(([from, size]) => point >= from && point < from + size);
|
|
317
|
+
return String(first === 127243 ? 0 : first === 10102 ? (point - first) % 10 + 1 : start + point - first);
|
|
318
|
+
}
|
|
319
|
+
var asciiDigits = (text) => (typeof text.normalize === "function" ? text.normalize("NFKC") : text).replace(ENCLOSED_CHAR, enclosedValue).replace(/\p{Nd}/gu, (char) => char >= "0" && char <= "9" ? char : digitValue(char));
|
|
320
|
+
var unescaped = (text) => text.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
321
|
+
var decodedInPlace = (text) => text.replace(BASE64_PIECE, (piece) => {
|
|
322
|
+
const bare = piece.replace(/=+$/, "");
|
|
323
|
+
for (let skip = 0; skip < 4 && skip < bare.length - 1; skip += 1) {
|
|
324
|
+
const bytes = base64Text(bare.slice(skip));
|
|
325
|
+
if (/^[\x20-\x7e]+$/.test(bytes))
|
|
326
|
+
return bare.slice(0, skip) + bytes;
|
|
327
|
+
}
|
|
328
|
+
return piece;
|
|
329
|
+
});
|
|
330
|
+
function collect(text, layers, streams, runs) {
|
|
331
|
+
const base = /* @__PURE__ */ new Set([...readings(text), ...readings(unescaped(text))]);
|
|
332
|
+
for (const reading of /* @__PURE__ */ new Set([...base, ...[...base].map(decodedInPlace)])) {
|
|
333
|
+
const digits = asciiDigits(reading);
|
|
334
|
+
const sequence = digits.match(/[0-9]+/g) ?? [];
|
|
335
|
+
streams.add(sequence);
|
|
336
|
+
for (const run of sequence)
|
|
337
|
+
runs.add(run);
|
|
338
|
+
for (const value of digits.split(VALUE_END))
|
|
339
|
+
if (/[0-9]/.test(value))
|
|
340
|
+
runs.add(value.replace(/[^0-9]/g, ""));
|
|
341
|
+
if (layers === 0)
|
|
342
|
+
continue;
|
|
343
|
+
for (const run of new Set(reading.match(BASE64_RUN))) {
|
|
344
|
+
for (let skip = 0; skip < 4; skip += 1)
|
|
345
|
+
collect(base64Text(run.slice(skip)), layers - 1, streams, runs);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function joined(target, runs, shown) {
|
|
350
|
+
const list = [...runs];
|
|
351
|
+
const hidden = (part, run) => !shown.has(part) && !shown.has(run);
|
|
352
|
+
const rest = (p, used, hides) => list.some((run) => !used.includes(run) && (run.startsWith(target.slice(p)) && (hides || hidden(target.slice(p), run)) || run.length >= PIECE && p + run.length <= target.length - PIECE && target.startsWith(run, p) && rest(p + run.length, [...used, run], hides || hidden(run, run))));
|
|
353
|
+
return list.some((first) => {
|
|
354
|
+
for (let p = PIECE; p <= target.length - PIECE && p <= first.length; p += 1) {
|
|
355
|
+
if (first.endsWith(target.slice(0, p)) && rest(p, [first], hidden(target.slice(0, p), first)))
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function inOrder(targets, stream, shown) {
|
|
362
|
+
const digits = stream.join("");
|
|
363
|
+
if (digits.length < targets[targets.length - 1].length)
|
|
364
|
+
return false;
|
|
365
|
+
const hidden = stream.map((run) => (shown.has(run) ? "0" : "1").repeat(run.length)).join("");
|
|
366
|
+
return targets.some((target) => {
|
|
367
|
+
for (let at = digits.indexOf(target); at >= 0; at = digits.indexOf(target, at + 1)) {
|
|
368
|
+
if (hidden.slice(at, at + target.length).includes("1"))
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function answerCardLeak(card, texts) {
|
|
375
|
+
const number = card && (typeof card.number === "string" || typeof card.number === "number") ? String(card.number).replace(/[^0-9]/g, "") : "";
|
|
376
|
+
if (number.length < 12 || number.length > 19 || DOCUMENTED_TEST_PANS.has(number))
|
|
377
|
+
return null;
|
|
378
|
+
const streams = /* @__PURE__ */ new Set();
|
|
379
|
+
const runs = /* @__PURE__ */ new Set();
|
|
380
|
+
for (const text of Array.isArray(texts) ? texts : [texts])
|
|
381
|
+
collect(typeof text === "string" ? text : JSON.stringify(text) ?? "", LAYERS, streams, runs);
|
|
382
|
+
const shown = new Set([6, 8].flatMap((bin) => [number.slice(0, bin), number.slice(0, bin) + number.slice(-4)]).concat(number.slice(-4)));
|
|
383
|
+
const targets = [number, number.slice(0, -1)];
|
|
384
|
+
return [...streams].some((stream) => inOrder(targets, stream, shown)) || targets.some((target) => joined(target, runs, shown)) ? ANSWER_HOLDS_CARD : null;
|
|
385
|
+
}
|
|
160
386
|
// src/deep-freeze.js
|
|
161
387
|
function deepFreeze(value) {
|
|
162
388
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -2421,9 +2647,11 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
|
2421
2647
|
"www.nike.com guest checkout, read live on Oct 23, 2026 in a Kernel browser with every non-GET request aborted before it left (a placeholder card, nothing sent): the card is typed into Nike's own card frame, https://paymentcc.nike.com/services/default?id=<creditCardInfoId>&ctx=…&language=…&maskerEnabled=…, a form #creditCardForm with #creditCardNumber, #expirationDate (MM/YY) and #cvNumber and hidden expirationMonth and expirationYear. No processor frame, tokenizer or client-side encryption is on the page.",
|
|
2422
2648
|
"The frame's own script (/resources/js/common.js) posts the card once all three fields are valid, on a field's blur or keyup, with no button press: XHR POST /creditcardsubmit/<creditCardInfoId>/store, Content-Type application/json; charset=utf-8, body {accountNumber (digits), cardType, expirationMonth (MM), expirationYear ('20' + YY), creditCardInfoId (the frame's id query value, the same as the path), cvNumber}. The captured request carried the .nike.com cookies (Akamai's ak_bmsc and bm_sv among them), Origin https://paymentcc.nike.com, and no CSRF or auth header.",
|
|
2423
2649
|
"cardType comes from the frame's own validator table, first match wins: AMERICANEXPRESS (34, 37), CARTASI (a list of Italian 4 and 5 BINs), CARTEBLEUE, DANKORT, DISCOVER, DINERS, DOMESTICMAESTRO, INTERNATIONALMAESTRO, JCB, MASTERCARD (51-55 and 2221-2720), VISAELECTRON, VISA (4).",
|
|
2424
|
-
"After the post the frame asks GET /creditcardsubmit/<id>/isValid?mode=1 and tells the page its brand, last four and expiry by postMessage. A CVV-only change sends PUT /creditcardsubmit/<id>/update {cvNumber}. Continue to Order Review then posts https://api.nike.com/payment/preview/v3 with paymentInfo naming the creditCardInfoId, the brand, the last four and a masked accountNumber, beside the checkout total and currency: no card number leaves the page outside the store request."
|
|
2650
|
+
"After the post the frame asks GET /creditcardsubmit/<id>/isValid?mode=1 and tells the page its brand, last four and expiry by postMessage. A CVV-only change sends PUT /creditcardsubmit/<id>/update {cvNumber}. Continue to Order Review then posts https://api.nike.com/payment/preview/v3 with paymentInfo naming the creditCardInfoId, the brand, the last four and a masked accountNumber, beside the checkout total and currency: no card number leaves the page outside the store request.",
|
|
2651
|
+
"Felipe Abello's recorded www.nike.com guest purchase with a one-time Agentcard Mastercard, Oct 23, 2026 (he reviewed the HAR and sent its facts as text; no card value, cookie or HAR entry is in this table): the store post answered 201 Created with an empty body (Content-Length 0), its path and body naming one creditCardInfoId UUID; isValid?mode=1 answered 200 with the JSON booleans cc, cvv, exp and isValid; payment/preview/v3 named the card MASTERCARD in paymentInfo[0].",
|
|
2652
|
+
"The same purchase: a checkout preview job (GET /buy/checkout_previews_jobs/v3/…, the rest of that line not recorded) prices the order, and the page sends the total twice, both after the store post: in payment/preview/v3 (a top-level total and currency, and paymentInfo[] naming the creditCardInfoId), a pre-check that answered validForCheckout true and is3DSRequired false and charges nothing, and in PUT https://api.nike.com/buy/checkouts/v3/<checkoutId> (request.totals.total, a paymentToken, no card data), which answers 202 with a PENDING job. GET /buy/checkouts_jobs/v3/<checkoutId> then read COMPLETED with an orderId, a paymentApprovalId and paymentStatus ACCEPT: that PUT authorizes the card."
|
|
2425
2653
|
],
|
|
2426
|
-
har:
|
|
2654
|
+
har: "Felipe Abello, Oct 23, 2026: one www.nike.com guest purchase he ran and recorded with a one-time Agentcard Mastercard and reviewed himself, its facts sent as text. It confirms the store post, its answer, isValid, the Mastercard code and the order calls after it. It ran in his own browser, so it shows nothing of how Akamai treats the relay's request.",
|
|
2427
2655
|
checkout: "https://www.nike.com",
|
|
2428
2656
|
checkout_frame: false,
|
|
2429
2657
|
frame: "https://paymentcc.nike.com",
|
|
@@ -2447,7 +2675,10 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
|
2447
2675
|
expiry: null,
|
|
2448
2676
|
last4: null,
|
|
2449
2677
|
leading: null,
|
|
2450
|
-
|
|
2678
|
+
// The recorded purchase shows MASTERCARD only. The frame's table writes other codes for other networks, but no
|
|
2679
|
+
// purchase shows Nike taking one, so the device refuses a card of another network and the body rules refuse a
|
|
2680
|
+
// placeholder the frame names otherwise: the agent types a Mastercard-range placeholder.
|
|
2681
|
+
brands: [{ at: ["cardType"], map: { mastercard: "MASTERCARD" } }],
|
|
2451
2682
|
bins: null
|
|
2452
2683
|
},
|
|
2453
2684
|
merchant_key: null,
|
|
@@ -2457,9 +2688,12 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
|
2457
2688
|
],
|
|
2458
2689
|
refuse: [],
|
|
2459
2690
|
payment_method: null,
|
|
2460
|
-
// The store
|
|
2691
|
+
// The store post carries no total: Nike's total travels after it, in payment/preview/v3 and the order's PUT, which the
|
|
2692
|
+
// checkout SDK holds to the approved amount (merchant-card-endpoint-nike-order.js).
|
|
2461
2693
|
amount: null,
|
|
2462
2694
|
currency: "USD",
|
|
2695
|
+
// Nike answers the store post 201 Created with an empty body: the card is stored under the creditCardInfoId and nothing
|
|
2696
|
+
// is charged until the order's PUT. The approve records the row approved on that answer, which means sent, never paid.
|
|
2463
2697
|
response: { headers: ["content-type"], withhold: [] },
|
|
2464
2698
|
resends: [
|
|
2465
2699
|
// The frame's CVV-only update, which sends the typed security code again outside the store request.
|
|
@@ -2472,12 +2706,10 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
|
2472
2706
|
timeout_ms: 3e4,
|
|
2473
2707
|
charge_timing: "deferred",
|
|
2474
2708
|
once_per: { path: "creditCardInfoId" },
|
|
2709
|
+
// What keeps Nike in observe. What only a purchase through the relay can show (Akamai's view of the relay's request,
|
|
2710
|
+
// a capture when the order ships) and the codes of other networks are open in PROOF.md, beside the attended purchase.
|
|
2475
2711
|
pending: [
|
|
2476
|
-
|
|
2477
|
-
"a total to hold the agent's amount to: the store request names none, and Nike's checkout total travels only in payment/preview/v3 after it",
|
|
2478
|
-
"which call authorizes the card (payment/preview/v3 or the order submit), and whether the charge when the order ships needs the card to stay open",
|
|
2479
|
-
"a card the frame names CARTASI, CARTEBLEUE, DANKORT or a Maestro type, which the brand map writes as its network's code",
|
|
2480
|
-
"whether Akamai accepts the relay's request with cookies the agent's browser minted"
|
|
2712
|
+
`a total to hold the agent's amount to, in the form Nike's order writes it: the checkout SDK holds the order's PUT to the approved amount (merchant-card-endpoint-nike-order.js), reading request.totals.total as a JSON number and request.currency, and refuses any other form. The recorded purchase's facts name that path and an amount ending "4.76 USD", and the paste cut the JSON type and where the order names its currency, which a person confirms from the recorded purchase`
|
|
2481
2713
|
]
|
|
2482
2714
|
}
|
|
2483
2715
|
]);
|
|
@@ -3285,6 +3517,96 @@ function merchantCardEndpointFactsRefusal(profile, facts, agent) {
|
|
|
3285
3517
|
}
|
|
3286
3518
|
return null;
|
|
3287
3519
|
}
|
|
3520
|
+
// src/merchant-card-endpoint-nike-order.js
|
|
3521
|
+
var NIKE_ORDER = /* @__PURE__ */ deepFreeze({
|
|
3522
|
+
profile: "nike_paymentcc_store",
|
|
3523
|
+
host: "api.nike.com",
|
|
3524
|
+
origin: "https://api.nike.com",
|
|
3525
|
+
method: "PUT",
|
|
3526
|
+
path: "/buy/checkouts/v3/:checkoutId",
|
|
3527
|
+
total: ["request", "totals", "total"],
|
|
3528
|
+
currency: ["request", "currency"]
|
|
3529
|
+
});
|
|
3530
|
+
var NIKE_ORDER_REFUSALS = /* @__PURE__ */ deepFreeze([
|
|
3531
|
+
"request_unrecognized",
|
|
3532
|
+
"body_unreadable",
|
|
3533
|
+
"total_unreadable",
|
|
3534
|
+
"currency_unreadable",
|
|
3535
|
+
"currency_mismatch",
|
|
3536
|
+
"amount_mismatch",
|
|
3537
|
+
"approval_unknown"
|
|
3538
|
+
]);
|
|
3539
|
+
var BODY_MAX = 256 * 1024;
|
|
3540
|
+
var TOTAL_TEXT = /^\d{1,9}(?:\.\d{1,2})?$/;
|
|
3541
|
+
var READS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
3542
|
+
function nikeOrderHost(url) {
|
|
3543
|
+
try {
|
|
3544
|
+
return new URL(url).hostname.replace(/\.$/, "") === NIKE_ORDER.host;
|
|
3545
|
+
}
|
|
3546
|
+
catch {
|
|
3547
|
+
return false;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
function serverSegments(pathname) {
|
|
3551
|
+
let path = pathname;
|
|
3552
|
+
for (let round = 0; round < 4; round += 1) {
|
|
3553
|
+
let decoded;
|
|
3554
|
+
try {
|
|
3555
|
+
decoded = decodeURIComponent(path);
|
|
3556
|
+
}
|
|
3557
|
+
catch {
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
if (decoded === path)
|
|
3561
|
+
break;
|
|
3562
|
+
path = decoded;
|
|
3563
|
+
}
|
|
3564
|
+
const segments = [];
|
|
3565
|
+
for (const raw of path.replace(/\\/g, "/").split("/")) {
|
|
3566
|
+
const segment = raw.split(";")[0].toLowerCase();
|
|
3567
|
+
if (segment === "..")
|
|
3568
|
+
segments.pop();
|
|
3569
|
+
else if (segment !== "" && segment !== ".")
|
|
3570
|
+
segments.push(segment);
|
|
3571
|
+
}
|
|
3572
|
+
return segments;
|
|
3573
|
+
}
|
|
3574
|
+
function nikeOrderRequest(url, method) {
|
|
3575
|
+
if (typeof method !== "string" || READS.has(method.toUpperCase()) || !nikeOrderHost(url))
|
|
3576
|
+
return false;
|
|
3577
|
+
const segments = serverSegments(new URL(url).pathname);
|
|
3578
|
+
return segments === null || segments[0] === "buy" && segments[1] === "checkouts";
|
|
3579
|
+
}
|
|
3580
|
+
function nikeOrderRefusal(url, method, body, approved) {
|
|
3581
|
+
const want = isPlainObject(approved) ? approved.amount : void 0;
|
|
3582
|
+
const currency = isPlainObject(approved) && typeof approved.currency === "string" ? approved.currency.toLowerCase() : "";
|
|
3583
|
+
if (!Number.isSafeInteger(want) || want <= 0 || !TWO_DECIMAL_CURRENCIES.has(currency))
|
|
3584
|
+
return "approval_unknown";
|
|
3585
|
+
const match = typeof method === "string" && method.toUpperCase() === NIKE_ORDER.method ? urlTemplateMatch({ path: NIKE_ORDER.path, query: {} }, url) : null;
|
|
3586
|
+
if (match?.origin !== NIKE_ORDER.origin)
|
|
3587
|
+
return "request_unrecognized";
|
|
3588
|
+
if (typeof body !== "string" || body.length > BODY_MAX)
|
|
3589
|
+
return "body_unreadable";
|
|
3590
|
+
let parsed;
|
|
3591
|
+
try {
|
|
3592
|
+
parsed = JSON.parse(body);
|
|
3593
|
+
}
|
|
3594
|
+
catch {
|
|
3595
|
+
return "body_unreadable";
|
|
3596
|
+
}
|
|
3597
|
+
if (!isPlainObject(parsed) || !roundTrips(parsed, body))
|
|
3598
|
+
return "body_unreadable";
|
|
3599
|
+
const total = jsonAt(parsed, NIKE_ORDER.total);
|
|
3600
|
+
if (!total.found || typeof total.value !== "number" || !TOTAL_TEXT.test(String(total.value)))
|
|
3601
|
+
return "total_unreadable";
|
|
3602
|
+
const named = jsonAt(parsed, NIKE_ORDER.currency);
|
|
3603
|
+
if (!named.found || typeof named.value !== "string" || !/^[A-Z]{3}$/.test(named.value))
|
|
3604
|
+
return "currency_unreadable";
|
|
3605
|
+
if (named.value !== currency.toUpperCase())
|
|
3606
|
+
return "currency_mismatch";
|
|
3607
|
+
const [whole, fraction = ""] = String(total.value).split(".");
|
|
3608
|
+
return Number(whole) * 100 + Number(fraction.padEnd(2, "0")) === want ? null : "amount_mismatch";
|
|
3609
|
+
}
|
|
3288
3610
|
// src/merchant-card-endpoints.js
|
|
3289
3611
|
var MERCHANT_CARD_ENDPOINT_LIMITS = /* @__PURE__ */ Object.freeze({
|
|
3290
3612
|
bodyBytes: 128 * 1024,
|
|
@@ -3377,7 +3699,7 @@ function codeNearKey(text, code) {
|
|
|
3377
3699
|
return true;
|
|
3378
3700
|
for (const [run] of near.replace(BASE64_ESCAPE, unescapeBase64).matchAll(CODE_BASE64_RUN)) {
|
|
3379
3701
|
for (let skip = 0; skip < 4; skip += 1)
|
|
3380
|
-
if (code.test(
|
|
3702
|
+
if (code.test(base64Bytes2(run, skip).replace(/\0/g, "")))
|
|
3381
3703
|
return true;
|
|
3382
3704
|
}
|
|
3383
3705
|
}
|
|
@@ -4415,13 +4737,13 @@ function answerParts(response) {
|
|
|
4415
4737
|
}
|
|
4416
4738
|
return pairs;
|
|
4417
4739
|
}
|
|
4418
|
-
var
|
|
4740
|
+
var BASE64_RUN2 = /[A-Za-z0-9+/_-]{8,}/g;
|
|
4419
4741
|
var STREAM_RUN_LENGTH = 16;
|
|
4420
4742
|
var CODE_BASE64_RUN = /[A-Za-z0-9+/_-]{4,}/g;
|
|
4421
4743
|
var BASE64_ESCAPE = /\\\/|\\u002[bBfF]|%2[bBfF]|&#(?:43|47|x2[bBfF]);/g;
|
|
4422
4744
|
var BASE64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4423
4745
|
var unescapeBase64 = (escape) => /43|[bB]/.test(escape) ? "+" : "/";
|
|
4424
|
-
function
|
|
4746
|
+
function base64Bytes2(run, skip) {
|
|
4425
4747
|
let bits = 0;
|
|
4426
4748
|
let value = 0;
|
|
4427
4749
|
let out = "";
|
|
@@ -4452,9 +4774,9 @@ function carriesCard(text, facts, pan, code, depth, seen) {
|
|
|
4452
4774
|
return true;
|
|
4453
4775
|
if (depth === 0)
|
|
4454
4776
|
return false;
|
|
4455
|
-
for (const [run] of text.replace(BASE64_ESCAPE, unescapeBase64).matchAll(
|
|
4777
|
+
for (const [run] of text.replace(BASE64_ESCAPE, unescapeBase64).matchAll(BASE64_RUN2)) {
|
|
4456
4778
|
for (let skip = 0; skip < 4; skip += 1) {
|
|
4457
|
-
const bytes =
|
|
4779
|
+
const bytes = base64Bytes2(run, skip);
|
|
4458
4780
|
if (run.length >= STREAM_RUN_LENGTH && compressedStream(bytes))
|
|
4459
4781
|
seen.compressed = true;
|
|
4460
4782
|
if (carriesCard(bytes.replace(/\0/g, ""), facts, pan, code, depth - 1, seen))
|
|
@@ -4468,10 +4790,13 @@ function responseCardLeak(card, response) {
|
|
|
4468
4790
|
const pan = digitRun(facts.number);
|
|
4469
4791
|
const code = codeRun(facts.cvc);
|
|
4470
4792
|
const seen = { compressed: false };
|
|
4471
|
-
|
|
4793
|
+
const texts = [response.bodyText, ...answerParts(response).flat()];
|
|
4794
|
+
for (const text of texts) {
|
|
4472
4795
|
if (carriesCard(text, facts, pan, code, 2, seen))
|
|
4473
4796
|
return "card_in_response";
|
|
4474
4797
|
}
|
|
4798
|
+
if (answerCardLeak(facts, texts))
|
|
4799
|
+
return "card_in_response";
|
|
4475
4800
|
return seen.compressed ? "unreadable_in_response" : null;
|
|
4476
4801
|
}
|
|
4477
4802
|
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
|
@@ -4532,4 +4857,4 @@ function merchantCardEndpointNavigationAnswer(profile, answer, requestUrl) {
|
|
|
4532
4857
|
return { kind: "refused", reason: "redirect_location_invalid" };
|
|
4533
4858
|
return { kind: "redirect", status, location: target.href };
|
|
4534
4859
|
}
|
|
4535
|
-
export { HILTON_CREATE_RESERVATION_DOCUMENTS, MERCHANT_CARD_ENDPOINT_CLIENT_TABLE, MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX, MERCHANT_CARD_ENDPOINT_FACT_READERS, MERCHANT_CARD_ENDPOINT_LIMITS, MERCHANT_CARD_ENDPOINT_MODE, MERCHANT_CARD_ENDPOINT_PROFILES, MERCHANT_CARD_ENDPOINT_PSP, MERCHANT_CARD_ENDPOINT_RECOGNIZERS, SubstitutionError, assertMerchantCardEndpoint, encryptMerchantCardEndpointBody, encryptedMerchantEndpointRequest, matchMerchantCardEndpoint, matchMerchantCardEndpointResend, merchantCardEndpointAmount, merchantCardEndpointAnswer, merchantCardEndpointAnswerRefusal, merchantCardEndpointBodyRefusal, merchantCardEndpointById, merchantCardEndpointCarriesCard, merchantCardEndpointCheckoutKey, merchantCardEndpointEncrypts, merchantCardEndpointFactsRefusal, merchantCardEndpointFactsRole, merchantCardEndpointLive, merchantCardEndpointMatches, merchantCardEndpointNavigationAnswer, merchantCardEndpointPasses, merchantCardEndpointRecognizerById, merchantCardEndpointRequestOrigin, merchantCardEndpointSessionRefusal, merchantCardEndpointSessionTtl, merchantEndpointRequest, projectMerchantCardEndpointFacts, readMerchantCardEndpointFacts, responseCardLeak, substituteMerchantCardEndpoint, trustedMerchantCardEndpoint };
|
|
4860
|
+
export { HILTON_CREATE_RESERVATION_DOCUMENTS, MERCHANT_CARD_ENDPOINT_CLIENT_TABLE, MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX, MERCHANT_CARD_ENDPOINT_FACT_READERS, MERCHANT_CARD_ENDPOINT_LIMITS, MERCHANT_CARD_ENDPOINT_MODE, MERCHANT_CARD_ENDPOINT_PROFILES, MERCHANT_CARD_ENDPOINT_PSP, MERCHANT_CARD_ENDPOINT_RECOGNIZERS, NIKE_ORDER, NIKE_ORDER_REFUSALS, SubstitutionError, assertMerchantCardEndpoint, encryptMerchantCardEndpointBody, encryptedMerchantEndpointRequest, matchMerchantCardEndpoint, matchMerchantCardEndpointResend, merchantCardEndpointAmount, merchantCardEndpointAnswer, merchantCardEndpointAnswerRefusal, merchantCardEndpointBodyRefusal, merchantCardEndpointById, merchantCardEndpointCarriesCard, merchantCardEndpointCheckoutKey, merchantCardEndpointEncrypts, merchantCardEndpointFactsRefusal, merchantCardEndpointFactsRole, merchantCardEndpointLive, merchantCardEndpointMatches, merchantCardEndpointNavigationAnswer, merchantCardEndpointPasses, merchantCardEndpointRecognizerById, merchantCardEndpointRequestOrigin, merchantCardEndpointSessionRefusal, merchantCardEndpointSessionTtl, merchantEndpointRequest, nikeOrderHost, nikeOrderRefusal, nikeOrderRequest, projectMerchantCardEndpointFacts, readMerchantCardEndpointFacts, responseCardLeak, substituteMerchantCardEndpoint, trustedMerchantCardEndpoint };
|
|
@@ -35,6 +35,12 @@ export type MerchantRequestVerdict = {
|
|
|
35
35
|
};
|
|
36
36
|
/** One step of a JSON path: an object key, or an index into an array. */
|
|
37
37
|
export type JsonPathStep = string | number;
|
|
38
|
+
/** The step into a string that holds JSON text (payment-core json-path.js TEXT_STEP). */
|
|
39
|
+
export type JsonTextStep = {
|
|
40
|
+
text: 'json';
|
|
41
|
+
};
|
|
42
|
+
/** One step of a profile's holder path: a JSON path step, or the step into JSON text the body carries. */
|
|
43
|
+
export type ProfilePathStep = JsonPathStep | JsonTextStep;
|
|
38
44
|
/** A card fact the API asks to write beside the ciphertext (reserved: no profile declares one yet). */
|
|
39
45
|
export interface MerchantHostedFact {
|
|
40
46
|
path: JsonPathStep[];
|
|
@@ -51,7 +57,8 @@ export interface MerchantHostedSubstitutions {
|
|
|
51
57
|
encoding: 'json';
|
|
52
58
|
kind: 'merchant_hosted';
|
|
53
59
|
profile: string;
|
|
54
|
-
|
|
60
|
+
/** The holder: it may step into JSON text the body carries (SCHEELS' `state`); a fact's path never does. */
|
|
61
|
+
path: ProfilePathStep[];
|
|
55
62
|
fields: Record<string, string>;
|
|
56
63
|
remove: string[];
|
|
57
64
|
/** Card facts to write after the swap. No profile declares any yet, and the substitution refuses them. */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the gate refuses Nike's order: payment-core's reasons; an earlier order under the same approval refused; another
|
|
3
|
+
* order than the one already let go under it; or an approval the checkout no longer names as under way (cancelled, a
|
|
4
|
+
* new attempt, a final state, a reconciliation running).
|
|
5
|
+
*/
|
|
6
|
+
export type NikeOrderRefusal = 'request_unrecognized' | 'body_unreadable' | 'total_unreadable' | 'currency_unreadable' | 'currency_mismatch' | 'amount_mismatch' | 'approval_unknown' | 'earlier_order_refused' | 'another_order' | 'approval_not_current';
|
|
7
|
+
/** What an event may say about Nike's order: its profile and origin, never the order's URL or body. */
|
|
8
|
+
export declare const NIKE_ORDER_DETAIL: Readonly<{
|
|
9
|
+
profile: string;
|
|
10
|
+
origin: string;
|
|
11
|
+
}>;
|
|
12
|
+
/** Fetch.enable's patterns for Nike's API host in every spelling a browser sends; nikeOrderRequest is the exact check. */
|
|
13
|
+
export declare const NIKE_ORDER_PATTERNS: readonly string[];
|
|
14
|
+
/** Whether the Playwright adapter routes a URL, which it sees without its method: any URL on Nike's API host. */
|
|
15
|
+
export declare function nikeOrderRouted(url: string): boolean;
|
|
16
|
+
/** The approval Nike's order is held to on one attachment, once a card went through Nike's profile. */
|
|
17
|
+
export declare class NikeOrders {
|
|
18
|
+
private held;
|
|
19
|
+
/**
|
|
20
|
+
* The adapters are about to ask the cardholder to pay a card request to `profile`. From the create on, the device may
|
|
21
|
+
* store the card at Nike whatever this attachment then reads back, so for Nike's profile every order is held from
|
|
22
|
+
* here, and refused until the approved answer names its amount (remember). True for Nike's profile; another profile
|
|
23
|
+
* changes nothing.
|
|
24
|
+
*/
|
|
25
|
+
asking(profile: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* An approved card endpoint answer, read before it reaches the page: for Nike's profile, its approval is the one Nike's
|
|
28
|
+
* orders are held to, at the amount the API's read names (none, and every order is refused). It is the attempt
|
|
29
|
+
* asking() armed, so an order refused while the approval was on its way keeps the rest refused; only the next
|
|
30
|
+
* asking() clears that. Another profile's answer changes nothing.
|
|
31
|
+
*/
|
|
32
|
+
remember(replay: {
|
|
33
|
+
authorizationId: string;
|
|
34
|
+
profile: string;
|
|
35
|
+
approved?: {
|
|
36
|
+
amount: number;
|
|
37
|
+
currency: string;
|
|
38
|
+
};
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* The gate's verdict on a paused request, or null when it is left alone: no card went through Nike's profile here, or
|
|
42
|
+
* the request is not Nike's order. `current` says whether the checkout still names an approval as under way: once an
|
|
43
|
+
* approved answer is in, an order the checkout no longer names as under way is stopped before the rule reads it, so a
|
|
44
|
+
* late order never ends an attempt that came after. One order goes, and after it only a byte-identical retry; after
|
|
45
|
+
* one refusal every later order under that approval is refused.
|
|
46
|
+
*/
|
|
47
|
+
judge(url: string, method: string, body: () => string | null, current: (authorizationId: string | null) => boolean): {
|
|
48
|
+
authorizationId: string | null;
|
|
49
|
+
refusal: NikeOrderRefusal | null;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Nike's order after its card request (payment-core merchant-card-endpoint-nike-order.js), Felipe's amount rule C.
|
|
2
|
+
//
|
|
3
|
+
// Nike's store post names no total: the cardholder's device stores the card under the checkout's id, and Nike charges
|
|
4
|
+
// nothing until the page sends its order, PUT https://api.nike.com/buy/checkouts/v3/<checkoutId>. So once the adapters
|
|
5
|
+
// ask the cardholder to pay a card request through Nike's profile on an attachment, they hand every write to Nike's API
|
|
6
|
+
// host to the pre-claim gate (pre-claim.ts nikeOrderGate). It lets one order go unchanged, only when its total and
|
|
7
|
+
// currency are the amount the cardholder approved and only while the checkout still names that approval as under way,
|
|
8
|
+
// and after it only a byte-identical retry. Before that, on any other attachment, and for every other request, nothing
|
|
9
|
+
// here holds anything. The order's URL and body are cardholder data: no event names them.
|
|
10
|
+
import { NIKE_ORDER as SHARED_ORDER, nikeOrderHost as sharedHost, nikeOrderRefusal as sharedRefusal, nikeOrderRequest as sharedRequest, } from './merchant-card-endpoints.generated.js';
|
|
11
|
+
const ORDER = SHARED_ORDER;
|
|
12
|
+
/** What an event may say about Nike's order: its profile and origin, never the order's URL or body. */
|
|
13
|
+
export const NIKE_ORDER_DETAIL = Object.freeze({ profile: ORDER.profile, origin: ORDER.origin });
|
|
14
|
+
/** Fetch.enable's patterns for Nike's API host in every spelling a browser sends; nikeOrderRequest is the exact check. */
|
|
15
|
+
export const NIKE_ORDER_PATTERNS = [`https://${ORDER.host}*`, `http://${ORDER.host}*`];
|
|
16
|
+
/** Whether the Playwright adapter routes a URL, which it sees without its method: any URL on Nike's API host. */
|
|
17
|
+
export function nikeOrderRouted(url) {
|
|
18
|
+
return sharedHost(url) === true;
|
|
19
|
+
}
|
|
20
|
+
/** The approval Nike's order is held to on one attachment, once a card went through Nike's profile. */
|
|
21
|
+
export class NikeOrders {
|
|
22
|
+
held = null;
|
|
23
|
+
/**
|
|
24
|
+
* The adapters are about to ask the cardholder to pay a card request to `profile`. From the create on, the device may
|
|
25
|
+
* store the card at Nike whatever this attachment then reads back, so for Nike's profile every order is held from
|
|
26
|
+
* here, and refused until the approved answer names its amount (remember). True for Nike's profile; another profile
|
|
27
|
+
* changes nothing.
|
|
28
|
+
*/
|
|
29
|
+
asking(profile) {
|
|
30
|
+
if (profile !== ORDER.profile)
|
|
31
|
+
return false;
|
|
32
|
+
this.held = { authorizationId: null, approved: null, refused: false, released: null };
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* An approved card endpoint answer, read before it reaches the page: for Nike's profile, its approval is the one Nike's
|
|
37
|
+
* orders are held to, at the amount the API's read names (none, and every order is refused). It is the attempt
|
|
38
|
+
* asking() armed, so an order refused while the approval was on its way keeps the rest refused; only the next
|
|
39
|
+
* asking() clears that. Another profile's answer changes nothing.
|
|
40
|
+
*/
|
|
41
|
+
remember(replay) {
|
|
42
|
+
if (replay.profile !== ORDER.profile)
|
|
43
|
+
return;
|
|
44
|
+
this.held = { authorizationId: replay.authorizationId, approved: replay.approved ?? null, refused: this.held?.refused ?? false, released: null };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The gate's verdict on a paused request, or null when it is left alone: no card went through Nike's profile here, or
|
|
48
|
+
* the request is not Nike's order. `current` says whether the checkout still names an approval as under way: once an
|
|
49
|
+
* approved answer is in, an order the checkout no longer names as under way is stopped before the rule reads it, so a
|
|
50
|
+
* late order never ends an attempt that came after. One order goes, and after it only a byte-identical retry; after
|
|
51
|
+
* one refusal every later order under that approval is refused.
|
|
52
|
+
*/
|
|
53
|
+
judge(url, method, body, current) {
|
|
54
|
+
const held = this.held;
|
|
55
|
+
if (!held || sharedRequest(url, method) !== true)
|
|
56
|
+
return null;
|
|
57
|
+
const verdict = (refusal) => ({ authorizationId: held.authorizationId, refusal });
|
|
58
|
+
if (held.refused)
|
|
59
|
+
return verdict('earlier_order_refused');
|
|
60
|
+
// While the approval is still on its way, the rule refuses every order (approval_unknown), and that stands.
|
|
61
|
+
if (held.authorizationId !== null && !current(held.authorizationId))
|
|
62
|
+
return verdict('approval_not_current');
|
|
63
|
+
let text;
|
|
64
|
+
try {
|
|
65
|
+
text = body();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
text = null;
|
|
69
|
+
}
|
|
70
|
+
const sent = held.released;
|
|
71
|
+
if (sent)
|
|
72
|
+
return verdict(sent.method === method.toUpperCase() && sent.url === url && sent.body === text ? null : 'another_order');
|
|
73
|
+
const refusal = sharedRefusal(url, method, text, held.approved);
|
|
74
|
+
if (refusal !== null) {
|
|
75
|
+
held.refused = true;
|
|
76
|
+
return verdict(refusal);
|
|
77
|
+
}
|
|
78
|
+
held.released = { method: method.toUpperCase(), url, body: text };
|
|
79
|
+
return verdict(null);
|
|
80
|
+
}
|
|
81
|
+
}
|