@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oceanpayment's hosted payment page (secure.oceanpayment.com, and test-secure.oceanpayment.com, its sandbox), as this SDK
|
|
3
|
+
* reads it.
|
|
4
|
+
*
|
|
5
|
+
* The page, where Oceanpayment's Shopify redirect app sends the buyer after Shopify's Pay now, submits its card form as one
|
|
6
|
+
* form post to /gateway/sendPay;jsessionid=<the payment's session>, the card in clear. That post is the one request this
|
|
7
|
+
* SDK pauses: the cardholder's device writes the real card into it and sends it through the checkout relay (payment-core
|
|
8
|
+
* processors/oceanpayment.js). The page's other posts to these hosts lead to or carry the card the agent typed: Cardinal's
|
|
9
|
+
* device-data call (/gateway/CreateJWTInterface) posts the whole number when the merchant's terminal turns it on, and a card
|
|
10
|
+
* post written another way would carry it to Oceanpayment as a payment. This SDK aborts every one of them in a page it
|
|
11
|
+
* watches, so the agent's placeholder never reaches Oceanpayment. A GET (the page, its scripts, its log) continues.
|
|
12
|
+
*
|
|
13
|
+
* OCEANPAYMENT_HOSTS is payment-core's OCEANPAYMENT_HOSTS, and isOceanpaymentSendPay takes the URLs payment-core's
|
|
14
|
+
* isOceanpaymentSendPayUrl takes; oceanpayment.test.mjs pins both to payment-core.
|
|
15
|
+
*/
|
|
16
|
+
export declare const OCEANPAYMENT_PSP = "oceanpayment";
|
|
17
|
+
/** The dark-launch capability the API serves Oceanpayment's recognizer behind (payment-core processors/oceanpayment.js). */
|
|
18
|
+
export declare const OCEANPAYMENT_CAPABILITY = "oceanpayment_hosted_payment_page";
|
|
19
|
+
/** The live host and Oceanpayment's sandbox, which serves the same page for a test terminal. */
|
|
20
|
+
export declare const OCEANPAYMENT_HOSTS: readonly string[];
|
|
21
|
+
/** Whether this URL is on one of Oceanpayment's two hosts. */
|
|
22
|
+
export declare function isOceanpaymentHost(url: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The only request on Oceanpayment's hosts that is the page's card post: a POST to exactly
|
|
25
|
+
* https://<host>/gateway/sendPay;jsessionid=<session>, as the page's form action writes it, with no port, query, fragment
|
|
26
|
+
* or other spelling (payment-core isOceanpaymentSendPayUrl takes the same strings). The post the SDK pauses.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isOceanpaymentSendPay(url: string, method: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether an adapter must hand this request to the pre-claim gates: it is on an Oceanpayment host and this build's
|
|
31
|
+
* registry serves Oceanpayment's card post there. Cardinal's call and a card post of another spelling are not the card
|
|
32
|
+
* post, so the registry's card-request test does not name them; the CDP adapter still pauses them through the
|
|
33
|
+
* recognizer's host pattern, and attachToPlaywright's route predicate asks this, so both adapters show the gates every
|
|
34
|
+
* request to Oceanpayment once Oceanpayment is armed.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isArmedOceanpaymentUrl(vault: {
|
|
37
|
+
isCardRequest(url: string, method?: string): boolean;
|
|
38
|
+
}, url: string): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oceanpayment's hosted payment page (secure.oceanpayment.com, and test-secure.oceanpayment.com, its sandbox), as this SDK
|
|
3
|
+
* reads it.
|
|
4
|
+
*
|
|
5
|
+
* The page, where Oceanpayment's Shopify redirect app sends the buyer after Shopify's Pay now, submits its card form as one
|
|
6
|
+
* form post to /gateway/sendPay;jsessionid=<the payment's session>, the card in clear. That post is the one request this
|
|
7
|
+
* SDK pauses: the cardholder's device writes the real card into it and sends it through the checkout relay (payment-core
|
|
8
|
+
* processors/oceanpayment.js). The page's other posts to these hosts lead to or carry the card the agent typed: Cardinal's
|
|
9
|
+
* device-data call (/gateway/CreateJWTInterface) posts the whole number when the merchant's terminal turns it on, and a card
|
|
10
|
+
* post written another way would carry it to Oceanpayment as a payment. This SDK aborts every one of them in a page it
|
|
11
|
+
* watches, so the agent's placeholder never reaches Oceanpayment. A GET (the page, its scripts, its log) continues.
|
|
12
|
+
*
|
|
13
|
+
* OCEANPAYMENT_HOSTS is payment-core's OCEANPAYMENT_HOSTS, and isOceanpaymentSendPay takes the URLs payment-core's
|
|
14
|
+
* isOceanpaymentSendPayUrl takes; oceanpayment.test.mjs pins both to payment-core.
|
|
15
|
+
*/
|
|
16
|
+
export const OCEANPAYMENT_PSP = 'oceanpayment';
|
|
17
|
+
/** The dark-launch capability the API serves Oceanpayment's recognizer behind (payment-core processors/oceanpayment.js). */
|
|
18
|
+
export const OCEANPAYMENT_CAPABILITY = 'oceanpayment_hosted_payment_page';
|
|
19
|
+
/** The live host and Oceanpayment's sandbox, which serves the same page for a test terminal. */
|
|
20
|
+
export const OCEANPAYMENT_HOSTS = Object.freeze(['secure.oceanpayment.com', 'test-secure.oceanpayment.com']);
|
|
21
|
+
const SEND_PAY = /^\/gateway\/sendPay;jsessionid=[A-Za-z0-9._!-]{16,128}$/;
|
|
22
|
+
// A session in the shape the recognizer takes, to ask whether this build's registry serves the card post on a host.
|
|
23
|
+
const PROBE_SESSION = 'ARMEDPROBESESSION0';
|
|
24
|
+
/** The Oceanpayment host a URL is on (https, no credentials, any port, any letter case, a trailing dot or not), or null. */
|
|
25
|
+
function oceanpaymentHost(raw) {
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (url.protocol !== 'https:' || url.username || url.password)
|
|
34
|
+
return null;
|
|
35
|
+
const host = url.hostname.toLowerCase().replace(/\.$/, '');
|
|
36
|
+
return OCEANPAYMENT_HOSTS.includes(host) ? host : null;
|
|
37
|
+
}
|
|
38
|
+
/** Whether this URL is on one of Oceanpayment's two hosts. */
|
|
39
|
+
export function isOceanpaymentHost(url) {
|
|
40
|
+
return oceanpaymentHost(url) !== null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The only request on Oceanpayment's hosts that is the page's card post: a POST to exactly
|
|
44
|
+
* https://<host>/gateway/sendPay;jsessionid=<session>, as the page's form action writes it, with no port, query, fragment
|
|
45
|
+
* or other spelling (payment-core isOceanpaymentSendPayUrl takes the same strings). The post the SDK pauses.
|
|
46
|
+
*/
|
|
47
|
+
export function isOceanpaymentSendPay(url, method) {
|
|
48
|
+
if (String(method).toUpperCase() !== 'POST' || typeof url !== 'string' || url.includes('?') || url.includes('#'))
|
|
49
|
+
return false;
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = new URL(url);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return parsed.href === url && parsed.protocol === 'https:' && !parsed.port && !parsed.username && !parsed.password
|
|
58
|
+
&& OCEANPAYMENT_HOSTS.includes(parsed.hostname) && SEND_PAY.test(parsed.pathname);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether an adapter must hand this request to the pre-claim gates: it is on an Oceanpayment host and this build's
|
|
62
|
+
* registry serves Oceanpayment's card post there. Cardinal's call and a card post of another spelling are not the card
|
|
63
|
+
* post, so the registry's card-request test does not name them; the CDP adapter still pauses them through the
|
|
64
|
+
* recognizer's host pattern, and attachToPlaywright's route predicate asks this, so both adapters show the gates every
|
|
65
|
+
* request to Oceanpayment once Oceanpayment is armed.
|
|
66
|
+
*/
|
|
67
|
+
export function isArmedOceanpaymentUrl(vault, url) {
|
|
68
|
+
const host = oceanpaymentHost(url);
|
|
69
|
+
return host !== null && vault.isCardRequest(`https://${host}/gateway/sendPay;jsessionid=${PROBE_SESSION}`, 'POST');
|
|
70
|
+
}
|
|
@@ -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: 4b24c682dbd8cd7bbaa0157f2932021d71f7c6802ce3814c6f3047173df29911
|
|
4
4
|
// src/sha256.js
|
|
5
5
|
var K = /* @__PURE__ */ new Uint32Array([
|
|
6
6
|
1116352408,
|
|
@@ -429,9 +429,64 @@ function isPlainObject(v) {
|
|
|
429
429
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
430
430
|
}
|
|
431
431
|
var MAX_PATH_STEPS = 16;
|
|
432
|
+
var MAX_TEXT_STEPS = 2;
|
|
433
|
+
function isTextStep(step) {
|
|
434
|
+
return isPlainObject(step) && Object.keys(step).length === 1 && step.text === "json";
|
|
435
|
+
}
|
|
436
|
+
function sameStep(a, b) {
|
|
437
|
+
return a === b || isTextStep(a) && isTextStep(b);
|
|
438
|
+
}
|
|
439
|
+
function spellJson(parsed, form) {
|
|
440
|
+
const text = JSON.stringify(parsed);
|
|
441
|
+
if (form !== "php")
|
|
442
|
+
return text;
|
|
443
|
+
return text.replace(/\//g, "\\/").replace(/[\u0080-\uffff]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
444
|
+
}
|
|
445
|
+
function jsonTextForm(value) {
|
|
446
|
+
if (typeof value !== "string" || !/^[[{]/.test(value))
|
|
447
|
+
return void 0;
|
|
448
|
+
let parsed;
|
|
449
|
+
try {
|
|
450
|
+
parsed = JSON.parse(value);
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return void 0;
|
|
454
|
+
}
|
|
455
|
+
if (parsed === null || typeof parsed !== "object")
|
|
456
|
+
return void 0;
|
|
457
|
+
for (const form of ["js", "php"]) {
|
|
458
|
+
try {
|
|
459
|
+
if (spellJson(parsed, form) === value)
|
|
460
|
+
return { parsed, form };
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return void 0;
|
|
467
|
+
}
|
|
468
|
+
function jsonText(value) {
|
|
469
|
+
return jsonTextForm(value)?.parsed;
|
|
470
|
+
}
|
|
432
471
|
function isJsonPath(path) {
|
|
433
472
|
return Array.isArray(path) && path.length <= MAX_PATH_STEPS && path.every((step) => typeof step === "string" ? step.length > 0 && !FORBIDDEN_KEYS.has(step) : Number.isSafeInteger(step) && step >= 0);
|
|
434
473
|
}
|
|
474
|
+
function isProfilePath(path) {
|
|
475
|
+
if (!Array.isArray(path) || path.length > MAX_PATH_STEPS)
|
|
476
|
+
return false;
|
|
477
|
+
let texts = 0;
|
|
478
|
+
for (const [i, step] of path.entries()) {
|
|
479
|
+
if (isTextStep(step)) {
|
|
480
|
+
texts += 1;
|
|
481
|
+
if (i === 0 || i === path.length - 1 || isTextStep(path[i - 1]) || texts > MAX_TEXT_STEPS)
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
else if (!isJsonPath([step])) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
435
490
|
function jsonAt(root, path) {
|
|
436
491
|
let node = root;
|
|
437
492
|
for (const step of path) {
|
|
@@ -446,6 +501,42 @@ function jsonAt(root, path) {
|
|
|
446
501
|
}
|
|
447
502
|
return { found: true, value: node };
|
|
448
503
|
}
|
|
504
|
+
function profileAt(root, path) {
|
|
505
|
+
if (!isProfilePath(path))
|
|
506
|
+
return { found: false };
|
|
507
|
+
let node = root;
|
|
508
|
+
for (const step of path) {
|
|
509
|
+
if (isTextStep(step)) {
|
|
510
|
+
const inner = jsonText(node);
|
|
511
|
+
if (inner === void 0)
|
|
512
|
+
return { found: false };
|
|
513
|
+
node = inner;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const at = jsonAt(node, [step]);
|
|
517
|
+
if (!at.found)
|
|
518
|
+
return { found: false };
|
|
519
|
+
node = at.value;
|
|
520
|
+
}
|
|
521
|
+
return { found: true, value: node };
|
|
522
|
+
}
|
|
523
|
+
function editAtProfilePath(root, path, edit) {
|
|
524
|
+
const at = profileAt(root, path);
|
|
525
|
+
if (!at.found || !isPlainObject(at.value))
|
|
526
|
+
return false;
|
|
527
|
+
const k = path.findIndex(isTextStep);
|
|
528
|
+
if (k === -1) {
|
|
529
|
+
edit(at.value);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
const holder = jsonAt(root, path.slice(0, k - 1)).value;
|
|
533
|
+
const key = path[k - 1];
|
|
534
|
+
const text = jsonTextForm(holder[key]);
|
|
535
|
+
if (!text || !editAtProfilePath(text.parsed, path.slice(k + 1), edit))
|
|
536
|
+
return false;
|
|
537
|
+
holder[key] = spellJson(text.parsed, text.form);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
449
540
|
function roundTrips(parsed, body) {
|
|
450
541
|
try {
|
|
451
542
|
return JSON.stringify(parsed) === body;
|
|
@@ -455,17 +546,17 @@ function roundTrips(parsed, body) {
|
|
|
455
546
|
}
|
|
456
547
|
}
|
|
457
548
|
function sameSteps(a, b) {
|
|
458
|
-
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((step, i) => step
|
|
549
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((step, i) => sameStep(step, b[i]));
|
|
459
550
|
}
|
|
460
551
|
function pathsOverlap(a, b) {
|
|
461
552
|
const n = Math.min(a.length, b.length);
|
|
462
553
|
for (let i = 0; i < n; i += 1)
|
|
463
|
-
if (a[i]
|
|
554
|
+
if (!sameStep(a[i], b[i]))
|
|
464
555
|
return false;
|
|
465
556
|
return true;
|
|
466
557
|
}
|
|
467
558
|
function describePath(path) {
|
|
468
|
-
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
559
|
+
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : isTextStep(step) ? "<json>" : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
469
560
|
}
|
|
470
561
|
// src/substitution-primitives.js
|
|
471
562
|
var SubstitutionError = class extends Error {
|
|
@@ -514,7 +605,7 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
514
605
|
const path = Array.isArray(spec.path) ? [...spec.path] : spec.path;
|
|
515
606
|
const fields = isPlainObject(spec.fields) ? Object.fromEntries(Object.entries(spec.fields)) : null;
|
|
516
607
|
const remove = Array.isArray(spec.remove) ? [...spec.remove] : spec.remove ?? [];
|
|
517
|
-
if (!
|
|
608
|
+
if (!isProfilePath(path))
|
|
518
609
|
throw new SubstitutionError("refusing substitution path");
|
|
519
610
|
if (!fields || Object.keys(fields).length === 0)
|
|
520
611
|
throw new SubstitutionError("substitutions carry no fields");
|
|
@@ -548,7 +639,7 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
548
639
|
}
|
|
549
640
|
if (!isPlainObject(parsed))
|
|
550
641
|
throw new SubstitutionError("the paused body is not a JSON object");
|
|
551
|
-
const holder =
|
|
642
|
+
const holder = profileAt(parsed, path);
|
|
552
643
|
if (!holder.found || !isPlainObject(holder.value))
|
|
553
644
|
throw new SubstitutionError(`the paused body has no object at ${describePath(path)}`);
|
|
554
645
|
const swaps = /* @__PURE__ */ new Map();
|
|
@@ -600,15 +691,19 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
600
691
|
catch {
|
|
601
692
|
throw new SubstitutionError("the substituted body is not JSON");
|
|
602
693
|
}
|
|
603
|
-
const written =
|
|
694
|
+
const written = profileAt(out, path);
|
|
604
695
|
if (!written.found || !isPlainObject(written.value) || names.some((name) => written.value[name] !== fields[name]) || dummies.some((dummy) => swapped.includes(dummy))) {
|
|
605
696
|
throw new SubstitutionError(`the substitution did not land at ${describePath(path)}`);
|
|
606
697
|
}
|
|
607
698
|
if (remove.length === 0 && set.length === 0)
|
|
608
699
|
return swapped;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
700
|
+
if (remove.length > 0 && !editAtProfilePath(out, path, (object) => {
|
|
701
|
+
for (const key of remove)
|
|
702
|
+
if (Object.prototype.hasOwnProperty.call(object, key))
|
|
703
|
+
delete object[key];
|
|
704
|
+
})) {
|
|
705
|
+
throw new SubstitutionError(`the substitution did not land at ${describePath(path)}`);
|
|
706
|
+
}
|
|
612
707
|
for (const entry of set) {
|
|
613
708
|
const parent = jsonAt(out, entry.path.slice(0, -1));
|
|
614
709
|
if (!parent.found || !isPlainObject(parent.value))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayPlug's hosted payment page (secure.payplug.com, where French merchants send a buyer who pays by card), as this SDK
|
|
3
|
+
* reads it.
|
|
4
|
+
*
|
|
5
|
+
* The merchant's server creates the payment with PayPlug and sends the buyer's browser to the page's URL PayPlug answered
|
|
6
|
+
* with, https://secure.payplug.com/pay/<payment id>, or /pay/test/<payment id> for a test payment, by a redirect or by
|
|
7
|
+
* setting the page's location. PayPlug's Shopify app does the same from Shopify's checkout. That GET carries no card, no
|
|
8
|
+
* amount and no merchant, and it is the one request a build that declares PayPlug's capability pauses (hosted-entry.ts):
|
|
9
|
+
* the cardholder's device repeats it over the end-to-end TLS relay and pays on the page PayPlug returns to it (payment-core
|
|
10
|
+
* processors/payplug.js). Every step toward paying on that page is a POST to PayPlug's host, the page's card post among
|
|
11
|
+
* them, with the card encrypted under the page's own key.
|
|
12
|
+
*
|
|
13
|
+
* PAYPLUG_HOST is payment-core's PAYPLUG_HOST, and isPayplugEntryUrl is payment-core's isPayplugEntryUrl; payplug.test.mjs
|
|
14
|
+
* pins both to payment-core on every case payment-core's own tests list.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PAYPLUG_PSP = "payplug";
|
|
17
|
+
/** The dark-launch capability the API serves PayPlug's recognizer behind (payment-core processors/payplug.js). */
|
|
18
|
+
export declare const PAYPLUG_CAPABILITY = "payplug_hosted_payment_page";
|
|
19
|
+
/** PayPlug's page host, which its test and live payments share; the entry's path says which one a payment is. */
|
|
20
|
+
export declare const PAYPLUG_HOST = "secure.payplug.com";
|
|
21
|
+
/**
|
|
22
|
+
* Whether a URL is the payment page's URL exactly as PayPlug writes payment_url: https, secure.payplug.com in lower case,
|
|
23
|
+
* the default port, no credentials, no query and no fragment, not even an empty one, and the path /pay/<id> or
|
|
24
|
+
* /pay/test/<id> with the payment's 22-character id. The lightbox's ?lightbox=1, or any other query, asks PayPlug's page
|
|
25
|
+
* for something else, so it is not the entry, and neither is any other request to PayPlug's host.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isPayplugEntryUrl(raw: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether a request on PayPlug's page host is a step toward paying there: any POST, the page's card post and its
|
|
30
|
+
* 3-D Secure posts among them. The page itself, its scripts, its images and its frames are GETs of no card and are not one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isPayplugPayStep(url: string, method: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether an adapter must hand this request to the pre-claim gates: it is on PayPlug's page host and this build's
|
|
35
|
+
* registry serves PayPlug's entry. The page's posts are not the entry, so the registry's card-request test does not name
|
|
36
|
+
* them; the CDP adapter still pauses them through the recognizer's host pattern, and attachToPlaywright's route predicate
|
|
37
|
+
* asks this, so both adapters show the gates every request to the page's host once PayPlug is armed.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isArmedPayplugUrl(vault: {
|
|
40
|
+
isCardRequest(url: string, method?: string): boolean;
|
|
41
|
+
} | undefined, url: string): boolean;
|
package/dist/payplug.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayPlug's hosted payment page (secure.payplug.com, where French merchants send a buyer who pays by card), as this SDK
|
|
3
|
+
* reads it.
|
|
4
|
+
*
|
|
5
|
+
* The merchant's server creates the payment with PayPlug and sends the buyer's browser to the page's URL PayPlug answered
|
|
6
|
+
* with, https://secure.payplug.com/pay/<payment id>, or /pay/test/<payment id> for a test payment, by a redirect or by
|
|
7
|
+
* setting the page's location. PayPlug's Shopify app does the same from Shopify's checkout. That GET carries no card, no
|
|
8
|
+
* amount and no merchant, and it is the one request a build that declares PayPlug's capability pauses (hosted-entry.ts):
|
|
9
|
+
* the cardholder's device repeats it over the end-to-end TLS relay and pays on the page PayPlug returns to it (payment-core
|
|
10
|
+
* processors/payplug.js). Every step toward paying on that page is a POST to PayPlug's host, the page's card post among
|
|
11
|
+
* them, with the card encrypted under the page's own key.
|
|
12
|
+
*
|
|
13
|
+
* PAYPLUG_HOST is payment-core's PAYPLUG_HOST, and isPayplugEntryUrl is payment-core's isPayplugEntryUrl; payplug.test.mjs
|
|
14
|
+
* pins both to payment-core on every case payment-core's own tests list.
|
|
15
|
+
*/
|
|
16
|
+
export const PAYPLUG_PSP = 'payplug';
|
|
17
|
+
/** The dark-launch capability the API serves PayPlug's recognizer behind (payment-core processors/payplug.js). */
|
|
18
|
+
export const PAYPLUG_CAPABILITY = 'payplug_hosted_payment_page';
|
|
19
|
+
/** PayPlug's page host, which its test and live payments share; the entry's path says which one a payment is. */
|
|
20
|
+
export const PAYPLUG_HOST = 'secure.payplug.com';
|
|
21
|
+
// The entry's path: /pay/<id> for a live payment, /pay/test/<id> for a test one, the id as PayPlug writes it.
|
|
22
|
+
const ENTRY_PATH = /^\/pay\/(?:test\/)?[A-Za-z0-9]{22}$/;
|
|
23
|
+
// An id of that shape, for asking whether this build's registry serves the entry.
|
|
24
|
+
const PROBE = 'https://secure.payplug.com/pay/0000000000000000000000';
|
|
25
|
+
/**
|
|
26
|
+
* Whether a URL is the payment page's URL exactly as PayPlug writes payment_url: https, secure.payplug.com in lower case,
|
|
27
|
+
* the default port, no credentials, no query and no fragment, not even an empty one, and the path /pay/<id> or
|
|
28
|
+
* /pay/test/<id> with the payment's 22-character id. The lightbox's ?lightbox=1, or any other query, asks PayPlug's page
|
|
29
|
+
* for something else, so it is not the entry, and neither is any other request to PayPlug's host.
|
|
30
|
+
*/
|
|
31
|
+
export function isPayplugEntryUrl(raw) {
|
|
32
|
+
if (typeof raw !== 'string' || raw.length > 256 || raw.includes('?') || raw.includes('#'))
|
|
33
|
+
return false;
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(raw);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (url.href !== raw || url.protocol !== 'https:' || url.hostname !== PAYPLUG_HOST || url.port || url.username || url.password)
|
|
42
|
+
return false;
|
|
43
|
+
return ENTRY_PATH.test(url.pathname);
|
|
44
|
+
}
|
|
45
|
+
/** Whether a URL is on PayPlug's page host: https, no credentials, any port, any letter case, a trailing dot or not. */
|
|
46
|
+
function onPayplugHost(raw) {
|
|
47
|
+
let url;
|
|
48
|
+
try {
|
|
49
|
+
url = new URL(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return url.protocol === 'https:' && !url.username && !url.password && url.hostname.toLowerCase().replace(/\.$/, '') === PAYPLUG_HOST;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether a request on PayPlug's page host is a step toward paying there: any POST, the page's card post and its
|
|
58
|
+
* 3-D Secure posts among them. The page itself, its scripts, its images and its frames are GETs of no card and are not one.
|
|
59
|
+
*/
|
|
60
|
+
export function isPayplugPayStep(url, method) {
|
|
61
|
+
return onPayplugHost(url) && String(method).toUpperCase() === 'POST';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether an adapter must hand this request to the pre-claim gates: it is on PayPlug's page host and this build's
|
|
65
|
+
* registry serves PayPlug's entry. The page's posts are not the entry, so the registry's card-request test does not name
|
|
66
|
+
* them; the CDP adapter still pauses them through the recognizer's host pattern, and attachToPlaywright's route predicate
|
|
67
|
+
* asks this, so both adapters show the gates every request to the page's host once PayPlug is armed.
|
|
68
|
+
*/
|
|
69
|
+
export function isArmedPayplugUrl(vault, url) {
|
|
70
|
+
return onPayplugHost(url) && typeof vault?.isCardRequest === 'function' && vault.isCardRequest(PROBE, 'GET');
|
|
71
|
+
}
|
|
@@ -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: 139d0001a168e053ebe3283721905ae95d7c159a25848469965f037b844a44cf
|
|
4
4
|
// src/json-path.js
|
|
5
5
|
function roundTrips(parsed, body) {
|
|
6
6
|
try {
|
|
@@ -10,6 +10,25 @@ function roundTrips(parsed, body) {
|
|
|
10
10
|
return false;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
// src/answer-card-leak.js
|
|
14
|
+
var DECIMAL = /\p{Nd}/u;
|
|
15
|
+
function digitValue(char) {
|
|
16
|
+
const point = char.codePointAt(0);
|
|
17
|
+
let first = point;
|
|
18
|
+
while (DECIMAL.test(String.fromCodePoint(first - 1)))
|
|
19
|
+
first -= 1;
|
|
20
|
+
return String((point - first) % 10);
|
|
21
|
+
}
|
|
22
|
+
var ENCLOSED = /* @__PURE__ */ Object.freeze([[9451, 10, 11], [9461, 10, 1], [9471, 1, 0], [10102, 30, 1], [127243, 2, 0]]);
|
|
23
|
+
var ENCLOSED_CHAR = /[\u24eb-\u24ff\u2776-\u2793\u{1f10b}\u{1f10c}]/gu;
|
|
24
|
+
function enclosedValue(char) {
|
|
25
|
+
const point = char.codePointAt(0);
|
|
26
|
+
const [first, , start] = ENCLOSED.find(([from, size]) => point >= from && point < from + size);
|
|
27
|
+
return String(first === 127243 ? 0 : first === 10102 ? (point - first) % 10 + 1 : start + point - first);
|
|
28
|
+
}
|
|
29
|
+
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));
|
|
30
|
+
var digitCount = (text) => (asciiDigits(String(text)).match(/\p{N}/gu) ?? []).length;
|
|
31
|
+
var NAME_DIGITS = 3;
|
|
13
32
|
// src/sha256.js
|
|
14
33
|
var K = /* @__PURE__ */ new Uint32Array([
|
|
15
34
|
1116352408,
|
|
@@ -139,7 +158,7 @@ function sha256Hex(input) {
|
|
|
139
158
|
H[6] += g;
|
|
140
159
|
H[7] += h;
|
|
141
160
|
}
|
|
142
|
-
return Array.from(H, (
|
|
161
|
+
return Array.from(H, (word2) => word2.toString(16).padStart(8, "0")).join("");
|
|
143
162
|
}
|
|
144
163
|
// src/rsa-pkcs1v15.js
|
|
145
164
|
var RsaPkcs1v15Error = class extends Error {
|
|
@@ -652,6 +671,7 @@ var PAYRAILS_SERVED_CARD_FACTS = Object.freeze(["bin", "binLookup", "holderName"
|
|
|
652
671
|
var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
|
|
653
672
|
var bin6 = (value) => exact(/^[0-9]{6,8}$/, value) ? value.slice(0, 6) : void 0;
|
|
654
673
|
var label = (value, max) => plainText(value, max) ? value : void 0;
|
|
674
|
+
var word = (value, max, digits = 0) => plainText(value, max) && digitCount(value) <= digits ? value : void 0;
|
|
655
675
|
var pick = (value, pattern) => exact(pattern, value) ? value : void 0;
|
|
656
676
|
var compact = (object) => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
|
|
657
677
|
var nonEmpty = (object) => Object.keys(object).length ? object : void 0;
|
|
@@ -691,13 +711,13 @@ function sanitizePayrailsResponse(text) {
|
|
|
691
711
|
bin: served.has("bin") ? bin6(data.bin) : void 0,
|
|
692
712
|
binLookup: lookup ? nonEmpty(compact({
|
|
693
713
|
bin: bin6(lookup.bin),
|
|
694
|
-
network:
|
|
695
|
-
issuer:
|
|
696
|
-
issuerCountry: country ? nonEmpty(compact({ code: pick(country.code, /^[A-Z]{2}$/), name:
|
|
697
|
-
type:
|
|
714
|
+
network: word(lookup.network, 32),
|
|
715
|
+
issuer: word(lookup.issuer, 128, NAME_DIGITS),
|
|
716
|
+
issuerCountry: country ? nonEmpty(compact({ code: pick(country.code, /^[A-Z]{2}$/), name: word(country.name, 64), iso3: pick(country.iso3, /^[A-Z]{3}$/) })) : void 0,
|
|
717
|
+
type: word(lookup.type, 32)
|
|
698
718
|
})) : void 0,
|
|
699
719
|
holderName: served.has("holderName") ? label(data.holderName, 200) : void 0,
|
|
700
|
-
network: served.has("network") ?
|
|
720
|
+
network: served.has("network") ? word(data.network, 32) : void 0,
|
|
701
721
|
suffix: data.suffix,
|
|
702
722
|
expiryMonth: served.has("expiryMonth") ? pick(data.expiryMonth, /^(?:0[1-9]|1[0-2])$/) : void 0,
|
|
703
723
|
expiryYear: served.has("expiryYear") ? pick(data.expiryYear, /^(?:[0-9]{2}|[0-9]{4})$/) : void 0
|
|
@@ -1,6 +1,174 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 5166082ae6c8f452fbcd2df5f679aa40e78fe7fc9f9dff20d9d50512ae7b4487
|
|
4
|
+
// src/three-ds-step.js
|
|
5
|
+
var RUN = /[A-Za-z0-9+/_-]{12,}/g;
|
|
6
|
+
var RUN_LENGTH = 12;
|
|
7
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8
|
+
var LAYERS = 2;
|
|
9
|
+
var PAREQ = /pareq/i;
|
|
10
|
+
var PAGE_PAREQ = /\bname\s*=\s*["']?[^"'\s>]*pareq/i;
|
|
11
|
+
var THREE_DS_STEP_UNREADABLE = "unreadable_three_ds_step";
|
|
12
|
+
var THREE_DS_STEP_HOLDS_CARD = "card_in_three_ds_step";
|
|
13
|
+
var PERCENT_LAYERS = 8;
|
|
14
|
+
function percentDecoded(text) {
|
|
15
|
+
let out = text;
|
|
16
|
+
for (let layer = 0; layer < PERCENT_LAYERS; layer += 1) {
|
|
17
|
+
const next = out.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
18
|
+
if (next === out)
|
|
19
|
+
break;
|
|
20
|
+
out = next;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
var NAMED = /* @__PURE__ */ Object.freeze({
|
|
25
|
+
amp: "&",
|
|
26
|
+
percnt: "%",
|
|
27
|
+
num: "#",
|
|
28
|
+
quest: "?",
|
|
29
|
+
semi: ";",
|
|
30
|
+
excl: "!",
|
|
31
|
+
dollar: "$",
|
|
32
|
+
lpar: "(",
|
|
33
|
+
rpar: ")",
|
|
34
|
+
ast: "*",
|
|
35
|
+
comma: ",",
|
|
36
|
+
commat: "@",
|
|
37
|
+
lsqb: "[",
|
|
38
|
+
rsqb: "]",
|
|
39
|
+
lcub: "{",
|
|
40
|
+
rcub: "}",
|
|
41
|
+
verbar: "|",
|
|
42
|
+
bsol: "\\",
|
|
43
|
+
hat: "^",
|
|
44
|
+
grave: "`",
|
|
45
|
+
plus: "+",
|
|
46
|
+
sol: "/",
|
|
47
|
+
equals: "=",
|
|
48
|
+
lowbar: "_",
|
|
49
|
+
period: ".",
|
|
50
|
+
colon: ":",
|
|
51
|
+
quot: '"',
|
|
52
|
+
apos: "'",
|
|
53
|
+
lt: "<",
|
|
54
|
+
gt: ">"
|
|
55
|
+
});
|
|
56
|
+
var NUMERIC = "#(?:[xX]([0-9a-fA-F]+)|([0-9]+));?";
|
|
57
|
+
var BARE_NAME = "(sup[123]|frac(?:12|14|34)|[A-Za-z]+);?";
|
|
58
|
+
var referenceText = (hex, decimal, name) => {
|
|
59
|
+
if (name !== void 0)
|
|
60
|
+
return Object.prototype.hasOwnProperty.call(NAMED, name.toLowerCase()) ? NAMED[name.toLowerCase()] : " ";
|
|
61
|
+
const point = hex === void 0 ? parseInt(decimal, 10) : parseInt(hex, 16);
|
|
62
|
+
return point <= 1114111 ? String.fromCodePoint(point) : "";
|
|
63
|
+
};
|
|
64
|
+
function referenceReadings(text) {
|
|
65
|
+
const read = (pattern) => text.replace(new RegExp(`&(?:${NUMERIC}|${pattern})`, "g"), (_, hex, decimal, name) => referenceText(hex, decimal, name));
|
|
66
|
+
return [read("([A-Za-z][A-Za-z0-9]*);"), read(BARE_NAME)];
|
|
67
|
+
}
|
|
68
|
+
function readings(text) {
|
|
69
|
+
const out = /* @__PURE__ */ new Set([text, percentDecoded(text)]);
|
|
70
|
+
for (const reading of [...out])
|
|
71
|
+
for (const decoded of referenceReadings(reading))
|
|
72
|
+
out.add(decoded).add(percentDecoded(decoded));
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function base64Bytes(run, skip) {
|
|
76
|
+
let bits = 0;
|
|
77
|
+
let value = 0;
|
|
78
|
+
let out = "";
|
|
79
|
+
for (let i = skip; i < run.length; i += 1) {
|
|
80
|
+
const char = run[i];
|
|
81
|
+
value = value << 6 | (char === "-" ? 62 : char === "_" ? 63 : ALPHABET.indexOf(char));
|
|
82
|
+
bits += 6;
|
|
83
|
+
if (bits >= 8) {
|
|
84
|
+
bits -= 8;
|
|
85
|
+
out += String.fromCharCode(value >> bits);
|
|
86
|
+
value &= (1 << bits) - 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
var compressed = (bytes) => bytes.charCodeAt(0) === 31 && bytes.charCodeAt(1) === 139 || bytes.charCodeAt(0) === 120 && (30720 + bytes.charCodeAt(1)) % 31 === 0;
|
|
92
|
+
function holdsStream(text, layers) {
|
|
93
|
+
for (const reading of readings(text)) {
|
|
94
|
+
for (const [run] of reading.matchAll(RUN)) {
|
|
95
|
+
const starts = [0];
|
|
96
|
+
for (let slash = run.indexOf("/"); slash >= 0; slash = run.indexOf("/", slash + 1))
|
|
97
|
+
starts.push(slash + 1);
|
|
98
|
+
if (starts.some((start) => run.length - start >= RUN_LENGTH && compressed(base64Bytes(run.slice(start, start + 4), 0))))
|
|
99
|
+
return true;
|
|
100
|
+
for (let skip = 0; layers > 0 && skip < 4; skip += 1)
|
|
101
|
+
if (holdsStream(base64Bytes(run, skip), layers - 1))
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function numberPattern(card) {
|
|
108
|
+
const number = card && (typeof card.number === "string" || typeof card.number === "number") ? String(card.number).replace(/[^0-9]/g, "") : "";
|
|
109
|
+
return number.length >= 12 && number.length <= 19 ? new RegExp(number.split("").join("[^0-9A-Za-z]{0,3}")) : null;
|
|
110
|
+
}
|
|
111
|
+
function holdsNumber(text, pattern, layers) {
|
|
112
|
+
for (const reading of readings(text)) {
|
|
113
|
+
if (pattern.test(reading))
|
|
114
|
+
return true;
|
|
115
|
+
if (layers === 0)
|
|
116
|
+
continue;
|
|
117
|
+
for (const [run] of reading.matchAll(RUN)) {
|
|
118
|
+
for (let skip = 0; skip < 4; skip += 1)
|
|
119
|
+
if (holdsNumber(base64Bytes(run, skip), pattern, layers - 1))
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function addressTexts(value) {
|
|
126
|
+
if (typeof value !== "string")
|
|
127
|
+
return null;
|
|
128
|
+
let url;
|
|
129
|
+
try {
|
|
130
|
+
url = new URL(value);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const names = [];
|
|
136
|
+
const texts = [value, ...url.pathname.split("/")];
|
|
137
|
+
for (const pair of `${url.search.slice(1)}&${url.hash.slice(1)}`.split("&")) {
|
|
138
|
+
if (pair === "")
|
|
139
|
+
continue;
|
|
140
|
+
const at = pair.indexOf("=");
|
|
141
|
+
names.push(at < 0 ? pair : pair.slice(0, at));
|
|
142
|
+
if (at >= 0)
|
|
143
|
+
texts.push(pair.slice(at + 1));
|
|
144
|
+
}
|
|
145
|
+
return { names, texts: [...texts, ...names] };
|
|
146
|
+
}
|
|
147
|
+
var textOf = (value) => typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
148
|
+
function threeDsStepLeak(card, step) {
|
|
149
|
+
const names = [];
|
|
150
|
+
const texts = [];
|
|
151
|
+
for (const url of step.urls ?? []) {
|
|
152
|
+
const address = addressTexts(url);
|
|
153
|
+
if (!address)
|
|
154
|
+
return THREE_DS_STEP_UNREADABLE;
|
|
155
|
+
names.push(...address.names);
|
|
156
|
+
texts.push(...address.texts);
|
|
157
|
+
}
|
|
158
|
+
for (const [name, value] of step.fields ?? []) {
|
|
159
|
+
names.push(textOf(name));
|
|
160
|
+
texts.push(textOf(name), textOf(value));
|
|
161
|
+
}
|
|
162
|
+
for (const page of step.pages ?? []) {
|
|
163
|
+
if ([...readings(textOf(page))].some((reading) => PAGE_PAREQ.test(reading)))
|
|
164
|
+
return THREE_DS_STEP_UNREADABLE;
|
|
165
|
+
texts.push(textOf(page));
|
|
166
|
+
}
|
|
167
|
+
if (names.some((name) => [...readings(name)].some((reading) => PAREQ.test(reading))) || texts.some((text) => holdsStream(text, LAYERS)))
|
|
168
|
+
return THREE_DS_STEP_UNREADABLE;
|
|
169
|
+
const pattern = numberPattern(card);
|
|
170
|
+
return pattern && texts.some((text) => holdsNumber(text, pattern, LAYERS)) ? THREE_DS_STEP_HOLDS_CARD : null;
|
|
171
|
+
}
|
|
4
172
|
// src/payrexx.js
|
|
5
173
|
var PAYREXX_PSP = "payrexx";
|
|
6
174
|
var PAYREXX_CAPABILITY = "payrexx_card_frame";
|
|
@@ -287,8 +455,14 @@ function payrexxAnswerOutcome(status, text, card) {
|
|
|
287
455
|
return { outcome: "refused", code: "payrexx_three_ds_declined" };
|
|
288
456
|
if (step === "tokenless")
|
|
289
457
|
return { outcome: "refused", code: "payrexx_three_ds_error" };
|
|
290
|
-
if (step !== null)
|
|
458
|
+
if (step !== null) {
|
|
459
|
+
const urls = [answer.threeDSMethodURL, answer.acsURL].filter((url) => typeof url === "string" && url !== "");
|
|
460
|
+
const members = Object.entries(answer).map(([key, value]) => [key, typeof value === "string" ? value : ""]);
|
|
461
|
+
const leak = threeDsStepLeak(card, { urls, fields: [...members, ["", text]] });
|
|
462
|
+
if (leak)
|
|
463
|
+
return { outcome: "unknown", reason: leak === THREE_DS_STEP_UNREADABLE ? "payrexx_three_ds_unreadable" : "payrexx_card_in_answer" };
|
|
291
464
|
return { outcome: "approved", body: text, pending: "three_ds" };
|
|
465
|
+
}
|
|
292
466
|
}
|
|
293
467
|
return { outcome: "unknown", reason: "payrexx_answer_unconfirmed" };
|
|
294
468
|
}
|