@agent-cards/sdk 0.23.28 → 0.24.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/PREFLIGHT.md +7 -1
  3. package/README.md +197 -102
  4. package/dist/aci.d.ts +52 -22
  5. package/dist/aci.js +79 -15
  6. package/dist/adyen-merchant-hosted.generated.d.ts +2 -2
  7. package/dist/adyen-merchant-hosted.generated.js +586 -55
  8. package/dist/agent-amount.generated.js +1 -1
  9. package/dist/agentcard.d.ts +4 -5
  10. package/dist/agentcard.js +4 -5
  11. package/dist/amadeus.d.ts +9 -3
  12. package/dist/amadeus.js +33 -4
  13. package/dist/api-module.d.ts +1 -7
  14. package/dist/api-module.js +3 -5
  15. package/dist/{voice-retry.js → api-retry.js} +1 -1
  16. package/dist/api-types.d.ts +53 -0
  17. package/dist/{voice-types.js → api-types.js} +7 -2
  18. package/dist/approvals.d.ts +166 -0
  19. package/dist/approvals.js +213 -0
  20. package/dist/aurus.d.ts +33 -0
  21. package/dist/aurus.js +75 -0
  22. package/dist/builtin-registry.generated.js +1 -1
  23. package/dist/buy.d.ts +20 -101
  24. package/dist/buy.js +19 -74
  25. package/dist/capabilities.generated.js +1 -1
  26. package/dist/card-fields.generated.js +2864 -249
  27. package/dist/cardlink-vpos.d.ts +43 -0
  28. package/dist/cardlink-vpos.js +90 -0
  29. package/dist/cdp.d.ts +3 -13
  30. package/dist/cdp.js +244 -175
  31. package/dist/checkout-com.generated.js +5 -1
  32. package/dist/client.d.ts +170 -22
  33. package/dist/client.js +281 -40
  34. package/dist/creditguard.generated.d.ts +4 -4
  35. package/dist/cybersource-flex.generated.d.ts +39 -1
  36. package/dist/cybersource-flex.generated.js +100 -11
  37. package/dist/datatrans.generated.js +22 -1
  38. package/dist/eghl.d.ts +40 -0
  39. package/dist/eghl.js +72 -0
  40. package/dist/elavon-converge.generated.js +5 -3
  41. package/dist/gmo.generated.js +7 -2
  42. package/dist/hold.d.ts +8 -0
  43. package/dist/hold.js +43 -2
  44. package/dist/hosted-entry.js +4 -0
  45. package/dist/index.d.ts +9 -15
  46. package/dist/index.js +6 -10
  47. package/dist/lifecycle.d.ts +9 -61
  48. package/dist/lifecycle.js +24 -10
  49. package/dist/merchant-card-endpoints.generated.d.ts +6 -1
  50. package/dist/merchant-card-endpoints.generated.js +344 -19
  51. package/dist/merchant-hosted.d.ts +8 -1
  52. package/dist/nike-order.d.ts +51 -0
  53. package/dist/nike-order.js +81 -0
  54. package/dist/oceanpayment.d.ts +38 -0
  55. package/dist/oceanpayment.js +70 -0
  56. package/dist/paddle-tokenex.generated.js +111 -11
  57. package/dist/payplug.d.ts +41 -0
  58. package/dist/payplug.js +71 -0
  59. package/dist/payrails.generated.js +33 -8
  60. package/dist/pre-claim.d.ts +11 -0
  61. package/dist/pre-claim.js +130 -7
  62. package/dist/preflight-capabilities.generated.d.ts +0 -4
  63. package/dist/preflight-capabilities.generated.js +0 -5
  64. package/dist/preflight-catalog.json +200 -6
  65. package/dist/preflight-playwright.d.ts +2 -2
  66. package/dist/preflight-playwright.js +63 -14
  67. package/dist/preflight-schemas.json +18 -0
  68. package/dist/preflight.generated.d.ts +5 -4
  69. package/dist/preflight.generated.js +63 -3
  70. package/dist/preparation.d.ts +7 -0
  71. package/dist/preparation.js +21 -0
  72. package/dist/prepared-processor.d.ts +18 -5
  73. package/dist/prepared-processor.js +48 -8
  74. package/dist/primer.generated.js +2889 -268
  75. package/dist/processout.generated.d.ts +61 -0
  76. package/dist/processout.generated.js +577 -0
  77. package/dist/requests.js +1 -1
  78. package/dist/shiji-sts.generated.js +111 -11
  79. package/dist/stripe-checkout.generated.js +2761 -319
  80. package/dist/substitutions.generated.js +553 -26
  81. package/dist/tap-payments.generated.js +111 -11
  82. package/dist/twoc2p.generated.js +111 -11
  83. package/dist/url-masks/bluesnap.d.ts +2 -0
  84. package/dist/url-masks/bluesnap.js +15 -0
  85. package/dist/url-masks/oceanpayment.d.ts +2 -0
  86. package/dist/url-masks/oceanpayment.js +13 -0
  87. package/dist/url-masks/payplug.d.ts +2 -0
  88. package/dist/url-masks/payplug.js +13 -0
  89. package/dist/url-masks/vtex_payments.d.ts +2 -0
  90. package/dist/url-masks/vtex_payments.js +12 -0
  91. package/dist/url-masks.generated.d.ts +1 -1
  92. package/dist/url-masks.generated.js +6 -2
  93. package/dist/verifone.generated.js +42 -2
  94. package/dist/vtex-payments.generated.d.ts +127 -0
  95. package/dist/vtex-payments.generated.js +544 -0
  96. package/dist/webhooks.d.ts +2 -2
  97. package/dist/webhooks.js +2 -2
  98. package/examples/preflight/kernel-native/inventory.json +1 -1
  99. package/package.json +6 -8
  100. package/dist/languages.d.ts +0 -23
  101. package/dist/languages.js +0 -76
  102. package/dist/voice-pkce.d.ts +0 -49
  103. package/dist/voice-pkce.js +0 -147
  104. package/dist/voice-stream.generated.d.ts +0 -118
  105. package/dist/voice-stream.generated.js +0 -170
  106. package/dist/voice-types.d.ts +0 -735
  107. package/dist/voice.d.ts +0 -373
  108. package/dist/voice.js +0 -815
  109. package/readme.test.mjs +0 -53
  110. /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: a1b7d280655947da8bb1d465d17c3436504ee350dd0be7f345d524eea0f2f374
