@agent-cards/sdk 0.21.0 → 0.23.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/PREFLIGHT.md +32 -0
  3. package/README.md +1177 -23
  4. package/dist/aci.d.ts +173 -0
  5. package/dist/aci.generated.d.ts +87 -0
  6. package/dist/aci.generated.js +306 -0
  7. package/dist/aci.js +371 -0
  8. package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
  9. package/dist/adyen-merchant-hosted.generated.js +302 -56
  10. package/dist/agentcard.d.ts +9 -2
  11. package/dist/agentcard.js +4 -2
  12. package/dist/amadeus-checkout.generated.d.ts +57 -0
  13. package/dist/amadeus-checkout.generated.js +549 -0
  14. package/dist/amadeus.d.ts +41 -0
  15. package/dist/amadeus.js +81 -0
  16. package/dist/builtin-registry.generated.js +1 -1
  17. package/dist/card-endpoint-facts.d.ts +96 -0
  18. package/dist/card-endpoint-facts.js +275 -0
  19. package/dist/card-endpoint-page.d.ts +54 -0
  20. package/dist/card-endpoint-page.js +69 -0
  21. package/dist/card-endpoint.d.ts +298 -0
  22. package/dist/card-endpoint.js +438 -0
  23. package/dist/card-fields.generated.d.ts +5 -3
  24. package/dist/card-fields.generated.js +284 -7
  25. package/dist/cdp.d.ts +5 -1
  26. package/dist/cdp.js +1155 -141
  27. package/dist/client.d.ts +456 -20
  28. package/dist/client.js +1072 -104
  29. package/dist/creditguard.generated.d.ts +79 -0
  30. package/dist/creditguard.generated.js +662 -0
  31. package/dist/cse-body.js +11 -1
  32. package/dist/cybersource-flex.d.ts +30 -0
  33. package/dist/cybersource-flex.generated.d.ts +27 -1
  34. package/dist/cybersource-flex.generated.js +42 -2
  35. package/dist/cybersource-flex.js +46 -0
  36. package/dist/datatrans.d.ts +96 -0
  37. package/dist/datatrans.generated.d.ts +64 -0
  38. package/dist/datatrans.generated.js +426 -0
  39. package/dist/datatrans.js +178 -0
  40. package/dist/ekashu.d.ts +35 -0
  41. package/dist/ekashu.js +55 -0
  42. package/dist/elavon-converge.d.ts +37 -0
  43. package/dist/elavon-converge.generated.d.ts +49 -0
  44. package/dist/elavon-converge.generated.js +439 -0
  45. package/dist/elavon-converge.js +84 -0
  46. package/dist/eprotect.d.ts +152 -0
  47. package/dist/eprotect.generated.d.ts +77 -0
  48. package/dist/eprotect.generated.js +498 -0
  49. package/dist/eprotect.js +222 -0
  50. package/dist/hosted-form.d.ts +3 -2
  51. package/dist/hosted-form.js +3 -2
  52. package/dist/index.d.ts +17 -3
  53. package/dist/index.js +6 -1
  54. package/dist/juspay-gate.d.ts +82 -0
  55. package/dist/juspay-gate.js +126 -0
  56. package/dist/juspay.generated.d.ts +89 -0
  57. package/dist/juspay.generated.js +705 -0
  58. package/dist/lifecycle.d.ts +60 -6
  59. package/dist/lifecycle.js +122 -8
  60. package/dist/merchant-card-endpoints.generated.d.ts +122 -0
  61. package/dist/merchant-card-endpoints.generated.js +4261 -0
  62. package/dist/merchant-hosted.d.ts +4 -1
  63. package/dist/merchant-hosted.js +5 -2
  64. package/dist/merchant-session-probe.d.ts +48 -0
  65. package/dist/merchant-session-probe.js +53 -0
  66. package/dist/merchant-session.generated.d.ts +69 -0
  67. package/dist/merchant-session.generated.js +919 -0
  68. package/dist/payrails.d.ts +133 -0
  69. package/dist/payrails.generated.d.ts +73 -0
  70. package/dist/payrails.generated.js +596 -0
  71. package/dist/payrails.js +305 -0
  72. package/dist/pelecard.d.ts +32 -0
  73. package/dist/pelecard.generated.d.ts +32 -0
  74. package/dist/pelecard.generated.js +283 -0
  75. package/dist/pelecard.js +42 -0
  76. package/dist/pre-claim.d.ts +81 -2
  77. package/dist/pre-claim.js +1023 -5
  78. package/dist/preflight-catalog.json +2267 -288
  79. package/dist/preflight-schemas.json +183 -6
  80. package/dist/preflight.generated.d.ts +1 -1
  81. package/dist/preflight.generated.js +243 -4
  82. package/dist/preparation.d.ts +12 -3
  83. package/dist/preparation.js +63 -12
  84. package/dist/prepared-processor.d.ts +84 -5
  85. package/dist/prepared-processor.js +147 -6
  86. package/dist/primer.d.ts +53 -0
  87. package/dist/primer.generated.d.ts +38 -0
  88. package/dist/primer.generated.js +624 -0
  89. package/dist/primer.js +82 -0
  90. package/dist/registry.d.ts +100 -8
  91. package/dist/registry.js +85 -11
  92. package/dist/shiji-sts.d.ts +144 -0
  93. package/dist/shiji-sts.generated.d.ts +72 -0
  94. package/dist/shiji-sts.generated.js +913 -0
  95. package/dist/shiji-sts.js +222 -0
  96. package/dist/stripe-checkout.generated.d.ts +1 -1
  97. package/dist/stripe-checkout.generated.js +764 -9
  98. package/dist/stripe-setup.d.ts +16 -0
  99. package/dist/stripe-setup.js +61 -0
  100. package/dist/substitutions.generated.js +187 -29
  101. package/dist/verifone.d.ts +68 -0
  102. package/dist/verifone.generated.d.ts +51 -0
  103. package/dist/verifone.generated.js +1413 -0
  104. package/dist/verifone.js +119 -0
  105. package/dist/voice-types.d.ts +27 -0
  106. package/dist/voice.d.ts +6 -1
  107. package/dist/voice.js +6 -1
  108. package/dist/wspay.d.ts +41 -0
  109. package/dist/wspay.generated.d.ts +41 -0
  110. package/dist/wspay.generated.js +370 -0
  111. package/dist/wspay.js +65 -0
  112. package/examples/preflight/kernel-native/inventory.json +1 -1
  113. package/package.json +3 -3
