@agent-cards/sdk 0.23.16 → 0.23.17

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Finish a Worldpay eProtect checkout that your code resumes itself. With `attachToCdp(…, { pauseOnly: true })`, you create the authorization, read it, and call `controller.resume(read)`. The read carries the card registration as Agentcard stored it, bare JSON, and earlier releases handed exactly that to the merchant's paused registration script. Worldpay's script reads its answer only through the callback the request named, so it never received the registration: the page waited until the merchant's timeout, and the checkout read `outcome_unknown` with reason `merchant_request_aborted` after the cardholder had approved. The SDK now answers the resumed registration as it answers one it created itself, with the registration inside the request's own callback under Worldpay's script type. The SDK never hands the page a read that is not this request's registration, such as one already inside a callback or another request's answer: `resume` rejects with `PaymentOutcomeUnknownError`, reason `eprotect_registration_unconfirmed`, and the paused request fails. A checkout whose authorization the SDK creates was already answered this way and does not change. No API or Vault release is needed.
6
+
5
7
  - Refuse a sandbox declaration whose endpoint is on agentcard.com, Agentcard's own domain, as one on agentcard.sh is already refused. A `sandboxMerchants` entry on `VaultClient` whose endpoint is agentcard.com or any host under it makes the constructor throw:
6
8
 
7
9
  ```text
@@ -141,6 +143,8 @@
141
143
 
142
144
  - Pay a Wix checkout whose card goes to Wix's own tokenizer, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it checks that the site instance Wix's card frame sends belongs to the reviewed store. Every Wix checkout mounts Wix Cashier's card frame, and for Wix Payments and classic Stripe on Wix that frame posts the card to `https://cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens`. Wix's payment widget fails the payment 30 seconds after it asks the frame for a token, too soon for an approval after Pay, so the SDK pays a Wix checkout only through `prepare({ psp: 'wix_payments', environment: 'production' })`, at a store Agentcard reviewed (ParaHouse Magazine, Psalms & Potions and Alexandra Hagel today), with `amount` and `currency`, the total the cardholder approves: the request names none. Wix has no test environment, so a test client's `prepare()` fails with `live_host_on_sandbox_row`. The sync now also asks for `capabilities=wix_payments_tokenize`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. The frame sends its card request once more under a fresh site instance after Wix answers 401 or 403: the SDK refuses that second request in the browser and never answers it from the first approval. The SDK fails a paused request the cardholder's device has not sent 20 seconds after the pause, of the 30 seconds Wix waits, with reason `wix_payments_send_window_closed`, as it fails a prepared Secure Acceptance token create, and the device sends nothing past its own earlier boundary.
143
145
 
146
+ - Keep a GoPay card form's token out of your events and error messages. GoPay's card frame sends the card to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>?lang=..&ver=..&SG=<signature>`, and that token, with the signature in the query, opens that payment's card form to anyone who holds it. `redactUrl`, every event and every error message now show it as `{token}` on GoPay's card frame hosts, as in `https://secure.gopay.com/gp-card-comm/h/{token}`, and drop the query as before. The SDK does not pause a GoPay payment yet. No API or Vault release is needed.
147
+
144
148
  ## 0.23.0
145
149
 
146
150
  ### Upgrading from 0.22.0
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: e4858832550c43f6e3cbef0c64facb820d7ae9b5118a2c8c4fcadb429c94b2b0
3
+ // artifact-sha256: 0fcc0e758acec92fba9d21092bcb3704b8a73ec864647ea194ca44eba57cb96d
4
4
  // src/card-number-key.js
5
5
  var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
6
6
  // src/json-path.js
@@ -1442,6 +1442,104 @@ function paymongoBodyVerdict(body, session) {
1442
1442
  const code = judgeCardAttach(parsed, session);
1443
1443
  return code === null ? CARD9 : refuse10(code);
1444
1444
  }
