@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/PREFLIGHT.md +7 -1
  3. package/README.md +137 -99
  4. package/dist/aci.d.ts +52 -22
  5. package/dist/aci.js +79 -15
  6. package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
  7. package/dist/adyen-merchant-hosted.generated.js +459 -21
  8. package/dist/agent-amount.generated.js +1 -1
  9. package/dist/agentcard.d.ts +4 -5
  10. package/dist/agentcard.js +4 -5
  11. package/dist/amadeus.d.ts +9 -3
  12. package/dist/amadeus.js +33 -4
  13. package/dist/api-module.d.ts +1 -7
  14. package/dist/api-module.js +3 -5
  15. package/dist/{voice-retry.js → api-retry.js} +1 -1
  16. package/dist/api-types.d.ts +53 -0
  17. package/dist/{voice-types.js → api-types.js} +7 -2
  18. package/dist/approvals.d.ts +166 -0
  19. package/dist/approvals.js +213 -0
  20. package/dist/buy.d.ts +20 -101
  21. package/dist/buy.js +19 -74
  22. package/dist/capabilities.generated.js +1 -1
  23. package/dist/card-fields.generated.js +1398 -106
  24. package/dist/cdp.d.ts +3 -13
  25. package/dist/cdp.js +231 -175
  26. package/dist/client.d.ts +104 -22
  27. package/dist/client.js +215 -40
  28. package/dist/creditguard.generated.d.ts +4 -4
  29. package/dist/datatrans.generated.js +22 -1
  30. package/dist/elavon-converge.generated.js +5 -3
  31. package/dist/hold.d.ts +8 -0
  32. package/dist/hold.js +43 -2
  33. package/dist/hosted-entry.js +2 -0
  34. package/dist/index.d.ts +9 -15
  35. package/dist/index.js +6 -10
  36. package/dist/juspay-gate.js +2 -2
  37. package/dist/juspay.generated.d.ts +2 -2
  38. package/dist/juspay.generated.js +174 -4
  39. package/dist/lifecycle.d.ts +9 -61
  40. package/dist/lifecycle.js +24 -10
  41. package/dist/merchant-card-endpoints.generated.d.ts +6 -1
  42. package/dist/merchant-card-endpoints.generated.js +344 -19
  43. package/dist/merchant-hosted.d.ts +8 -1
  44. package/dist/nike-order.d.ts +51 -0
  45. package/dist/nike-order.js +81 -0
  46. package/dist/oceanpayment.d.ts +38 -0
  47. package/dist/oceanpayment.js +70 -0
  48. package/dist/paddle-tokenex.generated.js +105 -10
  49. package/dist/payplug.d.ts +41 -0
  50. package/dist/payplug.js +71 -0
  51. package/dist/payrails.generated.js +27 -7
  52. package/dist/payrexx.generated.js +176 -2
  53. package/dist/pre-claim.d.ts +9 -0
  54. package/dist/pre-claim.js +86 -7
  55. package/dist/preflight-capabilities.generated.d.ts +0 -4
  56. package/dist/preflight-capabilities.generated.js +0 -5
  57. package/dist/preflight-catalog.json +46 -5
  58. package/dist/preflight-playwright.d.ts +2 -2
  59. package/dist/preflight-playwright.js +63 -14
  60. package/dist/preflight-schemas.json +4 -0
  61. package/dist/preflight.generated.d.ts +0 -4
  62. package/dist/preflight.generated.js +24 -1
  63. package/dist/prepared-processor.d.ts +18 -5
  64. package/dist/prepared-processor.js +48 -8
  65. package/dist/primer.generated.js +1418 -120
  66. package/dist/processout.generated.d.ts +61 -0
  67. package/dist/processout.generated.js +577 -0
  68. package/dist/requests.js +1 -1
  69. package/dist/shiji-sts.generated.js +105 -10
  70. package/dist/stripe-checkout.generated.js +1405 -234
  71. package/dist/substitutions.generated.js +460 -26
  72. package/dist/tap-payments.generated.js +105 -10
  73. package/dist/twoc2p.generated.js +105 -10
  74. package/dist/url-masks/bluesnap.d.ts +2 -0
  75. package/dist/url-masks/bluesnap.js +15 -0
  76. package/dist/url-masks/oceanpayment.d.ts +2 -0
  77. package/dist/url-masks/oceanpayment.js +13 -0
  78. package/dist/url-masks/payplug.d.ts +2 -0
  79. package/dist/url-masks/payplug.js +13 -0
  80. package/dist/url-masks/vtex_payments.d.ts +2 -0
  81. package/dist/url-masks/vtex_payments.js +12 -0
  82. package/dist/url-masks.generated.d.ts +1 -1
  83. package/dist/url-masks.generated.js +6 -2
  84. package/dist/verifone.generated.js +42 -2
  85. package/dist/vtex-payments.generated.d.ts +127 -0
  86. package/dist/vtex-payments.generated.js +544 -0
  87. package/dist/webhooks.d.ts +2 -2
  88. package/dist/webhooks.js +2 -2
  89. package/dist/wspay.generated.d.ts +1 -1
  90. package/dist/wspay.generated.js +175 -3
  91. package/examples/preflight/kernel-native/inventory.json +1 -1
  92. package/package.json +6 -8
  93. package/dist/languages.d.ts +0 -23
  94. package/dist/languages.js +0 -76
  95. package/dist/voice-pkce.d.ts +0 -49
  96. package/dist/voice-pkce.js +0 -147
  97. package/dist/voice-stream.generated.d.ts +0 -118
  98. package/dist/voice-stream.generated.js +0 -170
  99. package/dist/voice-types.d.ts +0 -735
  100. package/dist/voice.d.ts +0 -373
  101. package/dist/voice.js +0 -815
  102. package/readme.test.mjs +0 -53
  103. /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: cb62940ac92ca9f2a61b5168f362003d67998f12d045b18d8bd07ae352e0cb3e
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 === b[i]);
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] !== b[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 (!isJsonPath(path))
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 = jsonAt(parsed, path);
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 = jsonAt(out, path);
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
- for (const key of remove)
610
- if (Object.prototype.hasOwnProperty.call(written.value, key))
611
- delete written.value[key];
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;
@@ -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: 056fa54de953be9af775505906b2fba30def2787a217a12fe2ca2a50b8614649
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, (word) => word.toString(16).padStart(8, "0")).join("");
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: label(lookup.network, 32),
695
- issuer: label(lookup.issuer, 128),
696
- issuerCountry: country ? nonEmpty(compact({ code: pick(country.code, /^[A-Z]{2}$/), name: label(country.name, 64), iso3: pick(country.iso3, /^[A-Z]{3}$/) })) : void 0,
697
- type: label(lookup.type, 32)
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") ? label(data.network, 32) : void 0,
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: 12336c60151bbdaa63f9d3e665fb53c8e3407ba942140bef2928e7ce3a8b3e2c
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
  }