3
+ // artifact-sha256: f402656dc29200331356e78d920979d00a0768d87ea6c630e17c211455679d4b
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 === b[i]);
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] !== b[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 (typeof step === "number") {
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 (!isJsonPath(path))
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 = jsonAt(parsed, path);
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 = jsonAt(out, path);
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
- for (const key of remove)
246
- if (Object.prototype.hasOwnProperty.call(written.value, key))
247
- delete written.value[key];
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,382 @@ 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
+ }
1120
+ },
1121
+ {
1122
+ // Read Nov 3, 2026, from www.lego.com's shipped US checkout (a Next.js app under /checkout, Apollo GraphQL) and a
1123
+ // card-free run on its guest checkout up to the card form (test contact details, no card typed, nothing pressed;
1124
+ // every non-GET to adyen.com aborted). The checkout's cart answer (CheckoutV2State, paymentMethodsV3.primary) names
1125
+ // CREDIT_CARD_ADYEN with environment 'live' and this client key, and the card form is adyen-web 6's secured fields
1126
+ // (securedFields 6.0.0 on checkoutshopper-live.adyen.com). Place order's handler (chunk 487, the CreditCard submit)
1127
+ // copies encryptedCardNumber, encryptedExpiryMonth, encryptedExpiryYear, encryptedSecurityCode, type and holderName
1128
+ // out of adyen-web's state.data.paymentMethod, with no brand, and sends the Apollo mutation AddAdyenPayment with
1129
+ // variables {input: {paymentReference: <the cart's orderNumber>, telephoneNumber, browserInfo, origin, paymentMethod,
1130
+ // returnUrl, storePaymentMethod, tracking}}. Apollo posts {operationName, variables, query} as JSON to the gateway
1131
+ // (www.lego.com/checkout/api/graphql), whose link adds the operation name to the path: every checkout operation in
1132
+ // the run went to /checkout/api/graphql/<operationName>. So the card request is Lego's own server, Adyen's Advanced
1133
+ // flow. A saved card goes as AddAdyenSavedCardPaymentV2, a gift card as AddGiftCardPaymentV2Mutation, each on its
1134
+ // own path. The body names no amount. Public key read from checkoutshopper-live.adyen.com on 2026-11-03 (UTC). Not
1135
+ // yet seen on the wire: the card request itself, which leaves only on Place order.
1136
+ // Enabled on Nov 4, 2026 under D1, so the Vault's bundled copy of this table carries it at the next Vault release. No
1137
+ // company is admitted yet: Agentcard's proof company joins through a flip in backend merchant-profile-orgs.ts after that
1138
+ // release. Felipe's approval of this change is his D1 decision for LEGO, and Karen's its PCI review.
1139
+ // No afterPayment reader yet: nobody has seen what LEGO answers after a placed order, so the charge is not compared with
1140
+ // the approved amount after payment and merchantChargeVerdict has nothing to read (Greptile on this change). The amount
1141
+ // is checked before payment (amountSource below). Until a reviewed afterPayment lands, built from the response the first
1142
+ // live run captures, Agentcard's proof company makes no purchase LEGO's Adyen approves: its runs use a card Adyen declines.
1143
+ id: "lego_checkout_add_adyen_payment",
1144
+ merchant: "LEGO",
1145
+ status: "enabled",
1146
+ origins: [{ checkout: "https://www.lego.com", endpoint: "https://www.lego.com" }],
1147
+ method: "POST",
1148
+ path: "/checkout/api/graphql/AddAdyenPayment",
1149
+ query: {},
1150
+ requires: [
1151
+ { path: ["operationName"], equals: "AddAdyenPayment" },
1152
+ { path: ["variables", "input", "paymentMethod", "type"], equals: "scheme" }
1153
+ ],
1154
+ holder: ["variables", "input", "paymentMethod"],
1155
+ fields: {
1156
+ encryptedCardNumber: "encryptedCardNumber",
1157
+ encryptedExpiryMonth: "encryptedExpiryMonth",
1158
+ encryptedExpiryYear: "encryptedExpiryYear",
1159
+ encryptedSecurityCode: "encryptedSecurityCode"
1160
+ },
1161
+ // The page copies no brand out of adyen-web's state.
1162
+ remove: [],
1163
+ refuse: [
1164
+ { path: ["variables", "input", "storePaymentMethod"], when: "true" },
1165
+ { path: ["variables", "input", "paymentMethod", "storedPaymentMethodId"], when: "present" },
1166
+ { path: ["variables", "input", "paymentMethod", "recurringDetailReference"], when: "present" }
1167
+ ],
1168
+ amount: null,
1169
+ // Read Nov 3, 2026 (the run above). The checkout's cart answers carry the whole cart: CheckoutV2State at
1170
+ // data.me.cart, the address step at data.updateCartAddresses and the shipping step at data.setCartShippingMethod.
1171
+ // remainingTotalToPay is what is left for the card, in cents (1191 in the run, the page's Order Total $11.91), and
1172
+ // orderNumber is the paymentReference the card request sends, which ties each answer to it. A gift card already
1173
+ // applied shows in payments and lowers what is left below the order's total, so a cart with one is refused. Cart
1174
+ // changes whose answers were not seen in the run make the chosen answer stale.
1175
+ amountSource: {
1176
+ id: "lego_cart_remaining_total",
1177
+ origin: "endpoint",
1178
+ format: "json",
1179
+ responses: [
1180
+ { method: "POST", path: "/checkout/api/graphql/CheckoutV2State", query: {}, at: ["data", "me", "cart"] },
1181
+ { method: "POST", path: "/checkout/api/graphql/SetAddressMutation", query: {}, at: ["data", "updateCartAddresses"] },
1182
+ { method: "POST", path: "/checkout/api/graphql/SetShippingMethodV2Mutation", query: {}, at: ["data", "setCartShippingMethod"] }
1183
+ ],
1184
+ total: { path: ["remainingTotalToPay", "centAmount"], format: "number" },
1185
+ unit: "minor",
1186
+ currency: "USD",
1187
+ currencyPath: ["remainingTotalToPay", "currencyCode"],
1188
+ bind: [{ source: ["orderNumber"], card: ["variables", "input", "paymentReference"] }],
1189
+ refuse: [
1190
+ { path: ["payments"], when: "nonEmpty" },
1191
+ // A GraphQL error result is not a total, even beside partial data.
1192
+ { path: ["errors"], when: "present", root: true }
1193
+ ],
1194
+ voidedBy: [
1195
+ { method: "POST", path: "/checkout/api/graphql/AddGiftCardPaymentV2Mutation", query: {} },
1196
+ { method: "POST", path: "/checkout/api/graphql/AddGiftCardOnlyPayment", query: {} },
1197
+ { method: "POST", path: "/checkout/api/graphql/RemoveGiftCardPaymentMutations", query: {} },
1198
+ { method: "POST", path: "/checkout/api/graphql/AddVipLineItemCheckout", query: {} },
1199
+ { method: "POST", path: "/checkout/api/graphql/EditVipLineItemCheckout", query: {} },
1200
+ { method: "POST", path: "/checkout/api/graphql/RemoveVipPointsLineItemCheckout", query: {} },
1201
+ { method: "POST", path: "/checkout/api/graphql/SetShippingMethodOnlyMutation", query: {} },
1202
+ { method: "POST", path: "/checkout/api/graphql/SetAddressMutationWithoutEmail", query: {} },
1203
+ { method: "POST", path: "/checkout/api/graphql/SetAddressesMutationV2", query: {} },
1204
+ { method: "POST", path: "/checkout/api/graphql/saveCollectionPointMutation", query: {} }
1205
+ ],
1206
+ maxAgeMs: 9e5
1207
+ },
1208
+ adyen: {
1209
+ environment: "live",
1210
+ clientKey: "live_P34BZ2Q6ANHNHAUEKOFOX3IMHQHZ75ES",
1211
+ publicKeySha256: "5659f43faa157b82b45844fdf35382a8b83b7bb24662b5e0fbcca15fd120bcc2"
1212
+ }
736
1213
  }
