@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:
|
|
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
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
if (!
|
|
1653
|
-
return
|
|
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
|
|
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
|
|
1673
|
-
|
|
1674
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
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" &&
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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",
|