@agent-cards/sdk 0.23.0 → 0.23.1

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,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Stop the card fields adyen-web 5 encrypts at a reviewed Adyen merchant profile. adyen-web 5 seals each field as an RSA-OAEP / A256CBC-HS512 JWE, where adyen-web 6 writes RSA-OAEP-256 / A256GCM, and the SDK read only adyen-web 6's form. So the card requests of Cathay Pacific's payment page and Dunelm's legacy checkout, and Cathay's charge request after its signing step, reached the merchant with the agent's dummy card. The adapters now read both forms as a card. Both profiles ship in `observe`, so the adapters abort each of these requests before it leaves the browser and report it as `merchant_profile_observed`. The Vault writes only adyen-web 6's form, so the API refuses a create for a card request in adyen-web 5's form with `cse_template_unsupported`, and nothing is charged.
6
+
7
+ - Abort the requests in which IHG's pay page sends the typed card outside its reviewed card request, in every status, as `card_endpoint_refused` with reason `card_resend`. The page posts the same booking to IHG's enrollment service while its pre-ticked "Enroll for free" box stays ticked, posts it on other paths for more than one room, a travel agent or Business Rewards, and at a hotel that asks for 3-D Secure first posts the number to two 3-D Secure calls. The SDK let each of these reach IHG with the agent's dummy card. A booking with the box unticked reaches IHG's reviewed card request, which the adapters refuse as before while IHG is in `observe`.
8
+
5
9
  ## 0.23.0
6
10
 
7
11
  ### Upgrading from 0.22.0
@@ -1,6 +1,7 @@
1
1
  declare var MERCHANT_EXCHANGES_MAX: number;
2
2
  declare var MERCHANT_PROFILE_RECOGNIZERS: any;
3
3
  declare function declaredEndpointRefusal(endpoint: any): string | null;
4
+ declare function isAdyenWebJweCompact(s: any): boolean;
4
5
  declare function sha256Hex(input: any): string;