1445
+ // src/gopay.js
1446
+ var GOPAY_PSP = "gopay";
1447
+ var GOPAY_CARD_FORM_HOSTS = /* @__PURE__ */ Object.freeze({ "secure.gopay.com": "production", "secure.sandbox.gopay.com": "test" });
1448
+ var GOPAY_CARD_FORM_PATH = "/gp-card-comm/h/";
1449
+ var BODY_LIMIT7 = 1024;
1450
+ var ROOT_KEYS4 = /* @__PURE__ */ Object.freeze(["SG", "cardData", "type"]);
1451
+ var CARD_KEYS6 = /* @__PURE__ */ Object.freeze(["cardNumber", "cvc", "expiration"]);
1452
+ var TOKEN2 = /^[0-9A-Fa-f]{16,1024}$/;
1453
+ var SIGNATURE = /^[0-9A-Fa-f]{64}$/;
1454
+ var LANGUAGE = /^[a-z]{2}(?:-[A-Za-z]{2})?$/;
1455
+ var VERSION = /^[A-Za-z0-9]{1,16}$/;
1456
+ var QUERY_NAMES = /* @__PURE__ */ Object.freeze(["lang", "ver", "SG"]);
1457
+ var CARD_NUMBER7 = /^[0-9]{16}$/;
1458
+ var CODE = /^[0-9]{3}$/;
1459
+ var EXPIRATION = /^[0-9]{2}(?:0[1-9]|1[0-2])$/;
1460
+ var CARD_PAYMENT = "PAYMENT_CARD";
1461
+ var record8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1462
+ var sameKeys4 = (value, keys) => {
1463
+ const own2 = Object.keys(value);
1464
+ return own2.length === keys.length && own2.every((key, at) => key === keys[at]);
1465
+ };
1466
+ var refuse11 = (code) => Object.freeze({ verdict: "refuse", code });
1467
+ var CARD10 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
1468
+ function luhn(digits) {
1469
+ let sum = 0;
1470
+ for (let at = 0; at < digits.length; at += 1) {
1471
+ let digit = Number(digits[digits.length - 1 - at]);
1472
+ if (at % 2 === 1) {
1473
+ digit *= 2;
1474
+ if (digit > 9)
1475
+ digit -= 9;
1476
+ }
1477
+ sum += digit;
1478
+ }
1479
+ return sum % 10 === 0;
1480
+ }
1481
+ function gopayCardFormUrl(url) {
1482
+ if (typeof url !== "string" || url.length > 2048 || url.includes("#"))
1483
+ return null;
1484
+ let parsed;
1485
+ try {
1486
+ parsed = new URL(url);
1487
+ }
1488
+ catch {
1489
+ return null;
1490
+ }
1491
+ if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password)
1492
+ return null;
1493
+ const environment = Object.prototype.hasOwnProperty.call(GOPAY_CARD_FORM_HOSTS, parsed.hostname) ? GOPAY_CARD_FORM_HOSTS[parsed.hostname] : null;
1494
+ if (environment === null || !parsed.pathname.startsWith(GOPAY_CARD_FORM_PATH))
1495
+ return null;
1496
+ if (!TOKEN2.test(parsed.pathname.slice(GOPAY_CARD_FORM_PATH.length)))
1497
+ return null;
1498
+ const names = [...parsed.searchParams.keys()];
1499
+ if (names.some((name) => !QUERY_NAMES.includes(name)) || new Set(names).size !== names.length)
1500
+ return null;
1501
+ const signature = parsed.searchParams.get("SG");
1502
+ const language = parsed.searchParams.get("lang");
1503
+ const version = parsed.searchParams.get("ver");
1504
+ if (signature === null || !SIGNATURE.test(signature) || language !== null && !LANGUAGE.test(language) || version !== null && !VERSION.test(version))
1505
+ return null;
1506
+ return Object.freeze({ host: parsed.hostname, environment, signature });
1507
+ }
1508
+ function readBody4(body) {
1509
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT7)
1510
+ return null;
1511
+ let value;
1512
+ try {
1513
+ value = JSON.parse(body);
1514
+ if (!record8(value) || JSON.stringify(value) !== body)
1515
+ return null;
1516
+ }
1517
+ catch {
1518
+ return null;
1519
+ }
1520
+ return value;
1521
+ }
1522
+ function gopayBodyVerdict(body, url) {
1523
+ let target;
1524
+ if (url !== void 0) {
1525
+ target = gopayCardFormUrl(url);
1526
+ if (target === null)
1527
+ return refuse11("gopay_url_refused");
1528
+ }
1529
+ const value = readBody4(body);
1530
+ if (value === null || !sameKeys4(value, ROOT_KEYS4) || !record8(value.cardData) || !sameKeys4(value.cardData, CARD_KEYS6)) {
1531
+ return refuse11("gopay_body_unsupported");
1532
+ }
1533
+ const card = value.cardData;
1534
+ if (typeof value.SG !== "string" || !SIGNATURE.test(value.SG) || typeof card.cardNumber !== "string" || !CARD_NUMBER7.test(card.cardNumber) || !luhn(card.cardNumber) || typeof card.cvc !== "string" || !CODE.test(card.cvc) || typeof card.expiration !== "string" || !EXPIRATION.test(card.expiration) || typeof value.type !== "string") {
1535
+ return refuse11("gopay_body_unsupported");
1536
+ }
1537
+ if (value.type !== CARD_PAYMENT)
1538
+ return refuse11("gopay_card_saving_refused");
1539
+ if (target !== void 0 && value.SG !== target.signature)
1540
+ return refuse11("gopay_signature_mismatch");
1541
+ return CARD10;
1542
+ }
1445
1543
  // src/card-fields.js