737
1214
  ]);
738
1215
  function adyenKeyHost(environment) {
@@ -762,7 +1239,7 @@ var CARD_BRANDS = /* @__PURE__ */ deepFreeze([
762
1239
  ]);
763
1240
  var BRAND_CODE = /^[A-Za-z0-9][A-Za-z0-9 ._-]{0,31}$/;
764
1241
  var MAX_FACTS = 8;
765
- function factRefusal(entry, i, holder, besideHolder, reviewed) {
1242
+ function factRefusal(entry, i, holder, reviewed) {
766
1243
  const label = `fact ${i}`;
767
1244
  if (!isPlainObject(entry))
768
1245
  return `${label} is not an object`;
@@ -777,9 +1254,9 @@ function factRefusal(entry, i, holder, besideHolder, reviewed) {
777
1254
  return `${label}'s path is not a JSON path ending in a key`;
778
1255
  const key = path[path.length - 1];
779
1256
  const parent = path.slice(0, -1);
780
- const inHolder = sameSteps(parent, holder);
781
- if (!inHolder && !(besideHolder && sameSteps(parent, besideHolder) && key !== holder[holder.length - 1])) {
782
- return `${label}'s path ${describePath(path)} is neither in the holder nor beside it`;
1257
+ const inHolder = sameSteps(parent, holder) && !holder.some(isTextStep);
1258
+ if (!inHolder && pathsOverlap(path, holder)) {
1259
+ return `${label}'s path ${describePath(path)} reaches the card object or what holds it`;
783
1260
  }
784
1261
  if (inHolder && reviewed.written.includes(key))
785
1262
  return `${label} writes over an encrypted field or a removed key`;
@@ -805,16 +1282,15 @@ function merchantProfileFactsRefusal(profile) {
805
1282
  if (!Array.isArray(facts) || facts.length > MAX_FACTS)
806
1283
  return `facts is not a list of at most ${MAX_FACTS}`;
807
1284
  const holder = profile.holder;
808
- if (!isJsonPath(holder))
1285
+ if (!isProfilePath(holder))
809
1286
  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
1287
  const list = (value) => Array.isArray(value) ? value : [];
812
1288
  const reviewed = {
813
1289
  written: [...isPlainObject(profile.fields) ? Object.values(profile.fields) : [], ...list(profile.remove)],
814
1290
  read: [...list(profile.requires), ...list(profile.refuse)].map((rule) => rule?.path).concat(merchantBodyAmountPaths(profile), merchantAmountCardPaths(profile)).filter(Array.isArray)
815
1291
  };
816
1292
  for (const [i, entry] of facts.entries()) {
817
- const refusal = factRefusal(entry, i, holder, besideHolder, reviewed);
1293
+ const refusal = factRefusal(entry, i, holder, reviewed);
818
1294
  if (refusal)
819
1295
  return refusal;
820
1296
  const earlier = facts.slice(0, i).find((other) => isPlainObject(other) && Array.isArray(other.path) && pathsOverlap(other.path, entry.path));
@@ -826,23 +1302,74 @@ function merchantProfileFactsRefusal(profile) {
826
1302
  function merchantProfileRecognizerById(id) {
827
1303
  return typeof id === "string" ? MERCHANT_PROFILE_RECOGNIZERS.find((entry) => entry.id === id) ?? null : null;
828
1304
  }
829
- function merchantBodyRefusal(profile, body) {
830
- const p = merchantProfileRecognizerById(typeof profile === "string" ? profile : profile?.id);
831
- if (!p)
832
- throw new Error("Refusing to pay: unknown merchant profile.");
1305
+ function textRefusal(p, parsed) {
1306
+ const amount = p.amount;
1307
+ const amountPaths = Array.isArray(amount) ? [amount] : isPlainObject(amount) ? [amount.value, amount.currency] : [];
1308
+ const paths = [p.holder, ...p.requires.map((rule) => rule.path), ...p.refuse.map((rule) => rule.path), ...amountPaths].filter(Array.isArray);
1309
+ for (const path of paths) {
1310
+ for (const [k, step] of path.entries()) {
1311
+ if (!isTextStep(step))
1312
+ continue;
1313
+ const before = path.slice(0, k);
1314
+ if (valuesIgnoringCase(parsed, before).some((value) => valuesIgnoringCase(value, [TEXT_STEP]).length === 0)) {
1315
+ 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`;
1316
+ }
1317
+ }
1318
+ }
1319
+ return null;
1320
+ }
1321
+ function formBodyView(body) {
1322
+ if (typeof body !== "string" || body === "")
1323
+ return void 0;
1324
+ let params;
1325
+ try {
1326
+ params = new URLSearchParams(body);
1327
+ }
1328
+ catch {
1329
+ return void 0;
1330
+ }
1331
+ if (params.toString() !== body)
1332
+ return void 0;
1333
+ const view = /* @__PURE__ */ Object.create(null);
1334
+ for (const [name, value] of params) {
1335
+ if (!Object.prototype.hasOwnProperty.call(view, name))
1336
+ view[name] = value;
1337
+ else
1338
+ view[name] = Array.isArray(view[name]) ? [...view[name], value] : [view[name], value];
1339
+ }
1340
+ return view;
1341
+ }
1342
+ function readProfileBody(p, body) {
1343
+ if (p.encoding === "form") {
1344
+ const view = formBodyView(body);
1345
+ 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 };
1346
+ }
833
1347
  let parsed;
834
1348
  try {
835
1349
  parsed = typeof body === "string" ? JSON.parse(body) : void 0;
836
1350
  }
837
1351
  catch {
838
- return "the paused body is not JSON";
1352
+ return { refusal: "the paused body is not JSON" };
839
1353
  }
840
1354
  if (!isPlainObject(parsed))
841
- return "the paused body is not a JSON object";
1355
+ return { refusal: "the paused body is not a JSON object" };
842
1356
  if (!roundTrips(parsed, body))
843
- return "the paused body does not round-trip through JSON, so its rules cannot be read";
1357
+ return { refusal: "the paused body does not round-trip through JSON, so its rules cannot be read" };
1358
+ return { parsed };
1359
+ }
1360
+ function merchantBodyRefusal(profile, body) {
1361
+ const p = merchantProfileRecognizerById(typeof profile === "string" ? profile : profile?.id);
1362
+ if (!p)
1363
+ throw new Error("Refusing to pay: unknown merchant profile.");
1364
+ const read = readProfileBody(p, body);
1365
+ if (read.refusal)
1366
+ return read.refusal;
1367
+ const parsed = read.parsed;
1368
+ const unreadText = textRefusal(p, parsed);
1369
+ if (unreadText)
1370
+ return unreadText;
844
1371
  for (const rule of p.requires) {
845
- const at = jsonAt(parsed, rule.path);
1372
+ const at = profileAt(parsed, rule.path);
846
1373
  if (!at.found || at.value !== rule.equals || valuesIgnoringCase(parsed, rule.path).some((value) => value !== rule.equals)) {
847
1374
  return `the paused body is not ${p.merchant}'s card request: ${describePath(rule.path)} is not ${JSON.stringify(rule.equals)}`;
848
1375
  }