5
6
  declare function merchantAmountFromResponses(profile: any, input: any): {
6
7
  ok: boolean;
@@ -274,4 +275,4 @@ declare function declaredUrlGlobs(endpoint: any): string[];
274
275
  declare function merchantProfileRulesMatch(profileId: any, served: any): boolean;
275
276
  declare function merchantProfileEnvironment(profileId: any): "production" | "sandbox";
276
277
  declare function merchantProfileEndpointFor(profileId: any, checkoutOrigin: any): string | null;
277
- export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
278
+ export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, isAdyenWebJweCompact, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 7e4df686b96640378871d711a94bb545232ca858824a30c20e5a9d242a255f3b
3
+ // artifact-sha256: c979802cf1a5aa95eedd5dd00949cd56c5c478ca3c637b3232b69597cc62d4a4
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1640,40 +1640,38 @@ function merchantBodyRefusal(profile, body) {
1640
1640
  return null;
1641
1641
  }
1642
1642
  // src/adyen-jwe.js
1643
- var ADYEN_JWE_HEADER = { alg: "RSA-OAEP-256", enc: "A256GCM", version: "1" };
1644
1643
  var BASE64URL = /^[A-Za-z0-9_-]+$/;
1645
- function isAdyenJweCompact(s) {
1646
- if (typeof s !== "string" || s.length > 2048)
1647
- return false;
1648
- const parts = s.split(".");
1649
- if (parts.length !== 5)
1650
- return false;
1651
- const [header, encryptedKey, iv, ciphertext, tag] = parts;
1652
- if (!parts.every((p) => p.length > 0 && BASE64URL.test(p)))
1653
- return false;
1654
- if (encryptedKey.length !== 342 || iv.length !== 16 || tag.length !== 22)
1655
- return false;
1656
- if (ciphertext.length > 1024)
1657
- return false;
1658
- return isAdyenJweHeader(header);
1659
- }
1660
- function isAdyenJweHeader(segment) {
1661
- if (!BASE64URL.test(segment) || segment.length > 128)
1662
- return false;
1644
+ var ADYEN_WEB_JWE_FORMATS = /* @__PURE__ */ Object.freeze([
1645
+ /* @__PURE__ */ Object.freeze({ alg: "RSA-OAEP-256", enc: "A256GCM", iv: 16, tag: 22 }),
1646
+ /* @__PURE__ */ Object.freeze({ alg: "RSA-OAEP", enc: "A256CBC-HS512", iv: 22, tag: 43 })
1647
+ ]);
1648
+ var ADYEN_WEB_CIPHERTEXT_MAX = 8192;
1649
+ var ADYEN_WEB_HEADER_MAX = 128;
1650
+ function adyenWebJweHeader(segment) {
1651
+ if (!BASE64URL.test(segment) || segment.length > ADYEN_WEB_HEADER_MAX)
1652
+ return null;
1663
1653
  let parsed;
1664
1654
  try {
1665
1655
  parsed = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(segment.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
1666
1656
  }
1667
1657
  catch {
1668
- return false;
1658
+ return null;
1669
1659
  }
1670
1660
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1661
+ return null;
1662
+ return Object.keys(parsed).sort().join(",") === "alg,enc,version" && parsed.version === "1" ? parsed : null;
1663
+ }
1664
+ function isAdyenWebJweCompact(s) {
1665
+ if (typeof s !== "string" || s.length > ADYEN_WEB_HEADER_MAX + 342 + 22 + ADYEN_WEB_CIPHERTEXT_MAX + 43 + 4)
1671
1666
  return false;
1672
- const obj = parsed;
1673
- const keys = Object.keys(obj).sort();
1674
- if (keys.join(",") !== "alg,enc,version")
1667
+ const parts = s.split(".");
1668
+ if (parts.length !== 5 || !parts.every((p) => p.length > 0 && BASE64URL.test(p)))
1669
+ return false;
1670
+ const [header, encryptedKey, iv, ciphertext, tag] = parts;
1671
+ if (encryptedKey.length !== 342 || ciphertext.length > ADYEN_WEB_CIPHERTEXT_MAX)
1675
1672
  return false;
1676
- return obj.alg === ADYEN_JWE_HEADER.alg && obj.enc === ADYEN_JWE_HEADER.enc && obj.version === ADYEN_JWE_HEADER.version;
1673
+ const parsed = adyenWebJweHeader(header);
1674
+ return parsed !== null && ADYEN_WEB_JWE_FORMATS.some((form) => parsed.alg === form.alg && parsed.enc === form.enc && iv.length === form.iv && tag.length === form.tag);
1677
1675
  }
1678
1676
  // src/sha256.js
1679
1677
  var K = /* @__PURE__ */ new Uint32Array([
@@ -1873,16 +1871,18 @@ function merchantProfileUrlRelation(profileId, rawUrl) {
1873
1871
  return "then";
1874
1872
  return otherCardRequestOf(profile, url) ? "other" : null;
1875
1873
  }
1876
- var JWE_AT = /ey[A-Za-z0-9_-]{1,126}\.[A-Za-z0-9_-]{342}\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{1,1024}\.[A-Za-z0-9_-]{22}/y;
1874
+ var JWE_AT = /* @__PURE__ */ ADYEN_WEB_JWE_FORMATS.map(({ iv, tag }) => new RegExp(`ey[A-Za-z0-9_-]{1,126}\\.[A-Za-z0-9_-]{342}\\.[A-Za-z0-9_-]{${iv}}\\.[A-Za-z0-9_-]{1,${ADYEN_WEB_CIPHERTEXT_MAX}}\\.[A-Za-z0-9_-]{${tag}}`, "y"));
1877
1875
  var LEGACY_BLOB = /adyenjs_0_1_\d{1,4}(?:\$|%24)/i;
1878
1876
  function textCarriesCiphertext(text) {
1879
1877
  if (LEGACY_BLOB.test(text))
1880
1878
  return true;
1881
1879
  for (let at = text.indexOf("ey"); at !== -1; at = text.indexOf("ey", at + 1)) {
1882
- JWE_AT.lastIndex = at;
1883
- const match = JWE_AT.exec(text);
1884
- if (match && isAdyenJweCompact(match[0]))
1885
- return true;
1880
+ for (const pattern of JWE_AT) {
1881
+ pattern.lastIndex = at;
1882
+ const match = pattern.exec(text);
1883
+ if (match && isAdyenWebJweCompact(match[0]))
1884
+ return true;
1885
+ }
1886
1886
  }
1887
1887
  return false;
1888
1888
  }
@@ -1947,7 +1947,7 @@ function holderRefusal(profile, parsed) {
1947
1947
  return `the paused body carries no card object where ${profile.merchant}'s card request holds it`;
1948
1948
  }
1949
1949
  const values = Object.values(profile.fields).map((name) => Object.prototype.hasOwnProperty.call(holder.value, name) ? holder.value[name] : void 0);
1950
- if (!values.every((value) => typeof value === "string" && isAdyenJweCompact(value))) {
1950
+ if (!values.every((value) => typeof value === "string" && isAdyenWebJweCompact(value))) {
1951
1951
  return `the paused body does not carry the four encrypted card fields ${profile.merchant}'s card request holds a new card in`;
1952
1952
  }
1953
1953
  if (new Set(values).size !== values.length)
@@ -2145,4 +2145,4 @@ function merchantProfileEndpointFor(profileId, checkoutOrigin) {
2145
2145
  const query = Object.entries(profile.query).map(([key, value]) => `${key}=${value}`).join("&");
2146
2146
  return `${pair.endpoint}${profile.path}${query ? `?${query}` : ""}`;
2147
2147
  }
2148
- export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
2148
+ export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, isAdyenWebJweCompact, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 82c93c756fd934cb52d2d868b73e546d741aebbe53f12d4c38c9173abf7a0c03
3
+ // artifact-sha256: ed096050a26a3c26d7fc8167d1bfbeb05e120b392a9aa7a34a924514c56fc870
4
4
  // src/json-path.js
5
5
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
6
6
  function isPlainObject(v) {
@@ -1117,7 +1117,8 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
1117
1117
  'ReservationService.createReservation posts the first reservation, JSON with withCredentials, to https://apis.ihg.com/reservations/v3/hotels: {id, imageStatus "COMMIT", segments: [{payment: {paymentByPoints, paymentInformation: {paymentCard}, guaranteeType, billingAddress}, ...}], userProfiles, hotels}.',
1118
1118
  "paymentCard (getPaymentInformation): {cardCode, expireDate `${expirationMonth}${expirationYear}` (MMYY), cardHolderName, cvvNumber: {token: <CVV>}, cardNumber: {token: <PAN, spaces stripped>}}, plus 3dsData after a PSD2 check.",
1119
1119
  "Headers: x-ihg-api-key (public client key), content-type application/json; charset=UTF-8, ihg-language, X-IHG-SSO-TOKEN, x-accept-currency (USD), IHG-SellSource, Forter-Token; interceptors add IHG-SessionId and IHG-TransactionId.",
1120
- "Hotels that set isPaymentServiceInformationRequired first send the PAN to /payments/threedsecure/v1/token and /paymentSetup and put the result in 3dsData, which is refused here; China UnionPay and PayPal set cupTokenId and paymentSource, also refused."
1120
+ "Hotels that set isPaymentServiceInformationRequired first send the PAN to /payments/threedsecure/v1/token ({cardToken: <PAN>, cardType}) and /paymentSetup (card.token: <PAN>) and put the result in 3dsData, which is refused here; China UnionPay and PayPal set cupTokenId and paymentSource, also refused. Both 3-D Secure posts are in resends below.",
1121
+ 'pay-ng v14.149.0, read live Oct 6, 2026 (live-checks-oct6/ihg): the pay page posts the same reservation body, card in the clear, to /channels/direct/quickEnroll/v8/hotelReservations while its pre-ticked "Enroll for free as you book" box stays ticked (/quickEnroll/v9/hotelReservations behind the shouldUseNewQuickEnrolV9 flag, /quickEnroll/v8/multiRoomReservation for a cart), and to /reservations/v3/hotels/rooms/fromCart for more than one room, /reservations/v3/hotels/rooms/agent/reservations for a travel agent and /reservations/v3/businessRewardReservations for Business Rewards. Each is in resends below, so the SDK aborts it in every status.'
1121
1122
  ],
1122
1123
  har: null,
1123
1124
  checkout: "https://www.ihg.com",
@@ -1171,7 +1172,17 @@ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
1171
1172
  amount: null,
1172
1173
  currency: "USD",
1173
1174
  response: { headers: ["content-type", "set-cookie"], withhold: [] },
1174
- resends: [],
1175
+ // The page's other posts of the same card (evidence above): an approval never covers them.
1176
+ resends: [
1177
+ { method: "POST", path: "/channels/direct/quickEnroll/v8/hotelReservations", query: {} },
1178
+ { method: "POST", path: "/channels/direct/quickEnroll/v9/hotelReservations", query: {} },
1179
+ { method: "POST", path: "/channels/direct/quickEnroll/v8/multiRoomReservation", query: {} },
1180
+ { method: "POST", path: "/reservations/v3/hotels/rooms/fromCart", query: {} },
1181
+ { method: "POST", path: "/reservations/v3/hotels/rooms/agent/reservations", query: {} },
1182
+ { method: "POST", path: "/reservations/v3/businessRewardReservations", query: {} },
1183
+ { method: "POST", path: "/payments/threedsecure/v1/token", query: {} },
1184
+ { method: "POST", path: "/payments/threedsecure/v1/paymentSetup", query: {} }
1185
+ ],
1175
1186
  challenge: null,
1176
1187
  facts: null,
1177
1188
  bootstrap: "/robots.txt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/sdk",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
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",