@agent-cards/sdk 0.23.28 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +137 -99
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
- package/dist/adyen-merchant-hosted.generated.js +459 -21
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +1398 -106
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +231 -175
- package/dist/client.d.ts +104 -22
- package/dist/client.js +215 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/datatrans.generated.js +22 -1
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +105 -10
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +27 -7
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +86 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +46 -5
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.d.ts +0 -4
- package/dist/preflight.generated.js +24 -1
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +1418 -120
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +105 -10
- package/dist/stripe-checkout.generated.js +1405 -234
- package/dist/substitutions.generated.js +460 -26
- package/dist/tap-payments.generated.js +105 -10
- package/dist/twoc2p.generated.js +105 -10
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
|
@@ -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: d9723a3c2f261d8023b1966fd865481e713eaaa2756ba30fe110e543d68d5704
|
|
4
4
|
// src/adyen-jwe.js
|
|
5
5
|
var ADYEN_JWE_HEADER = { alg: "RSA-OAEP-256", enc: "A256GCM", version: "1" };
|
|
6
6
|
var BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
@@ -43,9 +43,65 @@ function isPlainObject(v) {
|
|
|
43
43
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
44
44
|
}
|
|
45
45
|
var MAX_PATH_STEPS = 16;
|
|
46
|
+
var MAX_TEXT_STEPS = 2;
|
|
47
|
+
var TEXT_STEP = /* @__PURE__ */ Object.freeze({ text: "json" });
|
|
48
|
+
function isTextStep(step) {
|
|
49
|
+
return isPlainObject(step) && Object.keys(step).length === 1 && step.text === "json";
|
|
50
|
+
}
|
|
51
|
+
function sameStep(a, b) {
|
|
52
|
+
return a === b || isTextStep(a) && isTextStep(b);
|
|
53
|
+
}
|
|
54
|
+
function spellJson(parsed, form) {
|
|
55
|
+
const text = JSON.stringify(parsed);
|
|
56
|
+
if (form !== "php")
|
|
57
|
+
return text;
|
|
58
|
+
return text.replace(/\//g, "\\/").replace(/[\u0080-\uffff]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
59
|
+
}
|
|
60
|
+
function jsonTextForm(value) {
|
|
61
|
+
if (typeof value !== "string" || !/^[[{]/.test(value))
|
|
62
|
+
return void 0;
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(value);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
if (parsed === null || typeof parsed !== "object")
|
|
71
|
+
return void 0;
|
|
72
|
+
for (const form of ["js", "php"]) {
|
|
73
|
+
try {
|
|
74
|
+
if (spellJson(parsed, form) === value)
|
|
75
|
+
return { parsed, form };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
function jsonText(value) {
|
|
84
|
+
return jsonTextForm(value)?.parsed;
|
|
85
|
+
}
|
|
46
86
|
function isJsonPath(path) {
|
|
47
87
|
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);
|
|
48
88
|
}
|
|
89
|
+
function isProfilePath(path) {
|
|
90
|
+
if (!Array.isArray(path) || path.length > MAX_PATH_STEPS)
|
|
91
|
+
return false;
|
|
92
|
+
let texts = 0;
|
|
93
|
+
for (const [i, step] of path.entries()) {
|
|
94
|
+
if (isTextStep(step)) {
|
|
95
|
+
texts += 1;
|
|
96
|
+
if (i === 0 || i === path.length - 1 || isTextStep(path[i - 1]) || texts > MAX_TEXT_STEPS)
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
else if (!isJsonPath([step])) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
49
105
|
function jsonAt(root, path) {
|
|
50
106
|
let node = root;
|
|
51
107
|
for (const step of path) {
|
|
@@ -60,6 +116,42 @@ function jsonAt(root, path) {
|
|
|
60
116
|
}
|
|
61
117
|
return { found: true, value: node };
|
|
62
118
|
}
|
|
119
|
+
function profileAt(root, path) {
|
|
120
|
+
if (!isProfilePath(path))
|
|
121
|
+
return { found: false };
|
|
122
|
+
let node = root;
|
|
123
|
+
for (const step of path) {
|
|
124
|
+
if (isTextStep(step)) {
|
|
125
|
+
const inner = jsonText(node);
|
|
126
|
+
if (inner === void 0)
|
|
127
|
+
return { found: false };
|
|
128
|
+
node = inner;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const at = jsonAt(node, [step]);
|
|
132
|
+
if (!at.found)
|
|
133
|
+
return { found: false };
|
|
134
|
+
node = at.value;
|
|
135
|
+
}
|
|
136
|
+
return { found: true, value: node };
|
|
137
|
+
}
|
|
138
|
+
function editAtProfilePath(root, path, edit) {
|
|
139
|
+
const at = profileAt(root, path);
|
|
140
|
+
if (!at.found || !isPlainObject(at.value))
|
|
141
|
+
return false;
|
|
142
|
+
const k = path.findIndex(isTextStep);
|
|
143
|
+
if (k === -1) {
|
|
144
|
+
edit(at.value);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const holder = jsonAt(root, path.slice(0, k - 1)).value;
|
|
148
|
+
const key = path[k - 1];
|
|
149
|
+
const text = jsonTextForm(holder[key]);
|
|
150
|
+
if (!text || !editAtProfilePath(text.parsed, path.slice(k + 1), edit))
|
|
151
|
+
return false;
|
|
152
|
+
holder[key] = spellJson(text.parsed, text.form);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
63
155
|
function roundTrips(parsed, body) {
|
|
64
156
|
try {
|
|
65
157
|
return JSON.stringify(parsed) === body;
|
|
@@ -69,12 +161,12 @@ function roundTrips(parsed, body) {
|
|
|
69
161
|
}
|
|
70
162
|
}
|
|
71
163
|
function sameSteps(a, b) {
|
|
72
|
-
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((step, i) => step
|
|
164
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((step, i) => sameStep(step, b[i]));
|
|
73
165
|
}
|
|
74
166
|
function pathsOverlap(a, b) {
|
|
75
167
|
const n = Math.min(a.length, b.length);
|
|
76
168
|
for (let i = 0; i < n; i += 1)
|
|
77
|
-
if (a[i]
|
|
169
|
+
if (!sameStep(a[i], b[i]))
|
|
78
170
|
return false;
|
|
79
171
|
return true;
|
|
80
172
|
}
|
|
@@ -86,7 +178,12 @@ function valuesIgnoringCase(root, path) {
|
|
|
86
178
|
for (const step of path) {
|
|
87
179
|
const next = [];
|
|
88
180
|
for (const node of nodes) {
|
|
89
|
-
if (
|
|
181
|
+
if (isTextStep(step)) {
|
|
182
|
+
const inner = jsonText(node);
|
|
183
|
+
if (inner !== void 0)
|
|
184
|
+
next.push(inner);
|
|
185
|
+
}
|
|
186
|
+
else if (typeof step === "number") {
|
|
90
187
|
if (Array.isArray(node) && Number.isSafeInteger(step) && step >= 0 && step < node.length)
|
|
91
188
|
next.push(node[step]);
|
|
92
189
|
}
|
|
@@ -101,7 +198,7 @@ function valuesIgnoringCase(root, path) {
|
|
|
101
198
|
return nodes;
|
|
102
199
|
}
|
|
103
200
|
function describePath(path) {
|
|
104
|
-
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
201
|
+
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : isTextStep(step) ? "<json>" : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
105
202
|
}
|
|
106
203
|
// src/substitution-primitives.js
|
|
107
204
|
var SubstitutionError = class extends Error {
|
|
@@ -150,7 +247,7 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
150
247
|
const path = Array.isArray(spec.path) ? [...spec.path] : spec.path;
|
|
151
248
|
const fields = isPlainObject(spec.fields) ? Object.fromEntries(Object.entries(spec.fields)) : null;
|
|
152
249
|
const remove = Array.isArray(spec.remove) ? [...spec.remove] : spec.remove ?? [];
|
|
153
|
-
if (!
|
|
250
|
+
if (!isProfilePath(path))
|
|
154
251
|
throw new SubstitutionError("refusing substitution path");
|
|
155
252
|
if (!fields || Object.keys(fields).length === 0)
|
|
156
253
|
throw new SubstitutionError("substitutions carry no fields");
|
|
@@ -184,7 +281,7 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
184
281
|
}
|
|
185
282
|
if (!isPlainObject(parsed))
|
|
186
283
|
throw new SubstitutionError("the paused body is not a JSON object");
|
|
187
|
-
const holder =
|
|
284
|
+
const holder = profileAt(parsed, path);
|
|
188
285
|
if (!holder.found || !isPlainObject(holder.value))
|
|
189
286
|
throw new SubstitutionError(`the paused body has no object at ${describePath(path)}`);
|
|
190
287
|
const swaps = /* @__PURE__ */ new Map();
|
|
@@ -236,15 +333,19 @@ function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
|
236
333
|
catch {
|
|
237
334
|
throw new SubstitutionError("the substituted body is not JSON");
|
|
238
335
|
}
|
|
239
|
-
const written =
|
|
336
|
+
const written = profileAt(out, path);
|
|
240
337
|
if (!written.found || !isPlainObject(written.value) || names.some((name) => written.value[name] !== fields[name]) || dummies.some((dummy) => swapped.includes(dummy))) {
|
|
241
338
|
throw new SubstitutionError(`the substitution did not land at ${describePath(path)}`);
|
|
242
339
|
}
|
|
243
340
|
if (remove.length === 0 && set.length === 0)
|
|
244
341
|
return swapped;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
342
|
+
if (remove.length > 0 && !editAtProfilePath(out, path, (object) => {
|
|
343
|
+
for (const key of remove)
|
|
344
|
+
if (Object.prototype.hasOwnProperty.call(object, key))
|
|
345
|
+
delete object[key];
|
|
346
|
+
})) {
|
|
347
|
+
throw new SubstitutionError(`the substitution did not land at ${describePath(path)}`);
|
|
348
|
+
}
|
|
248
349
|
for (const entry of set) {
|
|
249
350
|
const parent = jsonAt(out, entry.path.slice(0, -1));
|
|
250
351
|
if (!parent.found || !isPlainObject(parent.value))
|
|
@@ -733,6 +834,289 @@ var MERCHANT_HOSTED_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
|
733
834
|
facts: [{ path: ["sign_request", "raw_card_bin"], fact: "bin6" }],
|
|
734
835
|
then: { method: "POST", path: "/cpp-paypage/v1/miles-plus-cash-authorise", query: {}, holder: [] },
|
|
735
836
|
adyen: { environment: "live", clientKey: null, publicKeySha256: null }
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
// Read Oct 23, 2026, from teenage.engineering's shipped code and a card-free observe run on its
|
|
840
|
+
// US checkout (every non-GET aborted once the card form was reached, the placeholder typed into
|
|
841
|
+
// adyen-web's secured fields, nothing pressed). The store runs on Centra. Its checkout posts the
|
|
842
|
+
// order form to /_api/mw/selections/<selection>/payment2 with paymentInitiateOnly (no card), and
|
|
843
|
+
// Centra answers with the Adyen Drop-in's own script (adyen-web 6.41.1, environment live, this
|
|
844
|
+
// client key, holder name required, enableStoreDetails false). That script's onSubmit hands
|
|
845
|
+
// adyen-web's state.data to the page as paymentMethodSpecificFields (centra_checkout_payment_callback),
|
|
846
|
+
// and the page's finishAdyenPayment (assets/shop.*.js, cart-api.*.js) posts
|
|
847
|
+
// {paymentMethod: 'adyen-drop-in', address: <billing address>, paymentMethodSpecificFields:
|
|
848
|
+
// state.data, termsAndConditions: true, affiliate} to the same path through fetch, with no
|
|
849
|
+
// content type of its own (text/plain;charset=UTF-8 on the wire, a JSON text). The body names no
|
|
850
|
+
// amount: Centra charges the selection the path names, so the total comes from the selection's
|
|
851
|
+
// own answers below. The Drop-in's stored-payment update flow adds contracts. Public key read
|
|
852
|
+
// from checkoutshopper-live.adyen.com on 2026-10-23 (UTC). Not yet seen on the wire: the card
|
|
853
|
+
// request itself, which leaves only on the Pay press, so a press run confirms the body.
|
|
854
|
+
id: "teenage_engineering_centra_payment",
|
|
855
|
+
merchant: "teenage engineering",
|
|
856
|
+
status: "observe",
|
|
857
|
+
origins: [{ checkout: "https://teenage.engineering", endpoint: "https://teenage.engineering" }],
|
|
858
|
+
method: "POST",
|
|
859
|
+
path: "/_api/mw/selections/:selectionId/payment2",
|
|
860
|
+
query: {},
|
|
861
|
+
requires: [{ path: ["paymentMethod"], equals: "adyen-drop-in" }],
|
|
862
|
+
holder: ["paymentMethodSpecificFields", "paymentMethod"],
|
|
863
|
+
fields: {
|
|
864
|
+
encryptedCardNumber: "encryptedCardNumber",
|
|
865
|
+
encryptedExpiryMonth: "encryptedExpiryMonth",
|
|
866
|
+
encryptedExpiryYear: "encryptedExpiryYear",
|
|
867
|
+
encryptedSecurityCode: "encryptedSecurityCode"
|
|
868
|
+
},
|
|
869
|
+
// adyen-web 6.x stamps the dummy's brand after its bin lookup answers.
|
|
870
|
+
remove: ["brand"],
|
|
871
|
+
refuse: [
|
|
872
|
+
{ path: ["paymentMethodSpecificFields", "storePaymentMethod"], when: "true" },
|
|
873
|
+
{ path: ["paymentMethodSpecificFields", "paymentMethod", "storedPaymentMethodId"], when: "present" },
|
|
874
|
+
{ path: ["paymentMethodSpecificFields", "paymentMethod", "recurringDetailReference"], when: "present" },
|
|
875
|
+
// The order form's own post on the same path initiates the payment and carries no card.
|
|
876
|
+
{ path: ["paymentInitiateOnly"], when: "true" },
|
|
877
|
+
// The Drop-in's stored-payment update flow names subscription contracts the card would pay later.
|
|
878
|
+
{ path: ["contracts"], when: "present" }
|
|
879
|
+
],
|
|
880
|
+
amount: null,
|
|
881
|
+
// Read Oct 23, 2026 (the observe run above). Every selection call answers the whole selection
|
|
882
|
+
// as JSON: the read, the country and VAT changes, item quantities and the address step (which
|
|
883
|
+
// wraps it in `selection`). totals.grandTotalPriceAsNumber is what the Drop-in tells its own
|
|
884
|
+
// wallets to charge, in major units (2497.29 in the run), and currency names the currency. The
|
|
885
|
+
// card request's URL carries the same selection id, which binds each answer to it. Voucher,
|
|
886
|
+
// VAT-number and item-removal answers were not seen in the run, so they are not read: any of
|
|
887
|
+
// those calls after the chosen answer makes it stale.
|
|
888
|
+
amountSource: {
|
|
889
|
+
id: "teenage_engineering_selection_totals",
|
|
890
|
+
origin: "endpoint",
|
|
891
|
+
format: "json",
|
|
892
|
+
responses: [
|
|
893
|
+
{ method: "GET", path: "/_api/mw/selections/:selectionId", query: {} },
|
|
894
|
+
{ method: "PUT", path: "/_api/mw/selections/:selectionId", query: {} },
|
|
895
|
+
{ method: "PUT", path: "/_api/mw/selections/:selectionId/address", query: {}, at: ["selection"] },
|
|
896
|
+
{ method: "DELETE", path: "/_api/mw/selections/:selectionId/vat", query: {} },
|
|
897
|
+
{ method: "POST", path: "/_api/mw/selections/:selectionId/items/:itemId/quantity/:quantity", query: {} },
|
|
898
|
+
{ method: "DELETE", path: "/_api/mw/selections/:selectionId/items/:itemId/quantity/:quantity", query: {} }
|
|
899
|
+
],
|
|
900
|
+
total: { path: ["totals", "grandTotalPriceAsNumber"], format: "number" },
|
|
901
|
+
unit: "major",
|
|
902
|
+
currency: "USD",
|
|
903
|
+
currencyPath: ["currency"],
|
|
904
|
+
bind: [{ placeholder: "selectionId", cardPlaceholder: "selectionId" }],
|
|
905
|
+
voidedBy: [
|
|
906
|
+
{ method: "POST", path: "/_api/mw/selections/:selectionId/vouchers/:voucherCode", query: {} },
|
|
907
|
+
{ method: "DELETE", path: "/_api/mw/selections/:selectionId/vouchers", query: {} },
|
|
908
|
+
{ method: "POST", path: "/_api/mw/selections/:selectionId/vat/:vatNumber", query: {} },
|
|
909
|
+
{ method: "DELETE", path: "/_api/mw/selections/:selectionId/items/:itemId", query: {} }
|
|
910
|
+
],
|
|
911
|
+
maxAgeMs: 9e5
|
|
912
|
+
},
|
|
913
|
+
adyen: {
|
|
914
|
+
environment: "live",
|
|
915
|
+
clientKey: "live_FAQ7CUZM7JAO7K6CGXI67KMZ64K35AVH",
|
|
916
|
+
publicKeySha256: "b99a44120544b609c01ede6041c82b481d23423d7e956355022a5d7f362b5349"
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
// Read Oct 23, 2026, from www.scheels.com's shipped checkout chunk (a Next.js page on commercetools
|
|
921
|
+
// Frontastic) and a card-free observe run on its guest checkout (every non-GET aborted once the card
|
|
922
|
+
// form was reached, the placeholder typed into adyen-web 5.5.3's secured fields, Review Order pressed:
|
|
923
|
+
// the address save before it was aborted too, so the card request was not reached). The Card
|
|
924
|
+
// component's onSubmit (showPayButton false; the page submits it on Review Order) sets
|
|
925
|
+
// state.data.paymentMethod.brand from onBrand, then calls the Frontastic action
|
|
926
|
+
// /cart/setAdyenCreditCard with {payment: {paymentMethod: 'CREDIT_CARD', bin (onBinValue's six
|
|
927
|
+
// digits, or onFieldValid's issuerBin), lastFour (onFieldValid's endDigits), deviceFingerprint (when
|
|
928
|
+
// the page has one)}, state: JSON.stringify(state)}. The SDK posts JSON.stringify(payload) to
|
|
929
|
+
// apihub.scheels.com/frontastic/action/cart/setAdyenCreditCard (its path joiner collapses the
|
|
930
|
+
// action's leading '/') with content-type application/json and the Frontastic-Session header. So the
|
|
931
|
+
// four JWEs sit in adyen-web's state, inside the `state` text. bin and lastFour describe the dummy:
|
|
932
|
+
// the approved card's BIN6 and last four are written there. The body names no amount: the cart the
|
|
933
|
+
// session holds is charged when Place Order sends cart/checkout, which carries no card. Public key
|
|
934
|
+
// read from checkoutshopper-live-us.adyen.com on 2026-10-23 (UTC).
|
|
935
|
+
id: "scheels_frontastic_adyen_card",
|
|
936
|
+
merchant: "SCHEELS",
|
|
937
|
+
status: "observe",
|
|
938
|
+
origins: [{ checkout: "https://www.scheels.com", endpoint: "https://apihub.scheels.com" }],
|
|
939
|
+
method: "POST",
|
|
940
|
+
path: "/frontastic/action/cart/setAdyenCreditCard",
|
|
941
|
+
query: {},
|
|
942
|
+
requires: [{ path: ["payment", "paymentMethod"], equals: "CREDIT_CARD" }],
|
|
943
|
+
holder: ["state", TEXT_STEP, "data", "paymentMethod"],
|
|
944
|
+
fields: {
|
|
945
|
+
encryptedCardNumber: "encryptedCardNumber",
|
|
946
|
+
encryptedExpiryMonth: "encryptedExpiryMonth",
|
|
947
|
+
encryptedExpiryYear: "encryptedExpiryYear",
|
|
948
|
+
encryptedSecurityCode: "encryptedSecurityCode"
|
|
949
|
+
},
|
|
950
|
+
// The page copies onBrand's code (the dummy's) into the state before it is stringified.
|
|
951
|
+
remove: ["brand"],
|
|
952
|
+
refuse: [
|
|
953
|
+
// enableStoreDetails is on for a signed-in shopper only.
|
|
954
|
+
{ path: ["state", TEXT_STEP, "data", "storePaymentMethod"], when: "true" },
|
|
955
|
+
{ path: ["state", TEXT_STEP, "data", "paymentMethod", "storedPaymentMethodId"], when: "present" },
|
|
956
|
+
{ path: ["state", TEXT_STEP, "data", "paymentMethod", "recurringDetailReference"], when: "present" }
|
|
957
|
+
],
|
|
958
|
+
amount: null,
|
|
959
|
+
// Read Oct 23, 2026 (the observe run above). Every cart action on the checkout page answers the
|
|
960
|
+
// whole cart, camelCase JSON at the root (addToCart wraps it in `cart`); summary.amountDue is what
|
|
961
|
+
// the payment step shows as "Amount due" and what is left for the card, in cents (6538 in the run,
|
|
962
|
+
// the run's total). The cart is the session's, so an answer is tied to the card request by its
|
|
963
|
+
// page. A gift card or another tender lowers the amount due below the order's gross total and is
|
|
964
|
+
// refused until a HAR shows which one the charge follows.
|
|
965
|
+
amountSource: {
|
|
966
|
+
id: "scheels_cart_amount_due",
|
|
967
|
+
origin: "endpoint",
|
|
968
|
+
format: "json",
|
|
969
|
+
responses: [
|
|
970
|
+
{ method: "POST", path: "/frontastic/action/cart/updateCart", query: {} },
|
|
971
|
+
{ method: "POST", path: "/frontastic/action/cart/setCheckoutDefaults", query: {} },
|
|
972
|
+
{ method: "POST", path: "/frontastic/action/cart/clean", query: {} },
|
|
973
|
+
{ method: "POST", path: "/frontastic/action/cart/addToCart", query: {}, at: ["cart"] },
|
|
974
|
+
{ method: "POST", path: "/frontastic/action/route/addRouteProtection", query: {} },
|
|
975
|
+
{ method: "POST", path: "/frontastic/action/route/removeRouteProtection", query: {} }
|
|
976
|
+
],
|
|
977
|
+
total: { path: ["summary", "amountDue", "centAmount"], format: "number" },
|
|
978
|
+
unit: "minor",
|
|
979
|
+
currency: "USD",
|
|
980
|
+
currencyPath: ["summary", "amountDue", "currencyCode"],
|
|
981
|
+
bind: [{ samePage: true }],
|
|
982
|
+
requires: [{ path: ["summary", "amountDue", "centAmount"], equalsPath: ["summary", "grossTotal", "centAmount"] }],
|
|
983
|
+
refuse: [
|
|
984
|
+
{ path: ["summary", "giftCardTotal", "centAmount"], when: "nonzero" },
|
|
985
|
+
{ path: ["summary", "tenderTotal", "centAmount"], when: "nonzero" }
|
|
986
|
+
],
|
|
987
|
+
// Cart changes whose answers were not seen in the run: any of them after the chosen answer makes it stale.
|
|
988
|
+
voidedBy: [
|
|
989
|
+
{ method: "POST", path: "/frontastic/action/cart/redeemDiscountCode", query: {} },
|
|
990
|
+
{ method: "POST", path: "/frontastic/action/cart/removeDiscountCode", query: {} },
|
|
991
|
+
{ method: "POST", path: "/frontastic/action/cart/updateItem", query: {} },
|
|
992
|
+
{ method: "POST", path: "/frontastic/action/cart/removeItem", query: {} },
|
|
993
|
+
{ method: "POST", path: "/frontastic/action/cart/setShippingMethod", query: {} },
|
|
994
|
+
{ method: "POST", path: "/frontastic/action/sameDay/setTip", query: {} }
|
|
995
|
+
],
|
|
996
|
+
maxAgeMs: 9e5
|
|
997
|
+
},
|
|
998
|
+
facts: [
|
|
999
|
+
{ path: ["payment", "bin"], fact: "bin6" },
|
|
1000
|
+
{ path: ["payment", "lastFour"], fact: "last4" }
|
|
1001
|
+
],
|
|
1002
|
+
adyen: {
|
|
1003
|
+
environment: "live-us",
|
|
1004
|
+
clientKey: "live_7NQ5GGRX7ZD3PJCWBGV6WELTMMA3GBOO",
|
|
1005
|
+
publicKeySha256: "9d6aa672c539f6e76c082beed1fcbe089af526b87c9ea6878ab3671355089065"
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
// Read Oct 23, 2026, from www.buff.com's shipped US checkout (Magento on Hyvä Checkout with
|
|
1010
|
+
// Magewire, Adyen's Hyvä module, adyen-web 5.5.3) and a card-free observe run on its guest checkout
|
|
1011
|
+
// (every non-GET aborted once the card form was reached, the placeholder typed into the secured
|
|
1012
|
+
// fields, nothing pressed). The card renderer keeps adyen-web's state.data from onChange and the
|
|
1013
|
+
// brand code onBrand maps (amex AE, visa VI, mc MC). Place Order calls the adyen_cc component's
|
|
1014
|
+
// placeOrder({stateData, ccType, publicHash: null, extension_attributes}) through Magewire, which
|
|
1015
|
+
// posts JSON.stringify({fingerprint, serverMemo, updates: [{type: 'callMethod', payload: {id,
|
|
1016
|
+
// method: 'placeOrder', params: [that object]}}]}) to
|
|
1017
|
+
// /us/magewire/post/livewire/message/checkout.payment.methods.adyen_cc with content-type
|
|
1018
|
+
// application/json and X-CSRF-TOKEN. The server charges the quote there. ccType describes the
|
|
1019
|
+
// dummy: the approved card's code is written there. The body's own serverMemo, which Magewire signs
|
|
1020
|
+
// with its checksum, carries the component's paymentResponse as JSON text, and its
|
|
1021
|
+
// paymentMethodsExtraDetails.scheme.configuration.amount is the quote's grand total in cents: it
|
|
1022
|
+
// followed the run's total from 8600 to 9342 once shipping and tax were set. A saved card sends a
|
|
1023
|
+
// publicHash and enableStoreDetails is off for a guest. Public key read from
|
|
1024
|
+
// checkoutshopper-live.adyen.com on 2026-10-23 (UTC). Not yet seen on the wire: the card request
|
|
1025
|
+
// itself, which leaves only on Place Order.
|
|
1026
|
+
id: "buff_magewire_adyen_cc",
|
|
1027
|
+
merchant: "Buff",
|
|
1028
|
+
status: "observe",
|
|
1029
|
+
origins: [{ checkout: "https://www.buff.com", endpoint: "https://www.buff.com" }],
|
|
1030
|
+
method: "POST",
|
|
1031
|
+
path: "/us/magewire/post/livewire/message/checkout.payment.methods.adyen_cc",
|
|
1032
|
+
query: {},
|
|
1033
|
+
requires: [
|
|
1034
|
+
{ path: ["fingerprint", "name"], equals: "checkout.payment.methods.adyen_cc" },
|
|
1035
|
+
{ path: ["updates", 0, "type"], equals: "callMethod" },
|
|
1036
|
+
{ path: ["updates", 0, "payload", "method"], equals: "placeOrder" }
|
|
1037
|
+
],
|
|
1038
|
+
holder: ["updates", 0, "payload", "params", 0, "stateData", "paymentMethod"],
|
|
1039
|
+
fields: {
|
|
1040
|
+
encryptedCardNumber: "encryptedCardNumber",
|
|
1041
|
+
encryptedExpiryMonth: "encryptedExpiryMonth",
|
|
1042
|
+
encryptedExpiryYear: "encryptedExpiryYear",
|
|
1043
|
+
encryptedSecurityCode: "encryptedSecurityCode"
|
|
1044
|
+
},
|
|
1045
|
+
// adyen-web 5.x stamps the dummy's brand into state.data.
|
|
1046
|
+
remove: ["brand"],
|
|
1047
|
+
refuse: [
|
|
1048
|
+
// One call per message: a second update or a second argument is not the reviewed request.
|
|
1049
|
+
{ path: ["updates", 1], when: "present" },
|
|
1050
|
+
{ path: ["updates", 0, "payload", "params", 1], when: "present" },
|
|
1051
|
+
// A saved card's public hash, or a card kept for later.
|
|
1052
|
+
{ path: ["updates", 0, "payload", "params", 0, "publicHash"], when: "true" },
|
|
1053
|
+
{ path: ["updates", 0, "payload", "params", 0, "stateData", "storePaymentMethod"], when: "true" },
|
|
1054
|
+
{ path: ["updates", 0, "payload", "params", 0, "stateData", "paymentMethod", "storedPaymentMethodId"], when: "present" },
|
|
1055
|
+
{ path: ["updates", 0, "payload", "params", 0, "stateData", "paymentMethod", "recurringDetailReference"], when: "present" }
|
|
1056
|
+
],
|
|
1057
|
+
amount: ["serverMemo", "data", "paymentResponse", TEXT_STEP, "paymentMethodsExtraDetails", "scheme", "configuration", "amount"],
|
|
1058
|
+
facts: [
|
|
1059
|
+
// The renderer's own map (componentHandler.getCcCodeByAltCode); Buff's card list names these three.
|
|
1060
|
+
{ path: ["updates", 0, "payload", "params", 0, "ccType"], fact: "brand", map: { visa: "VI", mastercard: "MC", amex: "AE" } }
|
|
1061
|
+
],
|
|
1062
|
+
adyen: {
|
|
1063
|
+
environment: "live",
|
|
1064
|
+
clientKey: "live_5NIC7BWSNFDX3GBSKL5XCZ4B4YCIWLKP",
|
|
1065
|
+
publicKeySha256: "3f7e1740684cb4964aa8bb7185b9718564fc0957cfc83e096f7bf016d6a7d001"
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
// Read Oct 23, 2026, from www.plein.com's shipped US checkout (Salesforce Commerce Cloud SiteGenesis with Adyen's
|
|
1070
|
+
// int_adyen cartridge, adyen-web 4.9.0) and the press lane's stored copy of the card request the page sent with the
|
|
1071
|
+
// placeholder typed (aborted unsent by the checkout SDK's hold; values sanitized, field names and shape kept). The
|
|
1072
|
+
// card component's onSubmit writes JSON.stringify(state.data) into the hidden field dwfrm_adyPaydata_adyenStateData,
|
|
1073
|
+
// and CONTINUE TO PLACE ORDER submits the billing form: a top-level form post (application/x-www-form-urlencoded) to
|
|
1074
|
+
// /us/billing/. So the four JWEs sit in adyen-web's state, inside that field's JSON text, under paymentMethod
|
|
1075
|
+
// (adyen-web 4.9 writes no brand there; one a later version writes is dropped). Beside the state the page writes
|
|
1076
|
+
// onBrand's code into dwfrm_billing_paymentMethods_creditCard_type and twelve '*' with onFieldValid's last four into
|
|
1077
|
+
// dwfrm_billing_paymentMethods_creditCard_number, both describing the dummy: the approved card's are written there.
|
|
1078
|
+
// The form also carries its csrf_token and its submit button's name twice (dwfrm_billing_save); a repeated field
|
|
1079
|
+
// never reaches a rule. enableStoreDetails is on for a signed-in shopper only. The body names no amount, and the
|
|
1080
|
+
// page's total sits only in its analytics block (tc_vars.order_amount_ati_with_sf), which no reviewed rule reads,
|
|
1081
|
+
// so amountPending holds the profile in observe. Public key read from checkoutshopper-live-us.adyen.com on
|
|
1082
|
+
// 2026-10-23 (UTC).
|
|
1083
|
+
id: "plein_sfcc_billing_form",
|
|
1084
|
+
merchant: "Philipp Plein",
|
|
1085
|
+
status: "observe",
|
|
1086
|
+
origins: [{ checkout: "https://www.plein.com", endpoint: "https://www.plein.com" }],
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
path: "/us/billing/",
|
|
1089
|
+
query: {},
|
|
1090
|
+
encoding: "form",
|
|
1091
|
+
requires: [
|
|
1092
|
+
{ path: ["dwfrm_billing_paymentMethods_selectedPaymentMethodID"], equals: "AdyenComponent" },
|
|
1093
|
+
{ path: ["brandCode"], equals: "scheme" }
|
|
1094
|
+
],
|
|
1095
|
+
holder: ["dwfrm_adyPaydata_adyenStateData", TEXT_STEP, "paymentMethod"],
|
|
1096
|
+
fields: {
|
|
1097
|
+
encryptedCardNumber: "encryptedCardNumber",
|
|
1098
|
+
encryptedExpiryMonth: "encryptedExpiryMonth",
|
|
1099
|
+
encryptedExpiryYear: "encryptedExpiryYear",
|
|
1100
|
+
encryptedSecurityCode: "encryptedSecurityCode"
|
|
1101
|
+
},
|
|
1102
|
+
remove: ["brand"],
|
|
1103
|
+
refuse: [
|
|
1104
|
+
{ path: ["dwfrm_adyPaydata_adyenStateData", TEXT_STEP, "storePaymentMethod"], when: "true" },
|
|
1105
|
+
{ path: ["dwfrm_adyPaydata_adyenStateData", TEXT_STEP, "paymentMethod", "storedPaymentMethodId"], when: "present" },
|
|
1106
|
+
{ path: ["dwfrm_adyPaydata_adyenStateData", TEXT_STEP, "paymentMethod", "recurringDetailReference"], when: "present" }
|
|
1107
|
+
],
|
|
1108
|
+
amount: null,
|
|
1109
|
+
amountPending: "the page names its total only in its analytics block (tc_vars.order_amount_ati_with_sf), which no reviewed rule reads",
|
|
1110
|
+
facts: [
|
|
1111
|
+
// onBrand's adyen-web codes for the networks a US checkout takes.
|
|
1112
|
+
{ path: ["dwfrm_billing_paymentMethods_creditCard_type"], fact: "brand", map: { visa: "visa", mastercard: "mc", amex: "amex", discover: "discover" } },
|
|
1113
|
+
{ path: ["dwfrm_billing_paymentMethods_creditCard_number"], fact: "masked_last4" }
|
|
1114
|
+
],
|
|
1115
|
+
adyen: {
|
|
1116
|
+
environment: "live-us",
|
|
1117
|
+
clientKey: "live_P7RRXHAHEVFTLPS3XUD3ML7ZWUEZ6BXN",
|
|
1118
|
+
publicKeySha256: "3e0d4366563b714feb233d12ce98adcef3b606381596e097ca3a8123b9a91d67"
|
|
1119
|
+
}
|
|
736
1120
|
}
|
|
737
1121
|
]);
|
|
738
1122
|
function adyenKeyHost(environment) {
|
|
@@ -762,7 +1146,7 @@ var CARD_BRANDS = /* @__PURE__ */ deepFreeze([
|
|
|
762
1146
|
]);
|
|
763
1147
|
var BRAND_CODE = /^[A-Za-z0-9][A-Za-z0-9 ._-]{0,31}$/;
|
|
764
1148
|
var MAX_FACTS = 8;
|
|
765
|
-
function factRefusal(entry, i, holder,
|
|
1149
|
+
function factRefusal(entry, i, holder, reviewed) {
|
|
766
1150
|
const label = `fact ${i}`;
|
|
767
1151
|
if (!isPlainObject(entry))
|
|
768
1152
|
return `${label} is not an object`;
|
|
@@ -777,9 +1161,9 @@ function factRefusal(entry, i, holder, besideHolder, reviewed) {
|
|
|
777
1161
|
return `${label}'s path is not a JSON path ending in a key`;
|
|
778
1162
|
const key = path[path.length - 1];
|
|
779
1163
|
const parent = path.slice(0, -1);
|
|
780
|
-
const inHolder = sameSteps(parent, holder);
|
|
781
|
-
if (!inHolder &&
|
|
782
|
-
return `${label}'s path ${describePath(path)}
|
|
1164
|
+
const inHolder = sameSteps(parent, holder) && !holder.some(isTextStep);
|
|
1165
|
+
if (!inHolder && pathsOverlap(path, holder)) {
|
|
1166
|
+
return `${label}'s path ${describePath(path)} reaches the card object or what holds it`;
|
|
783
1167
|
}
|
|
784
1168
|
if (inHolder && reviewed.written.includes(key))
|
|
785
1169
|
return `${label} writes over an encrypted field or a removed key`;
|
|
@@ -805,16 +1189,15 @@ function merchantProfileFactsRefusal(profile) {
|
|
|
805
1189
|
if (!Array.isArray(facts) || facts.length > MAX_FACTS)
|
|
806
1190
|
return `facts is not a list of at most ${MAX_FACTS}`;
|
|
807
1191
|
const holder = profile.holder;
|
|
808
|
-
if (!
|
|
1192
|
+
if (!isProfilePath(holder))
|
|
809
1193
|
return "the holder is not a JSON path";
|
|
810
|
-
const besideHolder = holder.length > 0 && typeof holder[holder.length - 1] === "string" ? holder.slice(0, -1) : null;
|
|
811
1194
|
const list = (value) => Array.isArray(value) ? value : [];
|
|
812
1195
|
const reviewed = {
|
|
813
1196
|
written: [...isPlainObject(profile.fields) ? Object.values(profile.fields) : [], ...list(profile.remove)],
|
|
814
1197
|
read: [...list(profile.requires), ...list(profile.refuse)].map((rule) => rule?.path).concat(merchantBodyAmountPaths(profile), merchantAmountCardPaths(profile)).filter(Array.isArray)
|
|
815
1198
|
};
|
|
816
1199
|
for (const [i, entry] of facts.entries()) {
|
|
817
|
-
const refusal = factRefusal(entry, i, holder,
|
|
1200
|
+
const refusal = factRefusal(entry, i, holder, reviewed);
|
|
818
1201
|
if (refusal)
|
|
819
1202
|
return refusal;
|
|
820
1203
|
const earlier = facts.slice(0, i).find((other) => isPlainObject(other) && Array.isArray(other.path) && pathsOverlap(other.path, entry.path));
|
|
@@ -826,23 +1209,74 @@ function merchantProfileFactsRefusal(profile) {
|
|
|
826
1209
|
function merchantProfileRecognizerById(id) {
|
|
827
1210
|
return typeof id === "string" ? MERCHANT_PROFILE_RECOGNIZERS.find((entry) => entry.id === id) ?? null : null;
|
|
828
1211
|
}
|
|
829
|
-
function
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
1212
|
+
function textRefusal(p, parsed) {
|
|
1213
|
+
const amount = p.amount;
|
|
1214
|
+
const amountPaths = Array.isArray(amount) ? [amount] : isPlainObject(amount) ? [amount.value, amount.currency] : [];
|
|
1215
|
+
const paths = [p.holder, ...p.requires.map((rule) => rule.path), ...p.refuse.map((rule) => rule.path), ...amountPaths].filter(Array.isArray);
|
|
1216
|
+
for (const path of paths) {
|
|
1217
|
+
for (const [k, step] of path.entries()) {
|
|
1218
|
+
if (!isTextStep(step))
|
|
1219
|
+
continue;
|
|
1220
|
+
const before = path.slice(0, k);
|
|
1221
|
+
if (valuesIgnoringCase(parsed, before).some((value) => valuesIgnoringCase(value, [TEXT_STEP]).length === 0)) {
|
|
1222
|
+
return `the paused body's ${describePath(before)} is not JSON text written the way JSON.stringify or PHP's json_encode writes it, so ${p.merchant}'s rules cannot read it`;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
function formBodyView(body) {
|
|
1229
|
+
if (typeof body !== "string" || body === "")
|
|
1230
|
+
return void 0;
|
|
1231
|
+
let params;
|
|
1232
|
+
try {
|
|
1233
|
+
params = new URLSearchParams(body);
|
|
1234
|
+
}
|
|
1235
|
+
catch {
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
if (params.toString() !== body)
|
|
1239
|
+
return void 0;
|
|
1240
|
+
const view = /* @__PURE__ */ Object.create(null);
|
|
1241
|
+
for (const [name, value] of params) {
|
|
1242
|
+
if (!Object.prototype.hasOwnProperty.call(view, name))
|
|
1243
|
+
view[name] = value;
|
|
1244
|
+
else
|
|
1245
|
+
view[name] = Array.isArray(view[name]) ? [...view[name], value] : [view[name], value];
|
|
1246
|
+
}
|
|
1247
|
+
return view;
|
|
1248
|
+
}
|
|
1249
|
+
function readProfileBody(p, body) {
|
|
1250
|
+
if (p.encoding === "form") {
|
|
1251
|
+
const view = formBodyView(body);
|
|
1252
|
+
return view === void 0 ? { refusal: "the paused body is not a form written the way a browser writes it, so its rules cannot be read" } : { parsed: view };
|
|
1253
|
+
}
|
|
833
1254
|
let parsed;
|
|
834
1255
|
try {
|
|
835
1256
|
parsed = typeof body === "string" ? JSON.parse(body) : void 0;
|
|
836
1257
|
}
|
|
837
1258
|
catch {
|
|
838
|
-
return "the paused body is not JSON";
|
|
1259
|
+
return { refusal: "the paused body is not JSON" };
|
|
839
1260
|
}
|
|
840
1261
|
if (!isPlainObject(parsed))
|
|
841
|
-
return "the paused body is not a JSON object";
|
|
1262
|
+
return { refusal: "the paused body is not a JSON object" };
|
|
842
1263
|
if (!roundTrips(parsed, body))
|
|
843
|
-
return "the paused body does not round-trip through JSON, so its rules cannot be read";
|
|
1264
|
+
return { refusal: "the paused body does not round-trip through JSON, so its rules cannot be read" };
|
|
1265
|
+
return { parsed };
|
|
1266
|
+
}
|
|
1267
|
+
function merchantBodyRefusal(profile, body) {
|
|
1268
|
+
const p = merchantProfileRecognizerById(typeof profile === "string" ? profile : profile?.id);
|
|
1269
|
+
if (!p)
|
|
1270
|
+
throw new Error("Refusing to pay: unknown merchant profile.");
|
|
1271
|
+
const read = readProfileBody(p, body);
|
|
1272
|
+
if (read.refusal)
|
|
1273
|
+
return read.refusal;
|
|
1274
|
+
const parsed = read.parsed;
|
|
1275
|
+
const unreadText = textRefusal(p, parsed);
|
|
1276
|
+
if (unreadText)
|
|
1277
|
+
return unreadText;
|
|
844
1278
|
for (const rule of p.requires) {
|
|
845
|
-
const at =
|
|
1279
|
+
const at = profileAt(parsed, rule.path);
|
|
846
1280
|
if (!at.found || at.value !== rule.equals || valuesIgnoringCase(parsed, rule.path).some((value) => value !== rule.equals)) {
|
|
847
1281
|
return `the paused body is not ${p.merchant}'s card request: ${describePath(rule.path)} is not ${JSON.stringify(rule.equals)}`;
|
|
848
1282
|
}
|