@@ -0,0 +1,919 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: 622844dcc82d0276b966b1c6e6714d2cc6c3cbb0f854caeba0f92c0bfb041e1b
4
+ // src/deep-freeze.js
5
+ function deepFreeze(value) {
6
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
7
+ for (const child of Object.values(value))
8
+ deepFreeze(child);
9
+ Object.freeze(value);
10
+ }
11
+ return value;
12
+ }
13
+ // src/json-path.js
14
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
15
+ function isPlainObject(v) {
16
+ return !!v && typeof v === "object" && !Array.isArray(v);
17
+ }
18
+ var MAX_PATH_STEPS = 16;
19
+ function isJsonPath(path) {
20
+ 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);
21
+ }
22
+ function jsonAt(root, path) {
23
+ let node = root;
24
+ for (const step of path) {
25
+ if (typeof step === "string") {
26
+ if (!isPlainObject(node) || FORBIDDEN_KEYS.has(step) || !Object.prototype.hasOwnProperty.call(node, step))
27
+ return { found: false };
28
+ }
29
+ else if (!Array.isArray(node) || !Number.isSafeInteger(step) || step < 0 || step >= node.length) {
30
+ return { found: false };
31
+ }
32
+ node = node[step];
33
+ }
34
+ return { found: true, value: node };
35
+ }
36
+ // src/merchant-amount.js
37
+ var ZERO_DECIMAL = /* @__PURE__ */ new Set(["BIF", "CLP", "DJF", "GNF", "ISK", "JPY", "KMF", "KRW", "PYG", "RWF", "UGX", "VND", "VUV", "XAF", "XOF", "XPF"]);
38
+ var THREE_DECIMAL = /* @__PURE__ */ new Set(["BHD", "IQD", "JOD", "KWD", "LYD", "OMR", "TND"]);
39
+ var minorDigits = (currency) => ZERO_DECIMAL.has(currency) ? 0 : THREE_DECIMAL.has(currency) ? 3 : 2;
40
+ function minorFromDecimalText(text, digits, places) {
41
+ const m = /^(\d{1,13})(?:\.(\d+))?$/.exec(text);
42
+ if (!m)
43
+ return null;
44
+ const fraction = m[2] ?? "";
45
+ if (fraction.length > places || /[^0]/.test(fraction.slice(digits)))
46
+ return null;
47
+ const minor = Number(m[1]) * 10 ** digits + Number((fraction + "0".repeat(digits)).slice(0, digits) || "0");
48
+ return Number.isSafeInteger(minor) ? minor : null;
49
+ }
50
+ function totalMinor(value, format, unit, currency) {
51
+ const digits = minorDigits(currency);
52
+ if (format === "decimal2")
53
+ return typeof value === "string" && digits === 2 && /^\d{1,13}\.\d{2}$/.test(value) ? minorFromDecimalText(value, 2, 2) : null;
54
+ if (format === "decimal")
55
+ return typeof value === "string" ? minorFromDecimalText(value, digits, 4) : null;
56
+ if (format !== "number" || typeof value !== "number" || !Number.isFinite(value) || value < 0)
57
+ return null;
58
+ if (unit === "minor")
59
+ return Number.isSafeInteger(value) ? value : null;
60
+ const text = String(value);
61
+ return /e/i.test(text) ? null : minorFromDecimalText(text, digits, 17);
62
+ }
63
+ var INPUT_TAG = /<input\b([^<>]*)>/gi;
64
+ var ATTRIBUTE = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
65
+ var withoutComments = (html) => html.replace(/<!--[\s\S]*?-->/g, "");
66
+ function inputValues(html, id) {
67
+ const values = [];
68
+ for (const tag of withoutComments(html).matchAll(INPUT_TAG)) {
69
+ const attributes = /* @__PURE__ */ new Map();
70
+ for (const attribute of tag[1].matchAll(ATTRIBUTE)) {
71
+ const name = attribute[1].toLowerCase();
72
+ if (!attributes.has(name))
73
+ attributes.set(name, attribute[2] ?? attribute[3] ?? attribute[4] ?? "");
74
+ }
75
+ if (attributes.get("id") === id)
76
+ values.push(attributes.has("value") ? attributes.get("value") : null);
77
+ }
78
+ return values;
79
+ }
80
+ // src/merchant-session-documents.js
81
+ var DUNELM_CHECKOUT_VIEW = "query CheckoutView {\n viewer {\n __typename\n checkoutProvisionalOrder {\n __typename\n ...ProvisionalOrder\n customer {\n __typename\n emailAddress\n addresses {\n __typename\n home {\n ...Address\n __typename\n }\n billing {\n ...Address\n __typename\n }\n pudoLocation {\n ...Address\n __typename\n }\n }\n }\n }\n }\n}\n\nfragment CheckoutOrderline on CheckoutOrderline {\n skuCode\n quantity\n unitPrice\n totalPrice\n unitDiscount\n onOrBy\n delivered\n elapsed\n customerPromiseDate\n carrier\n labels\n quote {\n __typename\n quoteId\n }\n store {\n __typename\n sapSiteId\n name\n uri\n streetAddress\n localArea\n city\n county\n postcode\n country\n phone\n staticMapUrl\n ccCheckInType\n location {\n __typename\n lat\n lon\n }\n tillTimes\n openingHours {\n __typename\n day\n open\n close\n }\n facilities {\n __typename\n facilityIcon\n facility\n }\n seoText {\n __typename\n facility\n facilityIcon\n type\n text\n spans {\n __typename\n start\n end\n type\n }\n }\n }\n skuDetails {\n __typename\n name\n imageUrl\n skuDefiningValues\n productName\n }\n customProduct\n customProperties {\n madeToMeasure {\n widthInCentimeters\n lengthInCentimeters\n totalMeterage\n midRailInCentimeters\n splitTiltInCentimeters\n handleHolePositionInMillimeters\n display {\n width\n length\n unitOfMeasure\n midRailUnitOfMeasure\n splitTiltUnitOfMeasure\n handleHolePositionUnitOfMeasure\n options {\n recess\n controlSide\n chainColour\n rollDirection\n bracketType\n controlType\n stackSide\n bottomWeightType\n eyeletColour\n dressSide\n singlePair\n frame\n frameSides\n frameDepthInMillimeters\n hardwareColour\n louvreSize\n numberOfPanels\n panelConfiguration\n midRail\n midRailPosition\n tPostConfiguration\n splitTilt\n splitTiltMeasure\n handleHole\n handleHolePosition\n tiltControlSide\n __typename\n }\n __typename\n }\n options {\n recess\n controlSide\n chainColour\n rollDirection\n bracketType\n controlType\n stackSide\n bottomWeightType\n eyeletColour\n dressSide\n singlePair\n frame\n frameSides\n frameDepthInMillimeters\n hardwareColour\n louvreSize\n numberOfPanels\n panelConfiguration\n midRail\n midRailPosition\n tPostConfiguration\n splitTilt\n splitTiltMeasure\n handleHole\n handleHolePosition\n tiltControlSide\n __typename\n }\n __typename\n }\n fitting {\n isSelected\n durationInHours\n numberOfWindows\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment Address on Address {\n id\n addressCode\n lastName\n city\n addressType\n county\n postcode\n title\n firstName\n phoneNumber\n countryCode\n line1\n line2\n line3\n __typename\n}\n\nfragment ProvisionalOrder on CheckoutProvisionalOrder {\n provisionalOrderId\n preferredDeliveryMethod\n expiryDate\n pup {\n __typename\n itemCount\n lines {\n ...CheckoutOrderline\n __typename\n }\n }\n cc {\n __typename\n itemCount\n lines {\n ...CheckoutOrderline\n __typename\n }\n collections {\n __typename\n lines {\n ...CheckoutOrderline\n __typename\n }\n }\n }\n hd {\n __typename\n itemCount\n lines {\n ...CheckoutOrderline\n __typename\n }\n deliveries {\n type\n onOrBy\n date\n fulfilmentType\n deliveryCharge\n isDeliveryCallRequired\n groupId\n otherOptions {\n type\n date\n deliveryCharge\n __typename\n }\n lines {\n ...CheckoutOrderline\n __typename\n }\n __typename\n }\n deliveryCharges {\n type\n charge\n __typename\n }\n }\n flags {\n paymentRequired\n isDeliveryFree\n checkoutComplete\n __typename\n }\n voucher {\n __typename\n basketDiscount\n discountedDeliveryCharges {\n discountedAmount\n originalCharge\n type\n __typename\n }\n code\n description\n }\n prices {\n __typename\n delivery\n subtotal\n orderLinesPayable\n total\n }\n promises {\n sku\n available\n promises {\n ... on Delivery {\n __typename\n }\n ... on HomeDelivery {\n onOrBy\n date\n price\n available\n __typename\n }\n ... on Standard {\n onOrBy\n date\n price\n available\n __typename\n }\n ... on NominatedDay {\n onOrBy\n date\n price\n available\n __typename\n }\n ... on Express {\n onOrBy\n date\n price\n available\n __typename\n }\n ... on ClickAndCollect {\n available\n stores {\n sapSiteId\n availability {\n sku\n quantity\n stock\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n}\n";
82
+ // src/sha256.js
83
+ var K = /* @__PURE__ */ new Uint32Array([
84
+ 1116352408,
85
+ 1899447441,
86
+ 3049323471,
87
+ 3921009573,
88
+ 961987163,
89
+ 1508970993,
90
+ 2453635748,
91
+ 2870763221,
92
+ 3624381080,
93
+ 310598401,
94
+ 607225278,
95
+ 1426881987,
96
+ 1925078388,
97
+ 2162078206,
98
+ 2614888103,
99
+ 3248222580,
100
+ 3835390401,
101
+ 4022224774,
102
+ 264347078,
103
+ 604807628,
104
+ 770255983,
105
+ 1249150122,
106
+ 1555081692,
107
+ 1996064986,
108
+ 2554220882,
109
+ 2821834349,
110
+ 2952996808,
111
+ 3210313671,
112
+ 3336571891,
113
+ 3584528711,
114
+ 113926993,
115
+ 338241895,
116
+ 666307205,
117
+ 773529912,
118
+ 1294757372,
119
+ 1396182291,
120
+ 1695183700,
121
+ 1986661051,
122
+ 2177026350,
123
+ 2456956037,
124
+ 2730485921,
125
+ 2820302411,
126
+ 3259730800,
127
+ 3345764771,
128
+ 3516065817,
129
+ 3600352804,
130
+ 4094571909,
131
+ 275423344,
132
+ 430227734,
133
+ 506948616,
134
+ 659060556,
135
+ 883997877,
136
+ 958139571,
137
+ 1322822218,
138
+ 1537002063,
139
+ 1747873779,
140
+ 1955562222,
141
+ 2024104815,
142
+ 2227730452,
143
+ 2361852424,
144
+ 2428436474,
145
+ 2756734187,
146
+ 3204031479,
147
+ 3329325298
148
+ ]);
149
+ var rotr = (x, n) => x >>> n | x << 32 - n;
150
+ function utf8(text) {
151
+ const out = [];
152
+ for (const char of text) {
153
+ let cp = char.codePointAt(0);
154
+ if (cp >= 55296 && cp <= 57343)
155
+ cp = 65533;
156
+ if (cp < 128)
157
+ out.push(cp);
158
+ else if (cp < 2048)
159
+ out.push(192 | cp >> 6, 128 | cp & 63);
160
+ else if (cp < 65536)
161
+ out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
162
+ else
163
+ out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
164
+ }
165
+ return Uint8Array.from(out);
166
+ }
167
+ function sha256Hex(input) {
168
+ const isBytes = Object.prototype.toString.call(input) === "[object Uint8Array]";
169
+ if (typeof input !== "string" && !isBytes)
170
+ throw new TypeError("sha256Hex takes a string or a Uint8Array.");
171
+ const bytes = typeof input === "string" ? utf8(input) : input;
172
+ const length = bytes.length;
173
+ const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
174
+ padded.set(bytes);
175
+ padded[length] = 128;
176
+ const view = new DataView(padded.buffer);
177
+ view.setUint32(padded.length - 8, Math.floor(length / 536870912));
178
+ view.setUint32(padded.length - 4, length << 3 >>> 0);
179
+ const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
180
+ const W = new Uint32Array(64);
181
+ for (let offset = 0; offset < padded.length; offset += 64) {
182
+ for (let t = 0; t < 16; t++)
183
+ W[t] = view.getUint32(offset + t * 4);
184
+ for (let t = 16; t < 64; t++) {
185
+ const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
186
+ const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
187
+ W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
188
+ }
189
+ let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
190
+ for (let t = 0; t < 64; t++) {
191
+ const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
192
+ const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
193
+ h = g;
194
+ g = f;
195
+ f = e;
196
+ e = d + t1 >>> 0;
197
+ d = c;
198
+ c = b;
199
+ b = a;
200
+ a = t1 + t2 >>> 0;
201
+ }
202
+ H[0] += a;
203
+ H[1] += b;
204
+ H[2] += c;
205
+ H[3] += d;
206
+ H[4] += e;
207
+ H[5] += f;
208
+ H[6] += g;
209
+ H[7] += h;
210
+ }
211
+ return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
212
+ }
213
+ // src/merchant-session.js
214
+ var MERCHANT_SESSION_MODE = "merchant_session";
215
+ var MERCHANT_SESSION_USER_AGENT = "AgentcardMerchantRead/1.0 (+https://agentcard.sh)";
216
+ var MERCHANT_SESSION_BOT_COOKIES = /* @__PURE__ */ deepFreeze(["cf_clearance", "__cf_bm", "_abck", "bm_sz", "ak_bmsc"]);
217
+ var MERCHANT_SESSION_SIGN_IN_COOKIES = /* @__PURE__ */ deepFreeze([
218
+ "^auth0\\.[^.]+\\.is\\.authenticated$",
219
+ "^_legacy_auth0\\.[^.]+\\.is\\.authenticated$",
220
+ "^(?:__Secure-|__Host-)?(?:authjs|next-auth)\\.session-token(?:\\.\\d+)?$",
221
+ "^\\.ASPXAUTH$",
222
+ "^\\.AspNet\\.ApplicationCookie$",
223
+ "^\\.AspNetCore\\.Identity\\.Application$"
224
+ ]);
225
+ var MERCHANT_SESSION_READ_LIMITS = /* @__PURE__ */ deepFreeze({ toMerchantBytes: 256 * 1024, toBrowserBytes: 1024 * 1024, durationMs: 6e4 });
226
+ var MERCHANT_SESSION_RELAY_URL = "wss://merchant-session-relay.agentcard.sh/relay";
227
+ var MERCHANT_SESSION_COOKIE_LIMITS = /* @__PURE__ */ deepFreeze({ captured: 96, kept: 64, nameBytes: 256, valueBytes: 4096, headerBytes: 16 * 1024 });
228
+ var MAX_ANSWER_BYTES = 256 * 1024;
229
+ var DOCUMENTS = /* @__PURE__ */ Object.freeze({ ee1e641857cf90bead052cc581ecacc56bf67fa625808f4f094a0a13e1ab8b93: DUNELM_CHECKOUT_VIEW });
230
+ var HEADERS = (accept, language, extra = []) => [
231
+ ["accept", accept],
232
+ ["accept-encoding", "identity"],
233
+ ["accept-language", language],
234
+ ["cache-control", "no-cache"],
235
+ ...extra,
236
+ ["pragma", "no-cache"],
237
+ ["user-agent", MERCHANT_SESSION_USER_AGENT]
238
+ ];
239
+ var MERCHANT_SESSION_PROFILES = /* @__PURE__ */ deepFreeze([
240
+ {
241
+ // Read Sep 29, 2026 in Felipe's agent Chrome (D15 A, evidence/dunelm): the legacy
242
+ // checkout (Unleash exp_checkout_mfe 'control') posts CheckoutView with variables {}
243
+ // to was.dunelm.com and reads the provisional order from the signed cookie-session
244
+ // was_session_2. The answer names prices in pounds (a JSON number) and no currency;
245
+ // Dunelm sells only in GBP. customerPromiseDate changes on every read, so the goods
246
+ // digest leaves it out. The page sends sas-use-cache: 1; this read omits it until the
247
+ // evidence shows what it does (DESIGN 7.1), and every answer seen was no-store and a
248
+ // miss at both Fastly tiers. The MFE variant (ProvisionalOrder by id, no cookies) is a
249
+ // different read: its session reads CheckoutView as null and is refused ('no_order').
250
+ profile: "dunelm_was_graphql",
251
+ merchant: "Dunelm",
252
+ status: "observe",
253
+ mode: MERCHANT_SESSION_MODE,
254
+ binding: { level: "detected", evidence: [] },
255
+ guestOnly: true,
256
+ endpoint: "https://was.dunelm.com",
257
+ cookies: { drop: [] },
258
+ signIn: { cookies: [] },
259
+ read: {
260
+ method: "POST",
261
+ path: "/graphql",
262
+ query: {},
263
+ headers: HEADERS("*/*", "en-GB", [["content-type", "application/json"]]),
264
+ body: {
265
+ operationName: "CheckoutView",
266
+ documentSha256: "ee1e641857cf90bead052cc581ecacc56bf67fa625808f4f094a0a13e1ab8b93",
267
+ bodySha256: "e9f68a4fcb0dba9caf9649a9b1d72b95539efc09339106c59d7ffd5998994d57"
268
+ },
269
+ format: "json",
270
+ at: ["data", "viewer", "checkoutProvisionalOrder"],
271
+ // One Click & Collect line read 29,969 bytes, mostly the store's seoText.
272
+ maxAnswerBytes: 128 * 1024,
273
+ refuseCacheHit: true
274
+ },
275
+ total: { path: ["prices", "total"], format: "number", unit: "major", currency: "GBP" },
276
+ // false means nothing is left for a card; a GraphQL error result is not a total,
277
+ // even beside partial data; a completed checkout is not the one being paid.
278
+ requires: [{ path: ["flags", "paymentRequired"], equals: true }],
279
+ refuse: [{ path: ["errors"], when: "present", root: true }, { path: ["flags", "checkoutComplete"], when: "true" }],
280
+ cartId: ["provisionalOrderId"],
281
+ expiry: ["expiryDate"],
282
+ snapshot: [
283
+ ["provisionalOrderId"],
284
+ ["prices", "total"],
285
+ ["prices", "subtotal"],
286
+ ["prices", "orderLinesPayable"],
287
+ ["prices", "delivery"],
288
+ ["voucher", "basketDiscount"],
289
+ ["flags", "paymentRequired"],
290
+ ["expiryDate"]
291
+ ],
292
+ digests: {
293
+ goods: {
294
+ lists: [["pup", "lines"], ["cc", "lines"], ["cc", "collections", "*", "lines"], ["hd", "lines"], ["hd", "deliveries", "*", "lines"]],
295
+ fields: [["skuCode"], ["quantity"], ["unitPrice"], ["totalPrice"], ["unitDiscount"], ["carrier"], ["store", "sapSiteId"], ["customProduct"]]
296
+ },
297
+ destination: [
298
+ ["preferredDeliveryMethod"],
299
+ ["cc", "lines", "*", "store", "sapSiteId"],
300
+ ["cc", "collections", "*", "lines", "*", "store", "sapSiteId"],
301
+ ["hd", "deliveries", "*", "type"],
302
+ ["customer", "addresses", "home"],
303
+ ["customer", "addresses", "pudoLocation"]
304
+ ],
305
+ recipient: [["customer", "emailAddress"], ["customer", "addresses", "billing"]]
306
+ },
307
+ relay: { toMerchantBytes: 64 * 1024, toBrowserBytes: 320 * 1024, durationMs: 3e4 }
308
+ },
309
+ {
310
+ // Read Sep 29, 2026 in Felipe's agent Chrome (D15 A, evidence/cinemark): the guest
311
+ // checkout document GET /checkout renders the cart into #checkout_v2 with exactly one
312
+ // hidden input#CartSummary_TotalWithServiceFee (dollars as decimal text), next to
313
+ // eight anti-forgery tokens and six Umbraco route tokens that change on every render,
314
+ // which the goods digest never reads (it reads the container's text, not attribute
315
+ // values). The page's own XHR reload (GET /Checkout with X-Requested-With) did not
316
+ // run in that session, so the observed document is the pinned read. Cloudflare issued
317
+ // cf_clearance to Chrome silently; this read never sends it. __cflb (load-balancer
318
+ // affinity, not a bot score) stays in the session beside ASP.NET_SessionId, which
319
+ // holds the cart. An inline script must declare isAuthenticated false (the page's own
320
+ // adyenOptions does), and the page must write it true nowhere (D22).
321
+ profile: "cinemark_initiate",
322
+ merchant: "Cinemark",
323
+ status: "observe",
324
+ mode: MERCHANT_SESSION_MODE,
325
+ binding: { level: "detected", evidence: [] },
326
+ guestOnly: true,
327
+ endpoint: "https://www.cinemark.com",
328
+ cookies: { drop: [] },
329
+ signIn: { cookies: [] },
330
+ read: {
331
+ method: "GET",
332
+ path: "/checkout",
333
+ query: {},
334
+ headers: HEADERS("text/html", "en-US"),
335
+ body: null,
336
+ format: "html",
337
+ container: "checkout_v2",
338
+ // The document read 210,933 bytes decoded; the TLS runtime stops at 256 KiB.
339
+ maxAnswerBytes: 256 * 1024,
340
+ refuseCacheHit: true
341
+ },
342
+ total: { input: "CartSummary_TotalWithServiceFee", format: "decimal", unit: "major", currency: "USD" },
343
+ guest: { scriptFlag: "isAuthenticated" },
344
+ // The theater the tickets are for; Kount's device session id when the page renders
345
+ // one (none on Sep 29: risk data now comes from Adyen's own data collection).
346
+ scriptValues: ["theaterId"],
347
+ kountInput: "KountSessionId",
348
+ relay: { toMerchantBytes: 64 * 1024, toBrowserBytes: 576 * 1024, durationMs: 3e4 }
349
+ }
350
+ ]);
351
+ function merchantSessionProfile(id) {
352
+ return typeof id === "string" ? MERCHANT_SESSION_PROFILES.find((block) => block.profile === id) ?? null : null;
353
+ }
354
+ function required(id) {
355
+ const block = merchantSessionProfile(id);
356
+ if (!block)
357
+ throw new Error("Refusing to read: unknown merchant-session profile.");
358
+ const refusal = merchantSessionProfileRefusal(block);
359
+ if (refusal)
360
+ throw new Error(`Refusing to read: the ${block.profile} session block fails review (${refusal}).`);
361
+ return block;
362
+ }
363
+ function merchantSessionHosts() {
364
+ return [...new Set(MERCHANT_SESSION_PROFILES.map((block) => new URL(block.endpoint).hostname))].sort();
365
+ }
366
+ var JSON_MEMBERS = [
367
+ "profile",
368
+ "merchant",
369
+ "status",
370
+ "mode",
371
+ "binding",
372
+ "guestOnly",
373
+ "endpoint",
374
+ "cookies",
375
+ "signIn",
376
+ "read",
377
+ "total",
378
+ "requires",
379
+ "refuse",
380
+ "cartId",
381
+ "expiry",
382
+ "snapshot",
383
+ "digests",
384
+ "relay"
385
+ ];
386
+ var HTML_MEMBERS = [
387
+ "profile",
388
+ "merchant",
389
+ "status",
390
+ "mode",
391
+ "binding",
392
+ "guestOnly",
393
+ "endpoint",
394
+ "cookies",
395
+ "signIn",
396
+ "read",
397
+ "total",
398
+ "guest",
399
+ "scriptValues",
400
+ "kountInput",
401
+ "relay"
402
+ ];
403
+ var READ_MEMBERS = {
404
+ json: ["method", "path", "query", "headers", "body", "format", "at", "maxAnswerBytes", "refuseCacheHit"],
405
+ html: ["method", "path", "query", "headers", "body", "format", "container", "maxAnswerBytes", "refuseCacheHit"]
406
+ };
407
+ var READ_HEADERS = /* @__PURE__ */ new Set(["accept", "accept-encoding", "accept-language", "cache-control", "content-type", "pragma", "user-agent", "x-requested-with"]);
408
+ var MUST = { "accept-encoding": "identity", "cache-control": "no-cache", pragma: "no-cache", "user-agent": MERCHANT_SESSION_USER_AGENT };
409
+ var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,256}$/;
410
+ var ELEMENT_ID = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
411
+ var SCRIPT_NAME = /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/;
412
+ var exactly = (value, members) => isPlainObject(value) && Object.keys(value).length === members.length && members.every((m) => Object.hasOwn(value, m));
413
+ var isWildPath = (path) => Array.isArray(path) && path.length > 0 && isJsonPath(path.map((step) => step === "*" ? 0 : step));
414
+ var compiles = (source) => {
415
+ try {
416
+ new RegExp(source);
417
+ return true;
418
+ }
419
+ catch {
420
+ return false;
421
+ }
422
+ };
423
+ var bounded = (value, max) => Number.isSafeInteger(value) && value >= 1 && value <= max;
424
+ function headersRefusal(read) {
425
+ const headers = read.headers;
426
+ if (!Array.isArray(headers) || headers.length === 0 || headers.length > 12)
427
+ return "read.headers is not a list of at most 12 pairs";
428
+ const seen = /* @__PURE__ */ new Map();
429
+ for (const pair of headers) {
430
+ if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || typeof pair[1] !== "string")
431
+ return "a read header is not a [name, value] pair";
432
+ const [name, value] = pair;
433
+ if (!READ_HEADERS.has(name))
434
+ return `read header ${name} is not one a read may pin`;
435
+ if (seen.has(name))
436
+ return `read header ${name} is pinned twice`;
437
+ if (!/^[\x20-\x7e]{1,256}$/.test(value))
438
+ return `read header ${name} has no printable value`;
439
+ seen.set(name, value);
440
+ }
441
+ for (const [name, value] of Object.entries(MUST)) {
442
+ if (seen.get(name) !== value)
443
+ return `read header ${name} must be ${value}`;
444
+ }
445
+ if (read.method === "POST" ? seen.get("content-type") !== "application/json" : seen.has("content-type")) {
446
+ return "a POST read pins content-type application/json, and a GET read pins none";
447
+ }
448
+ return null;
449
+ }
450
+ function bodyRefusal(read) {
451
+ if (read.method === "GET")
452
+ return read.body === null ? null : "a GET read has no body";
453
+ const body = read.body;
454
+ if (!exactly(body, ["operationName", "documentSha256", "bodySha256"]) || typeof body.operationName !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(body.operationName))
455
+ return "read.body is not {operationName, documentSha256, bodySha256}";
456
+ const document = Object.hasOwn(DOCUMENTS, body.documentSha256) ? DOCUMENTS[body.documentSha256] : null;
457
+ if (!document || sha256Hex(document) !== body.documentSha256)
458
+ return "read.body names no pinned document";
459
+ if (!new RegExp(`^query ${body.operationName}\\b`).test(document))
460
+ return "the pinned document is not a query named by operationName";
461
+ if (sha256Hex(readBody(read)) !== body.bodySha256)
462
+ return "the pinned body does not hash to bodySha256";
463
+ return null;
464
+ }
465
+ function digestsRefusal(digests) {
466
+ if (!exactly(digests, ["goods", "destination", "recipient"]))
467
+ return "digests is not {goods, destination, recipient}";
468
+ const goods = digests.goods;
469
+ if (!exactly(goods, ["lists", "fields"]) || !Array.isArray(goods.lists) || !goods.lists.every(isWildPath) || !Array.isArray(goods.fields) || !goods.fields.every(isWildPath))
470
+ return "digests.goods is not {lists, fields} of paths";
471
+ for (const name of ["destination", "recipient"]) {
472
+ if (!Array.isArray(digests[name]) || !digests[name].every(isWildPath))
473
+ return `digests.${name} is not a list of paths`;
474
+ }
475
+ return null;
476
+ }
477
+ function merchantSessionProfileRefusal(block) {
478
+ if (!isPlainObject(block) || !isPlainObject(block.read))
479
+ return "the block is not an object with a read";
480
+ const format = block.read.format;
481
+ if (format !== "json" && format !== "html")
482
+ return "read.format is neither json nor html";
483
+ if (!exactly(block, format === "json" ? JSON_MEMBERS : HTML_MEMBERS))
484
+ return `the block's members are not the ${format} block's`;
485
+ if (typeof block.profile !== "string" || !/^[a-z][a-z0-9_]{0,63}$/.test(block.profile))
486
+ return "profile is not a profile id";
487
+ if (typeof block.merchant !== "string" || !block.merchant)
488
+ return "merchant is not named";
489
+ if (block.status !== "observe")
490
+ return "status is not observe: no session block reads for a payment yet";
491
+ if (block.mode !== MERCHANT_SESSION_MODE)
492
+ return `mode is not ${MERCHANT_SESSION_MODE}`;
493
+ const binding = block.binding;
494
+ if (!exactly(binding, ["level", "evidence"]) || !Array.isArray(binding.evidence))
495
+ return "binding is not {level, evidence}";
496
+ if (binding.level === "prevented") {
497
+ if (!binding.evidence.some((entry) => isPlainObject(entry) && ["merchant_statement", "merchant_documentation"].includes(entry.kind))) {
498
+ return "binding prevented needs a merchant statement or documentation";
499
+ }
500
+ }
501
+ else if (binding.level !== "detected")
502
+ return "binding.level is neither detected nor prevented";
503
+ if (binding.evidence.some((entry) => !isPlainObject(entry) || entry.kind === "har"))
504
+ return "binding evidence of kind har never proves a binding";
505
+ if (block.guestOnly !== true)
506
+ return "guestOnly is not true";
507
+ let origin;
508
+ try {
509
+ origin = new URL(block.endpoint);
510
+ }
511
+ catch {
512
+ return "endpoint is not a URL";
513
+ }
514
+ if (origin.protocol !== "https:" || origin.origin !== block.endpoint || origin.port)
515
+ return "endpoint is not an exact https origin";
516
+ if (!exactly(block.cookies, ["drop"]) || !Array.isArray(block.cookies.drop) || !block.cookies.drop.every((name) => TOKEN.test(name))) {
517
+ return "cookies is not {drop} of cookie names";
518
+ }
519
+ if (!exactly(block.signIn, ["cookies"]) || !Array.isArray(block.signIn.cookies) || !block.signIn.cookies.every((source) => typeof source === "string" && source.length > 0 && compiles(source))) {
520
+ return "signIn is not {cookies} of patterns";
521
+ }
522
+ const read = block.read;
523
+ if (!exactly(read, READ_MEMBERS[format]))
524
+ return `read's members are not the ${format} read's`;
525
+ if (read.method !== "GET" && read.method !== "POST")
526
+ return "read.method is neither GET nor POST";
527
+ if (typeof read.path !== "string" || !/^\/[A-Za-z0-9._~/-]{0,255}$/.test(read.path) || read.path.split("/").some((segment) => segment === "." || segment === ".."))
528
+ return "read.path is not a plain path";
529
+ if (!isPlainObject(read.query) || Object.keys(read.query).length > 0)
530
+ return "read.query is not {}";
531
+ const headers = headersRefusal(read);
532
+ if (headers)
533
+ return headers;
534
+ const body = bodyRefusal(read);
535
+ if (body)
536
+ return body;
537
+ if (!bounded(read.maxAnswerBytes, MAX_ANSWER_BYTES))
538
+ return `read.maxAnswerBytes is not 1 to ${MAX_ANSWER_BYTES}`;
539
+ if (read.refuseCacheHit !== true)
540
+ return "read.refuseCacheHit is not true";
541
+ const total = block.total;
542
+ if (!isPlainObject(total) || !["major", "minor"].includes(total.unit) || typeof total.currency !== "string" || !/^[A-Z]{3}$/.test(total.currency)) {
543
+ return "total names no unit and currency";
544
+ }
545
+ if (format === "json") {
546
+ if (read.method !== "POST" || !isJsonPath(read.at))
547
+ return "a json read is a POST with an at path";
548
+ if (!exactly(total, ["path", "format", "unit", "currency"]) || !isJsonPath(total.path) || !["number", "decimal", "decimal2"].includes(total.format)) {
549
+ return "total is not {path, format, unit, currency}";
550
+ }
551
+ if (!Array.isArray(block.requires) || !block.requires.every((rule) => exactly(rule, ["path", "equals"]) && isJsonPath(rule.path))) {
552
+ return "requires is not a list of {path, equals}";
553
+ }
554
+ if (!Array.isArray(block.refuse) || !block.refuse.every((rule) => isPlainObject(rule) && isJsonPath(rule.path) && ["present", "true"].includes(rule.when) && (rule.root === void 0 || rule.root === true)))
555
+ return "refuse is not a list of {path, when, root?}";
556
+ if (!isJsonPath(block.cartId) || !(block.expiry === null || isJsonPath(block.expiry)))
557
+ return "cartId or expiry is not a path";
558
+ if (!Array.isArray(block.snapshot) || block.snapshot.length === 0 || !block.snapshot.every(isJsonPath))
559
+ return "snapshot is not a list of paths";
560
+ const digests = digestsRefusal(block.digests);
561
+ if (digests)
562
+ return digests;
563
+ }
564
+ else {
565
+ if (read.method !== "GET" || typeof read.container !== "string" || !ELEMENT_ID.test(read.container))
566
+ return "an html read is a GET with a container id";
567
+ if (!exactly(total, ["input", "format", "unit", "currency"]) || !ELEMENT_ID.test(total.input) || total.format !== "decimal") {
568
+ return "total is not {input, format decimal, unit, currency}";
569
+ }
570
+ if (!exactly(block.guest, ["scriptFlag"]) || !SCRIPT_NAME.test(block.guest.scriptFlag))
571
+ return "guest is not {scriptFlag}";
572
+ if (!Array.isArray(block.scriptValues) || !block.scriptValues.every((name) => SCRIPT_NAME.test(name)))
573
+ return "scriptValues is not a list of names";
574
+ if (!(block.kountInput === null || ELEMENT_ID.test(block.kountInput)))
575
+ return "kountInput is not an element id";
576
+ }
577
+ if (total.format !== "number" && total.unit !== "major")
578
+ return "a decimal total is in major units";
579
+ if (total.format === "decimal2" && totalMinor("1.00", "decimal2", "major", total.currency) === null) {
580
+ return `a decimal2 total needs a currency with two decimals, which ${total.currency} does not have`;
581
+ }
582
+ const relay = block.relay;
583
+ if (!exactly(relay, ["toMerchantBytes", "toBrowserBytes", "durationMs"]) || !bounded(relay.toMerchantBytes, MERCHANT_SESSION_READ_LIMITS.toMerchantBytes) || !bounded(relay.toBrowserBytes, MERCHANT_SESSION_READ_LIMITS.toBrowserBytes) || !bounded(relay.durationMs, MERCHANT_SESSION_READ_LIMITS.durationMs))
584
+ return "relay does not fit the relay's read grant ceilings";
585
+ if (relay.toBrowserBytes < read.maxAnswerBytes + 32 * 1024)
586
+ return "relay.toBrowserBytes leaves no room for one whole answer";
587
+ return null;
588
+ }
589
+ var COOKIE_VALUE = /^(?:[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*|"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*")$/;
590
+ var byteLength = (text) => {
591
+ let n = 0;
592
+ for (const char of text) {
593
+ const cp = char.codePointAt(0);
594
+ n += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
595
+ }
596
+ return n;
597
+ };
598
+ var refused = (code, reason) => ({ ok: false, code, reason });
599
+ function merchantSessionCookies(id, cookies) {
600
+ const block = merchantSessionProfile(id);
601
+ if (!block)
602
+ return refused("unknown_profile", "no reviewed merchant-session block has this id");
603
+ if (!Array.isArray(cookies) || cookies.length > MERCHANT_SESSION_COOKIE_LIMITS.captured) {
604
+ return refused("invalid_session", `the session is not a list of at most ${MERCHANT_SESSION_COOKIE_LIMITS.captured} cookies`);
605
+ }
606
+ const signIn = [...MERCHANT_SESSION_SIGN_IN_COOKIES, ...block.signIn.cookies].map((source) => new RegExp(source));
607
+ const drop = /* @__PURE__ */ new Set([...MERCHANT_SESSION_BOT_COOKIES, ...block.cookies.drop]);
608
+ const kept = [];
609
+ const dropped = [];
610
+ const names = /* @__PURE__ */ new Set();
611
+ for (const cookie of cookies) {
612
+ if (!exactly(cookie, ["name", "value"]) || typeof cookie.name !== "string" || typeof cookie.value !== "string" || !TOKEN.test(cookie.name) || byteLength(cookie.name) > MERCHANT_SESSION_COOKIE_LIMITS.nameBytes || !COOKIE_VALUE.test(cookie.value) || byteLength(cookie.value) > MERCHANT_SESSION_COOKIE_LIMITS.valueBytes) {
613
+ return refused("invalid_session", "a session cookie is not an RFC 6265 name and value within the limits");
614
+ }
615
+ if (names.has(cookie.name))
616
+ return refused("invalid_session", `the session names cookie ${cookie.name} twice`);
617
+ names.add(cookie.name);
618
+ if (signIn.some((pattern) => pattern.test(cookie.name))) {
619
+ return refused("signed_in", `the session carries ${cookie.name}, which marks a signed-in account: phase 1 reads guest checkouts only`);
620
+ }
621
+ if (drop.has(cookie.name))
622
+ dropped.push(cookie.name);
623
+ else
624
+ kept.push({ name: cookie.name, value: cookie.value });
625
+ }
626
+ if (kept.length === 0)
627
+ return refused("no_session", "no cookie is left to name the cart");
628
+ if (kept.length > MERCHANT_SESSION_COOKIE_LIMITS.kept)
629
+ return refused("invalid_session", `more than ${MERCHANT_SESSION_COOKIE_LIMITS.kept} cookies are left to send`);
630
+ const header = kept.map(({ name, value }) => `${name}=${value}`).join("; ");
631
+ if (byteLength(header) > MERCHANT_SESSION_COOKIE_LIMITS.headerBytes)
632
+ return refused("invalid_session", "the Cookie header would pass 16 KiB");
633
+ return { ok: true, cookies: kept, dropped: dropped.sort(), header };
634
+ }
635
+ function readBody(read) {
636
+ return JSON.stringify({ operationName: read.body.operationName, variables: {}, query: DOCUMENTS[read.body.documentSha256] });
637
+ }
638
+ function merchantSessionRead(id, session) {
639
+ const block = required(id);
640
+ const kept = merchantSessionCookies(block.profile, isPlainObject(session) ? session.cookies : void 0);
641
+ if (!kept.ok)
642
+ throw Object.assign(new Error(`Refusing to read: ${kept.reason}.`), { code: kept.code });
643
+ const read = block.read;
644
+ return deepFreeze({
645
+ method: read.method,
646
+ url: `${block.endpoint}${read.path}`,
647
+ headers: [...read.headers.map(([name, value]) => [name, value]), ["cookie", kept.header]],
648
+ body: read.method === "POST" ? readBody(read) : null
649
+ });
650
+ }
651
+ function headerPairs(headers) {
652
+ if (!Array.isArray(headers) || headers.length > 256)
653
+ return null;
654
+ const pairs = [];
655
+ for (const pair of headers) {
656
+ if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || typeof pair[1] !== "string")
657
+ return null;
658
+ pairs.push([pair[0].toLowerCase(), pair[1]]);
659
+ }
660
+ return pairs;
661
+ }
662
+ var valuesOf = (pairs, name) => pairs.filter(([n]) => n === name).map(([, v]) => v);
663
+ function cacheHit(pairs) {
664
+ if (valuesOf(pairs, "age").some((value) => !/^\s*0+\s*$/.test(value)))
665
+ return true;
666
+ if (valuesOf(pairs, "x-cache").some((value) => /\bHIT\b/i.test(value)))
667
+ return true;
668
+ if (valuesOf(pairs, "x-cache-hits").some((value) => value.split(",").some((part) => !/^\s*0*\s*$/.test(part))))
669
+ return true;
670
+ return valuesOf(pairs, "cf-cache-status").some((value) => /^\s*(?:HIT|STALE|UPDATING|REVALIDATED)\s*$/i.test(value));
671
+ }
672
+ var CACHE_FACT_HEADERS = ["age", "cache-control", "cf-cache-status", "x-cache", "x-cache-hits"];
673
+ var CHALLENGE_STATUSES = /* @__PURE__ */ new Set([403, 406, 429, 503]);
674
+ var CACHE_FACT_VALUES = 4;
675
+ var MERCHANT_SESSION_CACHE_FACT_CHARS = CACHE_FACT_VALUES * 64 + (CACHE_FACT_VALUES - 1) * " | ".length + " | more".length;
676
+ function merchantSessionAnswerFacts(response) {
677
+ const pairs = isPlainObject(response) ? headerPairs(response.headers) : null;
678
+ const status = isPlainObject(response) && Number.isSafeInteger(response.status) ? response.status : null;
679
+ if (!pairs || status === null)
680
+ return { status, challenge: null, headerNames: [], cache: {} };
681
+ const body = typeof response.bodyText === "string" ? response.bodyText : "";
682
+ const names = new Set(pairs.map(([name]) => name));
683
+ let challenge = null;
684
+ if (names.has("cf-mitigated"))
685
+ challenge = "cloudflare";
686
+ else if (status !== 200) {
687
+ if (body.includes("/cdn-cgi/challenge-platform/h/") || /<title>\s*Just a moment\.\.\.\s*<\/title>/i.test(body))
688
+ challenge = "cloudflare";
689
+ else if (body.includes("/_sec/cp_challenge/"))
690
+ challenge = "akamai";
691
+ else if (names.has("x-datadome") || names.has("x-dd-b"))
692
+ challenge = "datadome";
693
+ else if (CHALLENGE_STATUSES.has(status))
694
+ challenge = "unknown";
695
+ }
696
+ const cache = {};
697
+ for (const name of CACHE_FACT_HEADERS) {
698
+ const values = valuesOf(pairs, name);
699
+ if (values.length) {
700
+ const kept = values.slice(0, CACHE_FACT_VALUES).map((value) => /^[A-Za-z0-9 ,=._/-]{0,64}$/.test(value) ? value : "other");
701
+ cache[name] = [...kept, ...values.length > CACHE_FACT_VALUES ? ["more"] : []].join(" | ");
702
+ }
703
+ }
704
+ const headerNames = [...names].filter((name) => /^[a-z0-9-]{1,64}$/.test(name)).sort().slice(0, 64);
705
+ return { status, challenge, headerNames, cache };
706
+ }
707
+ function valuesAtPath(root, path) {
708
+ let nodes = [root];
709
+ for (const step of path) {
710
+ const next = [];
711
+ for (const node of nodes) {
712
+ if (step === "*") {
713
+ if (Array.isArray(node))
714
+ next.push(...node);
715
+ continue;
716
+ }
717
+ const at = jsonAt(node, [step]);
718
+ if (at.found)
719
+ next.push(at.value);
720
+ }
721
+ nodes = next;
722
+ }
723
+ return nodes;
724
+ }
725
+ var hashOf = (value) => sha256Hex(JSON.stringify(value));
726
+ var pathKey = (path) => path.join(".");
727
+ function jsonReading(block, text, now) {
728
+ let parsed;
729
+ try {
730
+ parsed = JSON.parse(text);
731
+ }
732
+ catch {
733
+ return refused("unreadable", "the answer is not JSON");
734
+ }
735
+ const order = jsonAt(parsed, block.read.at);
736
+ for (const rule of block.refuse) {
737
+ const at = jsonAt(rule.root ? parsed : order.found ? order.value : void 0, rule.path);
738
+ const fires = rule.when === "present" ? at.found : at.found && at.value !== false && at.value !== null;
739
+ if (fires)
740
+ return refused(rule.root ? "error_result" : "not_payable", `the answer carries ${pathKey(rule.path)}`);
741
+ }
742
+ if (!order.found || !isPlainObject(order.value))
743
+ return refused("no_order", "the answer names no order to pay");
744
+ const node = order.value;
745
+ for (const rule of block.requires) {
746
+ const at = jsonAt(node, rule.path);
747
+ if (!at.found || at.value !== rule.equals)
748
+ return refused("not_payable", `${pathKey(rule.path)} is not ${JSON.stringify(rule.equals)}`);
749
+ }
750
+ const total = jsonAt(node, block.total.path);
751
+ const minor = total.found ? totalMinor(total.value, block.total.format, block.total.unit, block.total.currency) : null;
752
+ if (minor === null || minor <= 0)
753
+ return refused("unreadable", "the total is not a whole positive amount in minor units");
754
+ const cart = jsonAt(node, block.cartId);
755
+ const cartId = cart.found && Number.isSafeInteger(cart.value) ? String(cart.value) : cart.found ? cart.value : null;
756
+ if (typeof cartId !== "string" || !/^[\x21-\x7e]{1,256}$/.test(cartId))
757
+ return refused("unreadable", "the answer names no cart id");
758
+ let expiresAt = null;
759
+ if (block.expiry) {
760
+ const expiry = jsonAt(node, block.expiry);
761
+ expiresAt = expiry.found && typeof expiry.value === "string" ? Date.parse(expiry.value) : NaN;
762
+ if (!Number.isFinite(expiresAt))
763
+ return refused("unreadable", "the answer names no expiry");
764
+ if (Number.isFinite(now) && expiresAt <= now)
765
+ return refused("expired", "the order expired before this read");
766
+ }
767
+ const snapshot = block.snapshot.map((path) => {
768
+ const at = jsonAt(node, path);
769
+ return [pathKey(path), at.found ? at.value : null];
770
+ });
771
+ const { goods, destination, recipient } = block.digests;
772
+ const lines = goods.lists.flatMap((list) => valuesAtPath(node, list)).flatMap((list) => Array.isArray(list) ? list : []).map((line) => goods.fields.map((field) => valuesAtPath(line, field)));
773
+ const collect = (paths) => paths.map((path) => [pathKey(path), valuesAtPath(node, path)]);
774
+ return {
775
+ ok: true,
776
+ profile: block.profile,
777
+ totalMinor: minor,
778
+ currency: block.total.currency,
779
+ cartId,
780
+ cartIdSha256: sha256Hex(cartId),
781
+ expiresAt,
782
+ snapshotSha256: hashOf(snapshot),
783
+ digests: { goods: hashOf(lines), destination: hashOf(collect(destination)), recipient: hashOf(collect(recipient)) },
784
+ kountSessionSha256: null
785
+ };
786
+ }
787
+ var NON_MARKUP = /<!--[\s\S]*?-->|<(script|style|template)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
788
+ var START_TAG = /<([A-Za-z][A-Za-z0-9-]*)\b([^<>]*)>/g;
789
+ var ID_ATTRIBUTE = /(?:^|\s)id\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i;
790
+ function containerOf(markup, id) {
791
+ const opens = [...markup.matchAll(START_TAG)].filter((tag) => {
792
+ const m = ID_ATTRIBUTE.exec(tag[2]);
793
+ return m && (m[1] ?? m[2] ?? m[3]) === id;
794
+ });
795
+ if (opens.length !== 1)
796
+ return { found: false, count: opens.length };
797
+ const [open] = opens;
798
+ const name = open[1].toLowerCase();
799
+ const tags = new RegExp(`<(/?)${name}\\b[^<>]*>`, "gi");
800
+ tags.lastIndex = open.index + open[0].length;
801
+ let depth = 1;
802
+ for (let tag = tags.exec(markup); tag; tag = tags.exec(markup)) {
803
+ depth += tag[1] ? -1 : 1;
804
+ if (depth === 0)
805
+ return { found: true, inner: markup.slice(open.index + open[0].length, tag.index) };
806
+ }
807
+ return { found: false, count: 1 };
808
+ }
809
+ function scriptValues(text, name, shape) {
810
+ const pattern = new RegExp(`(?:^|[^A-Za-z0-9_$.])${name.replace(/\$/g, "\\$")}['"]?\\s*(?::|=(?!=))\\s*(['"]?)(${shape})\\1(?![A-Za-z0-9_$])`, "g");
811
+ return [...text.matchAll(pattern)].map((m) => m[2]);
812
+ }
813
+ var COMMENT_OR_SCRIPT = /<!--[\s\S]*?-->|<script\b([^<>]*)>([\s\S]*?)<\/script\s*>/gi;
814
+ var ATTRIBUTE2 = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
815
+ var JAVASCRIPT_TYPES = /* @__PURE__ */ new Set([
816
+ "application/ecmascript",
817
+ "application/javascript",
818
+ "application/x-ecmascript",
819
+ "application/x-javascript",
820
+ "text/ecmascript",
821
+ "text/javascript",
822
+ "text/javascript1.0",
823
+ "text/javascript1.1",
824
+ "text/javascript1.2",
825
+ "text/javascript1.3",
826
+ "text/javascript1.4",
827
+ "text/javascript1.5",
828
+ "text/jscript",
829
+ "text/livescript",
830
+ "text/x-ecmascript",
831
+ "text/x-javascript"
832
+ ]);
833
+ function attributesOf(text) {
834
+ const attributes = /* @__PURE__ */ new Map();
835
+ for (const m of text.matchAll(ATTRIBUTE2)) {
836
+ const name = m[1].toLowerCase();
837
+ if (!attributes.has(name))
838
+ attributes.set(name, m[2] ?? m[3] ?? m[4] ?? "");
839
+ }
840
+ return attributes;
841
+ }
842
+ function runsInline(attributes) {
843
+ if (attributes.has("src"))
844
+ return false;
845
+ const type = attributes.has("type") ? attributes.get("type") || "text/javascript" : attributes.get("language") ? `text/${attributes.get("language")}` : "text/javascript";
846
+ const typeString = type.replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "").toLowerCase();
847
+ if (typeString === "module")
848
+ return true;
849
+ return JAVASCRIPT_TYPES.has(typeString) && !attributes.has("nomodule");
850
+ }
851
+ function inlineScriptText(html) {
852
+ const texts = [];
853
+ for (const [, attributes, text] of html.matchAll(COMMENT_OR_SCRIPT)) {
854
+ if (text !== void 0 && runsInline(attributesOf(attributes)))
855
+ texts.push(text);
856
+ }
857
+ return texts.join("\n");
858
+ }
859
+ function htmlReading(block, html) {
860
+ const markup = html.replace(NON_MARKUP, "");
861
+ const container = containerOf(markup, block.read.container);
862
+ if (!container.found)
863
+ return refused(container.count > 1 ? "ambiguous" : "unreadable", `the page has ${container.count > 1 ? "more than one" : "no whole"} #${block.read.container}`);
864
+ const everywhere = inputValues(markup, block.total.input);
865
+ const inside = inputValues(container.inner, block.total.input);
866
+ if (everywhere.length > 1 || inside.length > 1)
867
+ return refused("ambiguous", `the page renders #${block.total.input} more than once`);
868
+ if (inside.length !== 1)
869
+ return refused("unreadable", `#${block.total.input} is not inside #${block.read.container}`);
870
+ const minor = totalMinor(inside[0], block.total.format, block.total.unit, block.total.currency);
871
+ if (minor === null || minor <= 0)
872
+ return refused("unreadable", "the total is not a whole positive amount in minor units");
873
+ const flag = block.guest.scriptFlag;
874
+ const flagValues = (text2) => scriptValues(text2, flag, "[Tt]rue|[Ff]alse|TRUE|FALSE").map((value) => value.toLowerCase());
875
+ if (flagValues(html).includes("true"))
876
+ return refused("signed_in", `the page says ${flag} is true: phase 1 reads guest checkouts only`);
877
+ if (!flagValues(inlineScriptText(html)).includes("false"))
878
+ return refused("unreadable", `no script on the page says ${flag} is false`);
879
+ const values = block.scriptValues.map((name) => [name, scriptValues(html, name, "\\d{1,9}")]);
880
+ const text = container.inner.replace(/<[^<>]*>/g, " ").replace(/\s+/g, " ").trim();
881
+ const kount = block.kountInput ? inputValues(markup, block.kountInput).filter((value) => typeof value === "string" && value) : [];
882
+ return {
883
+ ok: true,
884
+ profile: block.profile,
885
+ totalMinor: minor,
886
+ currency: block.total.currency,
887
+ cartId: null,
888
+ cartIdSha256: null,
889
+ expiresAt: null,
890
+ snapshotSha256: hashOf([["total", inside[0]], ...values]),
891
+ digests: { goods: sha256Hex(text), destination: hashOf(values), recipient: hashOf([]) },
892
+ kountSessionSha256: kount.length === 0 ? null : sha256Hex(kount.length === 1 ? kount[0] : JSON.stringify(kount))
893
+ };
894
+ }
895
+ function merchantSessionReading(id, response, { now } = {}) {
896
+ const block = required(id);
897
+ const pairs = isPlainObject(response) ? headerPairs(response.headers) : null;
898
+ if (!pairs || !Number.isSafeInteger(response.status) || typeof response.bodyText !== "string") {
899
+ return refused("unreadable", "the answer is not {status, headers, bodyText}");
900
+ }
901
+ const facts = merchantSessionAnswerFacts(response);
902
+ if (response.status >= 300 && response.status < 400)
903
+ return refused("redirect", "the merchant answered with a redirect");
904
+ if (facts.challenge && facts.challenge !== "unknown")
905
+ return refused("challenge", `${facts.challenge} challenged the read`);
906
+ if (response.status !== 200)
907
+ return refused(facts.challenge ? "challenge" : "status", `the merchant answered ${response.status}`);
908
+ if (block.read.refuseCacheHit && cacheHit(pairs))
909
+ return refused("cache_hit", "the answer came from a cache");
910
+ if (byteLength(response.bodyText) > block.read.maxAnswerBytes)
911
+ return refused("too_large", "the answer is larger than the block reads");
912
+ return block.read.format === "json" ? jsonReading(block, response.bodyText, now) : htmlReading(block, response.bodyText);
913
+ }
914
+ function sameMerchantSnapshot(a, b) {
915
+ if (!isPlainObject(a) || !isPlainObject(b) || a.ok !== true || b.ok !== true)
916
+ return false;
917
+ return a.profile === b.profile && a.totalMinor === b.totalMinor && a.currency === b.currency && a.cartId === b.cartId && a.expiresAt === b.expiresAt && a.snapshotSha256 === b.snapshotSha256 && isPlainObject(a.digests) && isPlainObject(b.digests) && ["goods", "destination", "recipient"].every((name) => typeof a.digests[name] === "string" && a.digests[name] === b.digests[name]);
918
+ }
919
+ export { MERCHANT_SESSION_BOT_COOKIES, MERCHANT_SESSION_CACHE_FACT_CHARS, MERCHANT_SESSION_COOKIE_LIMITS, MERCHANT_SESSION_MODE, MERCHANT_SESSION_PROFILES, MERCHANT_SESSION_READ_LIMITS, MERCHANT_SESSION_RELAY_URL, MERCHANT_SESSION_SIGN_IN_COOKIES, MERCHANT_SESSION_USER_AGENT, merchantSessionAnswerFacts, merchantSessionCookies, merchantSessionHosts, merchantSessionProfile, merchantSessionProfileRefusal, merchantSessionRead, merchantSessionReading, sameMerchantSnapshot };