1446
1544
  function processorGate(recognizer, url, body) {
1447
1545
  if (recognizer && recognizer.psp === LYRA_PSP)
@@ -1473,6 +1571,8 @@ function processorGate(recognizer, url, body) {
1473
1571
  return { verdict: "refuse", code: "paymongo_url_refused" };
1474
1572
  return paymongoBodyVerdict(body, session);
1475
1573
  }
1574
+ if (recognizer && recognizer.psp === GOPAY_PSP)
1575
+ return gopayBodyVerdict(body, url);
1476
1576
  if (recognizer && recognizer.psp === GRAVY_PSP)
1477
1577
  return gravyBodyVerdict(body);
1478
1578
  if (recognizer && recognizer.psp === MONERIS_PSP) {
package/dist/cdp.js CHANGED
@@ -7,7 +7,7 @@ import { MercadoCheckoutGate } from './mercado-checkout.js';
7
7
  import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
8
8
  import { JuspayCheckoutGate } from './juspay-gate.js';
9
9
  import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from './aci.js';
10
- import { EprotectGate } from './eprotect.js';
10
+ import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './eprotect.js';
11
11
  import { dispatchPreClaim, eventUrl, isArmedCreditguardCall } from './pre-claim.js';
12
12
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
13
13
  import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
@@ -1549,6 +1549,23 @@ function replayOf(result, timeoutMs) {
1549
1549
  return { mode: 'token', authorizationId, status: response.status, headers: { ...response.headers }, body: response.body, amountVerified: null,
1550
1550
  ...(result.processor_context !== undefined ? { processorContext: result.processor_context } : {}), ...execution };
1551
1551
  }
1552
+ /**
1553
+ * Worldpay eProtect's registration, resumed by the business. The read carries the registration as the API
1554
+ * stored it, bare JSON, and the paused request is the vendor's JSONP <script>, which runs its answer and reads
1555
+ * the registration only through the callback the paused URL named. So it gets the answer authorize() gives on
1556
+ * the create path (client.ts): the registration inside that callback, under Worldpay's script type, and only a
1557
+ * registration payment-core's judge accepts for that URL. A read in any other mode never answers it either. The
1558
+ * paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and a
1559
+ * create's replay never comes here, so no answer is wrapped twice.
1560
+ */
1561
+ function eprotectResumedReplay(replay, request) {
1562
+ if (classifyEprotectRequest(request.url, request.method)?.kind !== 'registration')
1563
+ return replay;
1564
+ const answer = replay.mode === 'token' ? eprotectScriptAnswer(request.url, replay) : null;
1565
+ if (replay.mode !== 'token' || !answer)
1566
+ throw new PaymentOutcomeUnknownError(replay.authorizationId, 'eprotect_registration_unconfirmed');
1567
+ return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1568
+ }
1552
1569
  /** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
1553
1570
  function recognizerNameOf(opts, url, method) {
1554
1571
  const read = opts.vault.recognizerOf;
@@ -2763,7 +2780,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2763
2780
  // through controller.resume; the replay it hands back goes down the same delivery path as a create's.
2764
2781
  const pause = opts.pauseOnly ? pauseForBusiness(opts, lifecycle, attempt.signal, { url: request.url, method: request.method, headers: request.headers ?? {}, body }, pageOrigin) : null;
2765
2782
  businessPause = pause;
2766
- const replay = pause ? await pause.replay : await opts.vault.authorize({
2783
+ const replay = pause ? eprotectResumedReplay(await pause.replay, request) : await opts.vault.authorize({
2767
2784
  user: opts.user,
2768
2785
  merchant: opts.merchant,
2769
2786
  amount: opts.amount,
package/dist/client.d.ts CHANGED
@@ -1175,6 +1175,7 @@ export declare class CheckoutAuthError extends Error {
1175
1175
  * On PayPal's hosts an order id reads `{order}`:
1176
1176
  * https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
1177
1177
  * On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
1178
+ * On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
1178
1179
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1179
1180
  * same segments masked.
1180
1181
  */
package/dist/client.js CHANGED
@@ -1115,6 +1115,18 @@ const PAYMONGO_SESSION = /(?:cs_)?(?:[A-Za-z0-9]|%[0-9A-Fa-f]{2}){24,}/gi;
1115
1115
  function maskPaymongoSession(path) {
1116
1116
  return path.replace(PAYMONGO_SESSION, '{session}');
1117
1117
  }
1118
+ /**
1119
+ * GoPay's card frame posts the card to its own URL,
1120
+ * https://secure.gopay.com/gp-card-comm/h/<hex token>?lang=..&ver=..&SG=<64 hex> (payment-core gopay.js). The
1121
+ * token names that payment's card form, and with the SG, which redactUrl drops with the query, anyone who holds
1122
+ * the URL can open the form and post a card to it. On GoPay's two card frame hosts the segment after /h/, in any
1123
+ * case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}.
1124
+ */
1125
+ const GOPAY_CARD_FORM_HOST = /^secure(?:\.sandbox)?\.gopay\.com\.?$/i;
1126
+ const GOPAY_CARD_FORM_TOKEN = /(\/h(?:\/|%2f))[^/?#]*/gi;
1127
+ function maskGopayToken(path) {
1128
+ return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}');
1129
+ }
1118
1130
  /**
1119
1131
  * The processors whose card request carries its own credential in the URL path, with the hosts
1120
1132
  * that send it, the text that names those hosts in a string that is not a URL, and the mask. The
@@ -1155,6 +1167,7 @@ const PATH_CREDENTIALS = [
1155
1167
  { host: LYRA_HOST, named: /(?:payzen\.eu|systempay\.fr|societegenerale\.eu|lyra\.com)/i, mask: maskLyraSession },
1156
1168
  { host: PAYPAL_HOST, named: /paypal\.com/i, mask: maskPaypalOrder },
1157
1169
  { host: PAYMONGO_CHECKOUT_HOST, named: /checkout\.paymongo\.com/i, mask: maskPaymongoSession },
1170
+ { host: GOPAY_CARD_FORM_HOST, named: /secure(?:\.sandbox)?\.gopay\.com/i, mask: maskGopayToken },
1158
1171
  ];
1159
1172
  /**
1160
1173
  * A URL as it may appear in an error message: origin + path only. A paused
@@ -1178,6 +1191,7 @@ const PATH_CREDENTIALS = [
1178
1191
  * On PayPal's hosts an order id reads `{order}`:
1179
1192
  * https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
1180
1193
  * On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
1194
+ * On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
1181
1195
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1182
1196
  * same segments masked.
1183
1197
  */
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 833d97716c57df725c942d14494012032a87b561ac7734676fe9dcb1d6be4819
3
+ // artifact-sha256: c3b720efaa20837c1bf50cc511610282f324867e9fe5538ac155b2ff79f2d9b4
4
4
  // src/card-number-key.js
5
5
  var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
6
6
  // src/json-path.js
@@ -1450,6 +1450,104 @@ function paymongoBodyVerdict(body, session) {
1450
1450
  const code = judgeCardAttach(parsed, session);
1451
1451
  return code === null ? CARD9 : refuse10(code);
1452
1452
  }
1453
+ // src/gopay.js
1454
+ var GOPAY_PSP = "gopay";
1455
+ var GOPAY_CARD_FORM_HOSTS = /* @__PURE__ */ Object.freeze({ "secure.gopay.com": "production", "secure.sandbox.gopay.com": "test" });
1456
+ var GOPAY_CARD_FORM_PATH = "/gp-card-comm/h/";
1457
+ var BODY_LIMIT7 = 1024;
1458
+ var ROOT_KEYS4 = /* @__PURE__ */ Object.freeze(["SG", "cardData", "type"]);
1459
+ var CARD_KEYS6 = /* @__PURE__ */ Object.freeze(["cardNumber", "cvc", "expiration"]);
1460
+ var TOKEN2 = /^[0-9A-Fa-f]{16,1024}$/;
1461
+ var SIGNATURE = /^[0-9A-Fa-f]{64}$/;
1462
+ var LANGUAGE = /^[a-z]{2}(?:-[A-Za-z]{2})?$/;
1463
+ var VERSION = /^[A-Za-z0-9]{1,16}$/;
1464
+ var QUERY_NAMES = /* @__PURE__ */ Object.freeze(["lang", "ver", "SG"]);
1465
+ var CARD_NUMBER7 = /^[0-9]{16}$/;
1466
+ var CODE = /^[0-9]{3}$/;
1467
+ var EXPIRATION = /^[0-9]{2}(?:0[1-9]|1[0-2])$/;
1468
+ var CARD_PAYMENT = "PAYMENT_CARD";
1469
+ var record8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1470
+ var sameKeys4 = (value, keys) => {
1471
+ const own2 = Object.keys(value);
1472
+ return own2.length === keys.length && own2.every((key, at) => key === keys[at]);
1473
+ };
1474
+ var refuse11 = (code) => Object.freeze({ verdict: "refuse", code });
1475
+ var CARD10 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
1476
+ function luhn(digits2) {
1477
+ let sum = 0;
1478
+ for (let at = 0; at < digits2.length; at += 1) {
1479
+ let digit = Number(digits2[digits2.length - 1 - at]);
1480
+ if (at % 2 === 1) {
1481
+ digit *= 2;
1482
+ if (digit > 9)
1483
+ digit -= 9;
1484
+ }
1485
+ sum += digit;
1486
+ }
1487
+ return sum % 10 === 0;
1488
+ }
1489
+ function gopayCardFormUrl(url) {
1490
+ if (typeof url !== "string" || url.length > 2048 || url.includes("#"))
1491
+ return null;
1492
+ let parsed;
1493
+ try {
1494
+ parsed = new URL(url);
1495
+ }
1496
+ catch {
1497
+ return null;
1498
+ }
1499
+ if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password)
1500
+ return null;
1501
+ const environment = Object.prototype.hasOwnProperty.call(GOPAY_CARD_FORM_HOSTS, parsed.hostname) ? GOPAY_CARD_FORM_HOSTS[parsed.hostname] : null;
1502
+ if (environment === null || !parsed.pathname.startsWith(GOPAY_CARD_FORM_PATH))
1503
+ return null;
1504
+ if (!TOKEN2.test(parsed.pathname.slice(GOPAY_CARD_FORM_PATH.length)))
1505
+ return null;
1506
+ const names = [...parsed.searchParams.keys()];
1507
+ if (names.some((name) => !QUERY_NAMES.includes(name)) || new Set(names).size !== names.length)
1508
+ return null;
1509
+ const signature = parsed.searchParams.get("SG");
1510
+ const language = parsed.searchParams.get("lang");
1511
+ const version = parsed.searchParams.get("ver");
1512
+ if (signature === null || !SIGNATURE.test(signature) || language !== null && !LANGUAGE.test(language) || version !== null && !VERSION.test(version))
1513
+ return null;
1514
+ return Object.freeze({ host: parsed.hostname, environment, signature });
1515
+ }
1516
+ function readBody4(body) {
1517
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT7)
1518
+ return null;
1519
+ let value;
1520
+ try {
1521
+ value = JSON.parse(body);
1522
+ if (!record8(value) || JSON.stringify(value) !== body)
1523
+ return null;
1524
+ }
1525
+ catch {
1526
+ return null;
1527
+ }
1528
+ return value;
1529
+ }
1530
+ function gopayBodyVerdict(body, url) {
1531
+ let target;
1532
+ if (url !== void 0) {
1533
+ target = gopayCardFormUrl(url);
1534
+ if (target === null)
1535
+ return refuse11("gopay_url_refused");
1536
+ }
1537
+ const value = readBody4(body);
1538
+ if (value === null || !sameKeys4(value, ROOT_KEYS4) || !record8(value.cardData) || !sameKeys4(value.cardData, CARD_KEYS6)) {
1539
+ return refuse11("gopay_body_unsupported");
1540
+ }
1541
+ const card = value.cardData;
1542
+ if (typeof value.SG !== "string" || !SIGNATURE.test(value.SG) || typeof card.cardNumber !== "string" || !CARD_NUMBER7.test(card.cardNumber) || !luhn(card.cardNumber) || typeof card.cvc !== "string" || !CODE.test(card.cvc) || typeof card.expiration !== "string" || !EXPIRATION.test(card.expiration) || typeof value.type !== "string") {
1543
+ return refuse11("gopay_body_unsupported");
1544
+ }
1545
+ if (value.type !== CARD_PAYMENT)
1546
+ return refuse11("gopay_card_saving_refused");
1547
+ if (target !== void 0 && value.SG !== target.signature)
1548
+ return refuse11("gopay_signature_mismatch");
1549
+ return CARD10;
1550
+ }
1453
1551
  // src/card-fields.js
1454
1552
  function processorGate(recognizer, url, body) {
1455
1553
  if (recognizer && recognizer.psp === LYRA_PSP)
@@ -1481,6 +1579,8 @@ function processorGate(recognizer, url, body) {
1481
1579
  return { verdict: "refuse", code: "paymongo_url_refused" };
1482
1580
  return paymongoBodyVerdict(body, session);
1483
1581
  }
1582
+ if (recognizer && recognizer.psp === GOPAY_PSP)
1583
+ return gopayBodyVerdict(body, url);
1484
1584
  if (recognizer && recognizer.psp === GRAVY_PSP)
1485
1585
  return gravyBodyVerdict(body);
1486
1586
  if (recognizer && recognizer.psp === MONERIS_PSP) {
@@ -1601,12 +1701,12 @@ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this Primer payment would keep the
1601
1701
  var UNSUPPORTED_SDK_REFUSAL = "Refusing to pay: this checkout uses a Primer SDK Agentcard does not support yet. Nothing was sent.";
1602
1702
  var NETWORK_MISMATCH_REFUSAL = "Refusing to pay: this Primer payment names a card network that is not your card's. Nothing was sent.";
1603
1703
  var FILL_REFUSAL = "Could not fill the card fields for Primer. Nothing was sent.";
1604
- var BODY_LIMIT7 = 16384;
1704
+ var BODY_LIMIT8 = 16384;
1605
1705
  var RESPONSE_LIMIT = 32768;
1606
- var ROOT_KEYS4 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
1706
+ var ROOT_KEYS5 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
1607
1707
  var INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear", "cardholderName", "preferredNetwork"];
1608
1708
  var REQUIRED_INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear"];
1609
- var record8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1709
+ var record9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1610
1710
  var owns5 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1611
1711
  var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
1612
1712
  var text2 = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
@@ -1629,7 +1729,7 @@ function isPrimerTokenizeUrl(url, method = "POST") {
1629
1729
  return parsed.href === url && parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && !parsed.search && !parsed.hash && PRIMER_PCI_HOSTS.includes(parsed.hostname) && parsed.pathname === PRIMER_TOKENIZE_PATH;
1630
1730
  }
1631
1731
  function judgeBody2(body) {
1632
- if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT7)
1732
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT8)
1633
1733
  return null;
1634
1734
  let value;
1635
1735
  try {
@@ -1638,12 +1738,12 @@ function judgeBody2(body) {
1638
1738
  catch {
1639
1739
  return null;
1640
1740
  }
1641
- if (!record8(value) || !roundTrips(value, body))
1741
+ if (!record9(value) || !roundTrips(value, body))
1642
1742
  return null;
1643
- if (!Object.keys(value).every((key) => ROOT_KEYS4.includes(key)) || !owns5(value, "paymentInstrument"))
1743
+ if (!Object.keys(value).every((key) => ROOT_KEYS5.includes(key)) || !owns5(value, "paymentInstrument"))
1644
1744
  return null;
1645
1745
  const instrument = value.paymentInstrument;
1646
- if (!record8(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns5(instrument, key)))
1746
+ if (!record9(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns5(instrument, key)))
1647
1747
  return null;
1648
1748
  if (!digits(instrument.number, 12, 19) || !digits(instrument.cvv, 3, 4) || !exact(MONTH3, instrument.expirationMonth) || !exact(YEAR3, instrument.expirationYear))
1649
1749
  return null;
@@ -1671,7 +1771,7 @@ var CLIENT_TOKEN = /^[A-Za-z0-9._~+/=-]{16,4096}$/;
1671
1771
  var REQUEST_ID = /^[A-Za-z0-9-]{1,64}$/;
1672
1772
  var API_VERSION = /^2\.[0-9]{1,2}$/;
1673
1773
  function readHeaders(headers) {
1674
- if (!record8(headers))
1774
+ if (!record9(headers))
1675
1775
  return null;
1676
1776
  const folded = /* @__PURE__ */ new Map();
1677
1777
  for (const [name, value] of Object.entries(headers)) {
@@ -1718,7 +1818,7 @@ function onTokenizePath(url) {
1718
1818
  }
1719
1819
  }
1720
1820
  function primerTokenizeRefusal(request) {
1721
- if (!record8(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
1821
+ if (!record9(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
1722
1822
  return "unsupported";
1723
1823
  return headersRefusal(request.headers) ?? primerBodyRefusal(request.body);
1724
1824
  }
@@ -1733,7 +1833,7 @@ function primerCardOnFileRefusal(url, options, policy = PRIMER_SESSION_CARD_ON_F
1733
1833
  return "unsupported";
1734
1834
  if (new URL(url).hostname !== LIVE_PCI_HOST)
1735
1835
  return null;
1736
- return primerCardOnFileDisclosure(url, policy) && record8(options) && options.keepOnFile === true ? null : "keep_on_file";
1836
+ return primerCardOnFileDisclosure(url, policy) && record9(options) && options.keepOnFile === true ? null : "keep_on_file";
1737
1837
  }
1738
1838
  function primerRefusalMessage(reason) {
1739
1839
  if (reason === "keep_on_file")
@@ -1792,9 +1892,9 @@ function injectPrimerCard(body, card) {
1792
1892
  }
1793
1893
  return { body: JSON.stringify(judged.value), where };
1794
1894
  }
1795
- var TOKEN2 = /^[A-Za-z0-9_-]{8,256}$/;
1895
+ var TOKEN3 = /^[A-Za-z0-9_-]{8,256}$/;
1796
1896
  var ERROR_ID = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
1797
- var CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
1897
+ var CODE2 = /^[A-Z][A-Z0-9_]{0,63}$/;
1798
1898
  var NETWORK_NAME = /^[A-Za-z][A-Za-z0-9 _-]{0,31}$/;
1799
1899
  var RESPONSE_MONTH = /^(?:0?[1-9]|1[0-2])$/;
1800
1900
  var RESPONSE_YEAR = /^(?:[0-9]{2}|20[0-9]{2})$/;
@@ -1817,25 +1917,25 @@ function readAnswer(body) {
1817
1917
  return null;
1818
1918
  try {
1819
1919
  const value = JSON.parse(body);
1820
- return record8(value) ? value : null;
1920
+ return record9(value) ? value : null;
1821
1921
  }
1822
1922
  catch {
1823
1923
  return null;
1824
1924
  }
1825
1925
  }
1826
- var tokenValue = (value) => exact(TOKEN2, value) && !panLike(value);
1926
+ var tokenValue = (value) => exact(TOKEN3, value) && !panLike(value);
1827
1927
  var optional = (value, key, check) => !owns5(value, key) || value[key] === null || check(value[key]);
1828
1928
  function cardDetails(data) {
1829
- if (!record8(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
1929
+ if (!record9(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
1830
1930
  return null;
1831
- if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData", record8))
1931
+ if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData", record9))
1832
1932
  return null;
1833
1933
  const safe = { last4Digits: data.last4Digits };
1834
1934
  for (const key of ["first6Digits", "expirationMonth", "expirationYear", "cardholderName", "network", "isNetworkTokenized"]) {
1835
1935
  if (owns5(data, key) && data[key] !== null)
1836
1936
  safe[key] = data[key];
1837
1937
  }
1838
- if (record8(data.binData)) {
1938
+ if (record9(data.binData)) {
1839
1939
  const bin = {};
1840
1940
  for (const key of BIN_KEYS) {
1841
1941
  if (!owns5(data.binData, key) || data.binData[key] === null)
@@ -1851,7 +1951,7 @@ function cardDetails(data) {
1851
1951
  function threeDsSummary(value) {
1852
1952
  if (value === void 0 || value === null)
1853
1953
  return null;
1854
- if (!record8(value) || !exact(CODE, value.responseCode) || !optional(value, "reasonCode", (v) => exact(CODE, v)) || !optional(value, "reasonText", (v) => plain(v, 256)) || !optional(value, "protocolVersion", (v) => v === "" || exact(PROTOCOL, v)) || !optional(value, "challengeIssued", (v) => typeof v === "boolean"))
1954
+ if (!record9(value) || !exact(CODE2, value.responseCode) || !optional(value, "reasonCode", (v) => exact(CODE2, v)) || !optional(value, "reasonText", (v) => plain(v, 256)) || !optional(value, "protocolVersion", (v) => v === "" || exact(PROTOCOL, v)) || !optional(value, "challengeIssued", (v) => typeof v === "boolean"))
1855
1955
  return void 0;
1856
1956
  const safe = { responseCode: value.responseCode };
1857
1957
  for (const key of ["reasonCode", "reasonText", "protocolVersion", "challengeIssued"]) {
@@ -1879,7 +1979,7 @@ function rebuildToken(value) {
1879
1979
  }
1880
1980
  function primerErrorId(body) {
1881
1981
  const value = readAnswer(body);
1882
- const id = value && record8(value.error) ? value.error.errorId : null;
1982
+ const id = value && record9(value.error) ? value.error.errorId : null;
1883
1983
  return exact(ERROR_ID, id) && !panLike(id) ? id : null;
1884
1984
  }
1885
1985
  function matchesCard(details, card) {
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 3e73afac7d2a16c36e81a7ddca5ea143be4880e056cb31558d31d3d4d72d84b1
3
+ // artifact-sha256: 7af6b8dae5c779ea1901a4deac135effd97b00ea0925db7a269c82330e110cc9
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1715,6 +1715,34 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
1715
1715
  passthroughHeaders: ["^next-action$", "^next-router-state-tree$", "^accept$"],
1716
1716
  jsonCardFields: [[1, "data", "attributes", "details", "card_number"]],
1717
1717
  passWithoutCard: "checkout\\.paymongo\\.com/(?:cs_)?[A-Za-z0-9]{24}"
1718
+ },
1719
+ {
1720
+ psp: "gopay",
1721
+ // GoPay's payment gateway (Czech Republic and Slovakia), which a shop opens as a page or as an overlay frame
1722
+ // (embed.js). The gateway loads GoPay's card frame from https://secure.gopay.com/gp-card-comm/h/<hex
1723
+ // token>?lang=..&ver=..&SG=<64 hex>, and on Pay the frame posts the card as one JSON body to its own URL:
1724
+ // {"SG":<the URL's SG>,"cardData":{"cardNumber":<16 digits>,"cvc":<3 digits>,"expiration":<YYMM>},
1725
+ // "type":"PAYMENT_CARD"}, unencrypted. gopay.js holds the request rule the pause decision, the API's create
1726
+ // and the device all read: exactly a card frame URL, a JSON Content-Type, and the frame's body byte for byte,
1727
+ // with a body that names Click to Pay (the frame's "save the card" box) refused. The body names no amount
1728
+ // and no shop: the payment GoPay keeps on its side holds both, so a live request is taken only for a
1729
+ // checkout origin a reviewed pin lists. The frame's host answers no browser origin, so the card leaves the
1730
+ // cardholder's device only through the checkout relay. The answer tells the gateway only whether GoPay took
1731
+ // the card; the gateway then reads the payment's end from its own API and runs 3-D Secure by redirect.
1732
+ // secure.sandbox.gopay.com is GoPay's sandbox: pinned (S7) to test rows, which send only test cards.
1733
+ // Ships dark: served only to a client that declares `gopay_card_form`, gated off for every org until an
1734
+ // operator lists one (`gopay:<org>`), and autopilot never sends this request (autopilot.js
1735
+ // DEVICE_REPLAY_REFUSED_OPERATIONS). `(?![\w/.%-])` ends the path for a consumer that tests `match`
1736
+ // unanchored.
1737
+ capability: "gopay_card_form",
1738
+ match: "secure(?:\\.sandbox)?\\.gopay\\.com/gp-card-comm/h/[0-9A-Fa-f]{16,1024}(?![\\w/.%-])",
1739
+ hosts: ["^secure\\.gopay\\.com$", "^secure\\.sandbox\\.gopay\\.com$"],
1740
+ sandboxHosts: ["^secure\\.sandbox\\.gopay\\.com$"],
1741
+ hostEnvironmentPinned: true,
1742
+ encoding: "json",
1743
+ // The frame's own Accept rides as it sent it; origin, referer and cookies never do.
1744
+ passthroughHeaders: ["^accept$"],
1745
+ jsonCardFields: [["cardData", "cardNumber"]]
1718
1746
  }
1719
1747
  ]);
1720
1748
  var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, deviceSteps, deviceEncrypt, ...definition }) => definition));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/sdk",
3
- "version": "0.23.16",
3
+ "version": "0.23.17",
4
4
  "description": "Agentcard's SDK: one client, Agentcard, with two modules. checkout pauses a browser purchase and pays it with the person's own card; voice connects a person to your product once and approves a purchase by voice inside it. Card data never enters your process.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",