@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,4261 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: 82c93c756fd934cb52d2d868b73e546d741aebbe53f12d4c38c9173abf7a0c03
4
+ // src/json-path.js
5
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
6
+ function isPlainObject(v) {
7
+ return !!v && typeof v === "object" && !Array.isArray(v);
8
+ }
9
+ function jsonAt(root, path) {
10
+ let node = root;
11
+ for (const step of path) {
12
+ if (typeof step === "string") {
13
+ if (!isPlainObject(node) || FORBIDDEN_KEYS.has(step) || !Object.prototype.hasOwnProperty.call(node, step))
14
+ return { found: false };
15
+ }
16
+ else if (!Array.isArray(node) || !Number.isSafeInteger(step) || step < 0 || step >= node.length) {
17
+ return { found: false };
18
+ }
19
+ node = node[step];
20
+ }
21
+ return { found: true, value: node };
22
+ }
23
+ function roundTrips(parsed, body) {
24
+ try {
25
+ return JSON.stringify(parsed) === body;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ function sameKeyIgnoringCase(key, step) {
32
+ return !FORBIDDEN_KEYS.has(key) && (key.toLowerCase() === step.toLowerCase() || key.toUpperCase() === step.toUpperCase());
33
+ }
34
+ function valuesIgnoringCase(root, path) {
35
+ let nodes = [root];
36
+ for (const step of path) {
37
+ const next = [];
38
+ for (const node of nodes) {
39
+ if (typeof step === "number") {
40
+ if (Array.isArray(node) && Number.isSafeInteger(step) && step >= 0 && step < node.length)
41
+ next.push(node[step]);
42
+ }
43
+ else if (isPlainObject(node)) {
44
+ for (const key of Object.keys(node))
45
+ if (sameKeyIgnoringCase(key, step))
46
+ next.push(node[key]);
47
+ }
48
+ }
49
+ nodes = next;
50
+ }
51
+ return nodes;
52
+ }
53
+ function describePath(path) {
54
+ return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "") : "(root)";
55
+ }
56
+ // src/url-template.js
57
+ var PLACEHOLDER_VALUE = /^[A-Za-z0-9._~@+-]{1,256}$/;
58
+ var placeholderValue = (value) => PLACEHOLDER_VALUE.test(value) && value !== "." && !value.includes("..");
59
+ function queryPlaceholder(value) {
60
+ const mark = value.lastIndexOf(":");
61
+ return mark === -1 ? null : { prefix: value.slice(0, mark), name: value.slice(mark + 1) };
62
+ }
63
+ function pathPlaceholders(template, pathname) {
64
+ if (typeof template !== "string" || typeof pathname !== "string")
65
+ return null;
66
+ if (!template.startsWith("/") || !pathname.startsWith("/"))
67
+ return null;
68
+ const want = template.split("/");
69
+ const got = pathname.split("/");
70
+ if (want.length !== got.length)
71
+ return null;
72
+ const values = {};
73
+ for (const [i, segment] of want.entries()) {
74
+ if (segment.startsWith(":")) {
75
+ if (!placeholderValue(got[i]))
76
+ return null;
77
+ values[segment.slice(1)] = got[i];
78
+ }
79
+ else if (segment !== got[i]) {
80
+ return null;
81
+ }
82
+ }
83
+ return values;
84
+ }
85
+ function pathMatchesTemplate(template, pathname) {
86
+ return pathPlaceholders(template, pathname) !== null;
87
+ }
88
+ function queryPlaceholders(query, text) {
89
+ if (!query || typeof query !== "object" || Array.isArray(query))
90
+ return null;
91
+ const pairs = Object.entries(query);
92
+ if (pairs.length === 0)
93
+ return text === null ? {} : null;
94
+ if (typeof text !== "string")
95
+ return null;
96
+ const got = text.split("&");
97
+ if (got.length !== pairs.length)
98
+ return null;
99
+ const values = {};
100
+ for (const [i, [key, value]] of pairs.entries()) {
101
+ const mark = got[i].indexOf("=");
102
+ if (mark === -1 || got[i].slice(0, mark) !== key)
103
+ return null;
104
+ const written = got[i].slice(mark + 1);
105
+ if (typeof value !== "string")
106
+ return null;
107
+ const placeholder = queryPlaceholder(value);
108
+ if (placeholder) {
109
+ if (!written.startsWith(placeholder.prefix))
110
+ return null;
111
+ const rest = written.slice(placeholder.prefix.length);
112
+ if (!placeholderValue(rest))
113
+ return null;
114
+ values[placeholder.name] = rest;
115
+ }
116
+ else if (written !== value) {
117
+ return null;
118
+ }
119
+ }
120
+ return values;
121
+ }
122
+ function strictUrl(rawUrl) {
123
+ if (typeof rawUrl !== "string" || rawUrl.includes("#"))
124
+ return null;
125
+ let url;
126
+ try {
127
+ url = new URL(rawUrl);
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ if (url.href !== rawUrl || url.protocol !== "https:" || url.port || url.username || url.password)
133
+ return null;
134
+ return url;
135
+ }
136
+ function urlTemplateMatch(template, rawUrl) {
137
+ const url = strictUrl(rawUrl);
138
+ if (!url || !template || typeof template !== "object")
139
+ return null;
140
+ const fromPath = pathPlaceholders(template.path, url.pathname);
141
+ if (!fromPath)
142
+ return null;
143
+ const mark = rawUrl.indexOf("?");
144
+ const text = mark === -1 ? null : rawUrl.slice(mark + 1);
145
+ const queries = Array.isArray(template.queries) ? template.queries : [template.query];
146
+ for (const query of queries) {
147
+ const fromQuery = queryPlaceholders(query, text);
148
+ if (fromQuery)
149
+ return { origin: url.origin, placeholders: { ...fromPath, ...fromQuery } };
150
+ }
151
+ return null;
152
+ }
153
+ // src/substitution-primitives.js
154
+ var SubstitutionError = class extends Error {
155
+ constructor(message) {
156
+ super(message);
157
+ this.name = "SubstitutionError";
158
+ }
159
+ };
160
+ // src/deep-freeze.js
161
+ function deepFreeze(value) {
162
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
163
+ for (const child of Object.values(value))
164
+ deepFreeze(child);
165
+ Object.freeze(value);
166
+ }
167
+ return value;
168
+ }
169
+ // src/merchant-card-endpoint-documents.js
170
+ var HILTON_CREATE_RESERVATION_DOCUMENTS = /* @__PURE__ */ Object.freeze([
171
+ `mutation createReservation($language: String!, $input: ReservationInput!, $mfaInput: ReservationMFAInput, $scaInput: SCAInput, $enrollment: ReservationEnrollInput, $guestLocationCountry: String, $guestUpdate: ReservationGuestUpdateInput, $includePreArrivalOffers: Boolean! = false) {
172
+ createReservation(
173
+ language: $language
174
+ input: $input
175
+ mfaInput: $mfaInput
176
+ scaInput: $scaInput
177
+ enroll: $enrollment
178
+ guestUpdate: $guestUpdate
179
+ ) {
180
+ data {
181
+ digitalPayment {
182
+ action {
183
+ paymentData {
184
+ md
185
+ paReq
186
+ }
187
+ redirectMethod
188
+ redirectUrl
189
+ }
190
+ }
191
+ enroll {
192
+ hhonorsNumber
193
+ resourceAlreadyExists
194
+ }
195
+ mfa {
196
+ transactionState
197
+ }
198
+ sca {
199
+ ...RESERVATION_SCA_FRAGMENT
200
+ }
201
+ reservation {
202
+ ...RESERVATION_FRAGMENT
203
+ }
204
+ guestUpdate {
205
+ preferredAddress {
206
+ statusCode
207
+ message
208
+ }
209
+ updatePaymentMethod {
210
+ statusCode
211
+ message
212
+ }
213
+ }
214
+ }
215
+ error {
216
+ code
217
+ message
218
+ context
219
+ notifications {
220
+ code
221
+ fields
222
+ message
223
+ }
224
+ }
225
+ }
226
+ }
227
+ fragment RESERVATION_SCA_FRAGMENT on SCA {
228
+ scaId
229
+ md
230
+ paReq
231
+ url
232
+ termUrl
233
+ }
234
+ fragment RESERVATION_FRAGMENT on Reservation {
235
+ additionalContext {
236
+ bookedFor
237
+ profile
238
+ program
239
+ userPermission
240
+ userRole
241
+ }
242
+ addOnsResModifyEligible
243
+ adjoiningRoomStay
244
+ adjoiningRoomsFailure
245
+ arrivalDate
246
+ autoUpgradedStay
247
+ cancelEligible
248
+ certificates {
249
+ totalPoints
250
+ totalPointsFmt
251
+ }
252
+ clientAccounts {
253
+ clientId
254
+ clientType
255
+ clientName
256
+ programAccountId
257
+ }
258
+ comments {
259
+ generalInfo
260
+ }
261
+ confNumber
262
+ cost {
263
+ currency {
264
+ currencyCode
265
+ currencySymbol
266
+ description
267
+ }
268
+ roomRevUSD: totalAmountBeforeTax(currencyCode: "USD")
269
+ totalAddOnsAmount
270
+ totalAddOnsAmountFmt
271
+ totalAddOnsAmountUSD: totalAddOnsAmount(currencyCode: "USD")
272
+ totalAmountBeforeTax
273
+ totalAmountAfterTaxFmt: guestTotalCostAfterTaxFmt
274
+ totalAmountAfterTax: guestTotalCostAfterTax
275
+ totalAmountBeforeTaxFmt
276
+ totalServiceCharges
277
+ totalServiceChargesFmt
278
+ totalServiceChargesUSD: totalServiceCharges(currencyCode: "USD")
279
+ totalTaxes
280
+ totalTaxesFmt
281
+ totalTaxesUSD: totalTaxes(currencyCode: "USD")
282
+ }
283
+ cxlNumber
284
+ departureDate
285
+ disclaimer {
286
+ confirmableUpgradeReward
287
+ confirmableUpgradeReward_noTx: confirmableUpgradeReward
288
+ diamond48
289
+ diamond48_noTx: diamond48
290
+ fullPrePayNonRefundable
291
+ fullPrePayNonRefundable_noTx: fullPrePayNonRefundable
292
+ hgfConfirmation
293
+ hgfConfirmation_noTx: hgfConfirmation
294
+ hgvMaxTermsAndConditions
295
+ hgvMaxTermsAndConditions_noTx: hgvMaxTermsAndConditions
296
+ hhonorsCancellationCharges
297
+ hhonorsCancellationCharges_noTx: hhonorsCancellationCharges
298
+ hhonorsPointsDeduction
299
+ hhonorsPointsDeduction_noTx: hhonorsPointsDeduction
300
+ hhonorsPrintedConfirmation
301
+ hhonorsPrintedConfirmation_noTx: hhonorsPrintedConfirmation
302
+ lengthOfStay
303
+ lengthOfStay_noTx: lengthOfStay
304
+ rightToCancel
305
+ rightToCancel_noTx: rightToCancel
306
+ totalRate
307
+ totalRate_noTx: totalRate
308
+ teamMemberEligibility
309
+ teamMemberEligibility_noTx: teamMemberEligibility
310
+ vatCharge
311
+ vatCharge_noTx: vatCharge
312
+ cancellationRefundPolicy
313
+ cancellationRefundPolicy_noTx: cancellationRefundPolicy
314
+ }
315
+ guarantee {
316
+ depositRequired
317
+ nonRefundable
318
+ cxlPolicyCode
319
+ cxlPolicyDesc
320
+ cxlPolicyDesc_noTx: cxlPolicyDesc
321
+ guarPolicyCode
322
+ guarPolicyDesc
323
+ guarMethodCode
324
+ taxDisclaimers {
325
+ text
326
+ title
327
+ }
328
+ disclaimer {
329
+ legal
330
+ legal_noTx: legal
331
+ }
332
+ paymentCard {
333
+ cardCode
334
+ cardName
335
+ cardNumberMasked(format: masked)
336
+ cardExpireDate
337
+ cardPaymentType
338
+ expireDate: cardExpireDateFmt(format: "MMM yyyy")
339
+ expireDateFull: cardExpireDateFmt(format: "MMMM yyyy")
340
+ expired
341
+ }
342
+ deposit {
343
+ amount
344
+ }
345
+ taxDisclaimers {
346
+ text
347
+ title
348
+ }
349
+ }
350
+ guest {
351
+ guestId
352
+ tier
353
+ name {
354
+ firstName
355
+ lastName
356
+ nameFmt
357
+ }
358
+ emails {
359
+ emailAddressMasked
360
+ emailType
361
+ emailAddressHashed
362
+ }
363
+ addresses {
364
+ addressLine1
365
+ addressLine2
366
+ city
367
+ country
368
+ state
369
+ postalCode
370
+ addressFmt
371
+ addressType
372
+ }
373
+ hhonorsNumber
374
+ phones {
375
+ phoneNumberMasked(format: masked)
376
+ phoneType
377
+ }
378
+ }
379
+ guestBenefits {
380
+ foodAndBeverageCreditBenefit {
381
+ description
382
+ heading
383
+ linkLabel
384
+ linkUrl
385
+ }
386
+ }
387
+ lastUpdatedBooker {
388
+ guestId
389
+ programAccountId
390
+ }
391
+ modifyEligible
392
+ nor1Upgrade(provider: "DOHWR") {
393
+ content {
394
+ button
395
+ description
396
+ firstName
397
+ title
398
+ }
399
+ offerLink
400
+ requested
401
+ success
402
+ }
403
+ notifications {
404
+ subType
405
+ text
406
+ type
407
+ }
408
+ originalBooker {
409
+ guestId
410
+ programAccountId
411
+ }
412
+ propCode
413
+ requests {
414
+ specialRequests {
415
+ pets
416
+ servicePets
417
+ }
418
+ }
419
+ restricted
420
+ rooms {
421
+ gnrNumber
422
+ resCreateDateFmt(format: "yyyy-MM-dd")
423
+ addOns {
424
+ addOnCost {
425
+ amountAfterTax
426
+ amountAfterTaxFmt
427
+ }
428
+ addOnDetails {
429
+ addOnAvailType
430
+ addOnDescription
431
+ addOnCode
432
+ addOnName
433
+ addOnPricing
434
+ addOnUnit
435
+ addOnUnitCategory
436
+ amountAfterTax
437
+ amountAfterTaxFmt
438
+ averageDailyRate
439
+ averageDailyRateFmt
440
+ categoryCode
441
+ counts {
442
+ numAddOns
443
+ fulfillmentDate
444
+ rate
445
+ rateFmt
446
+ }
447
+ numAddOnDays
448
+ }
449
+ }
450
+ additionalNames {
451
+ firstName
452
+ lastName
453
+ }
454
+ certificates {
455
+ certType
456
+ certNumber
457
+ totalPoints
458
+ totalPointsFmt
459
+ }
460
+ offers {
461
+ offerSource
462
+ originalRoomTypeName
463
+ }
464
+ preArrivalOffers(guestLocationCountry: $guestLocationCountry) @include(if: $includePreArrivalOffers) {
465
+ offeredRoomTypes(
466
+ filter: {isUpsell: true}
467
+ sort: [{by: displayCost, order: asc}]
468
+ ) {
469
+ adaAccessibleRoom
470
+ cmaTotalPriceIndicator
471
+ displayCostFmtRnd: displayCostFmt(decimal: 0, strategy: ceiling)
472
+ displayCostHotelCurrency: displayCost
473
+ displayCostUsd: displayCost(currencyCode: "USD")
474
+ feeTransparencyIndicator
475
+ images(ratios: [threeByTwo], first: 1) {
476
+ altText
477
+ ratios {
478
+ url
479
+ }
480
+ }
481
+ offerDescription
482
+ roomRate {
483
+ amountAfterTax
484
+ amountAfterTaxFmt(decimal: 0, strategy: ceiling)
485
+ amountBeforeTaxFmt
486
+ averageRateFmt
487
+ fullAmountAfterTax: amountAfterTaxFmt
488
+ rateAmount
489
+ rateAmountFmt
490
+ rateChangeIndicator
491
+ rateDetails {
492
+ effectiveDateFmt(format: "medium")
493
+ effectiveDateFmtAda: effectiveDateFmt(format: "long")
494
+ rateAmount
495
+ rateAmountFmt
496
+ }
497
+ }
498
+ roomTypeCode
499
+ roomTypeName
500
+ }
501
+ offerId
502
+ offerType
503
+ originalRatePlanCode
504
+ originalRoomTypeCode
505
+ validUntil
506
+ }
507
+ numAdults
508
+ numChildren
509
+ childAges
510
+ autoUpgradedStay
511
+ isStaySmu
512
+ isStayUpsell
513
+ isStayUpsellOverAutoUpgrade
514
+ priorRoomType {
515
+ roomTypeName
516
+ }
517
+ cost {
518
+ currency {
519
+ currencyCode
520
+ currencySymbol
521
+ description
522
+ }
523
+ amountAfterTax: guestTotalCostAfterTax
524
+ amountAfterTaxFmt: guestTotalCostAfterTaxFmt
525
+ amountBeforeTax
526
+ amountBeforeTaxFmt
527
+ serviceChargeDetails
528
+ serviceChargeFeeType
529
+ serviceChargePeriods {
530
+ serviceCharges {
531
+ amount
532
+ amountFmt
533
+ description
534
+ }
535
+ }
536
+ rateAmountFmt
537
+ rateChanges
538
+ totalServiceCharges
539
+ totalServiceChargesFmt
540
+ totalTaxes
541
+ totalTaxesFmt
542
+ rateDetails(perNight: true) {
543
+ effectiveDateFmt(format: "medium")
544
+ effectiveDateFmtAda: effectiveDateFmt(format: "long")
545
+ rateAmount
546
+ rateAmountFmt
547
+ }
548
+ upgradedAmount
549
+ upgradedAmountFmt
550
+ }
551
+ guarantee {
552
+ cxlPolicyCode
553
+ cxlPolicyDesc
554
+ guarPolicyCode
555
+ guarPolicyDesc
556
+ }
557
+ numAdults
558
+ numChildren
559
+ ratePlan {
560
+ confidentialRates
561
+ hhonorsMembershipRequired
562
+ advancePurchase
563
+ promoCode
564
+ disclaimer {
565
+ diamond48
566
+ diamond48_noTx: diamond48
567
+ fullPrePayNonRefundable
568
+ fullPrePayNonRefundable_noTx: fullPrePayNonRefundable
569
+ hhonorsCancellationCharges
570
+ hhonorsCancellationCharges_noTx: hhonorsCancellationCharges
571
+ hhonorsPointsDeduction
572
+ hhonorsPointsDeduction_noTx: hhonorsPointsDeduction
573
+ hhonorsPrintedConfirmation
574
+ hhonorsPrintedConfirmation_noTx: hhonorsPrintedConfirmation
575
+ lengthOfStay
576
+ lengthOfStay_noTx: lengthOfStay
577
+ rightToCancel
578
+ rightToCancel_noTx: rightToCancel
579
+ totalRate
580
+ totalRate_noTx: totalRate
581
+ cancellationRefundPolicy
582
+ cancellationRefundPolicy_noTx: cancellationRefundPolicy
583
+ }
584
+ ratePlanCode
585
+ ratePlanName
586
+ ratePlanDesc
587
+ ratePlanDesc_noTx: ratePlanDesc
588
+ specialRateType
589
+ serviceChargesAndTaxesIncluded
590
+ }
591
+ roomType {
592
+ adaAccessibleRoom
593
+ carousel(first: 1) {
594
+ altText
595
+ variants(filter: {size: md}) {
596
+ url
597
+ }
598
+ }
599
+ roomTypeCode
600
+ roomTypeName
601
+ roomTypeDesc
602
+ roomOccupancy
603
+ }
604
+ }
605
+ roomUpgradesAvailable @include(if: $includePreArrivalOffers)
606
+ scaRequired
607
+ scaType
608
+ showAutoUpgradeIndicator
609
+ specialRateOptions {
610
+ corporateId
611
+ groupCode
612
+ hhonors
613
+ lta
614
+ pnd
615
+ promoCode
616
+ travelAgent
617
+ familyAndFriends
618
+ teamMember
619
+ owner
620
+ ownerHGV
621
+ smb
622
+ }
623
+ taxPeriods {
624
+ taxes {
625
+ description
626
+ }
627
+ }
628
+ totalNumAdults
629
+ totalNumChildren
630
+ totalNumRooms
631
+ unlimitedRewardsNumber
632
+ }`
633
+ ]);
634
+ // src/merchant-card-endpoint-profiles.js
635
+ var MERCHANT_CARD_ENDPOINT_PSP = "merchant_card_endpoint";
636
+ var MERCHANT_CARD_ENDPOINT_MODE = "card_endpoint";
637
+ var BROWSER_HEADERS = /* @__PURE__ */ Object.freeze(["user-agent", "accept", "accept-language", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform"]);
638
+ var NAVIGATION_HEADERS = /* @__PURE__ */ Object.freeze([
639
+ "content-type",
640
+ "cache-control",
641
+ "upgrade-insecure-requests",
642
+ "sec-fetch-dest",
643
+ "sec-fetch-mode",
644
+ "sec-fetch-site",
645
+ "sec-fetch-user"
646
+ ]);
647
+ var GUARANTEE_RATES = "a policy for guarantee rates: the hotel charges the card at the stay, by when a single-use card may have closed";
648
+ var MERCHANT_CARD_ENDPOINT_PROFILES = /* @__PURE__ */ deepFreeze([
649
+ {
650
+ id: "att_payment_info",
651
+ merchant: "AT&T",
652
+ status: "observe",
653
+ evidence: [
654
+ "att.com buy flow bundles (release/26.09.23.REL.COMMERCEUI), read Sep 25, 2026: with isUnifiedMfeEnabled on, Networker posts JSON with credentials: include to /msapi/checkoutexporch/v1/paymentInfo?cartId=<CHECKOUT_MFE_CART_ID>.",
655
+ "Headers: content-type application/json, appname and idpctx-appname onlinecart, X-CSRF-TOKEN from sessionStorage csrf-token.",
656
+ "Body: flat JSON with cardNumber, cvv, expDate (the month), expYear, name, zip, type, amount, cardSubType (the brand, derived from the PAN), and savedPaymentData, includeAutoPay, autoPayInfoList, leaseId, transactionId, statusCode on other flows.",
657
+ "The card-free order POST (/msapi/checkoutexporch/v1/post) carries the SMS PIN step-up."
658
+ ],
659
+ har: null,
660
+ checkout: "https://www.att.com",
661
+ checkout_frame: false,
662
+ frame: null,
663
+ endpoint: "https://www.att.com",
664
+ method: "POST",
665
+ path: "/msapi/checkoutexporch/v1/paymentInfo",
666
+ query: { cartId: ":cartId" },
667
+ navigation: null,
668
+ encoding: "json",
669
+ session: {
670
+ cookies: true,
671
+ cookie_names: null,
672
+ headers: [...BROWSER_HEADERS, "content-type", "x-csrf-token", "appname", "idpctx-appname"],
673
+ required: ["x-csrf-token", "appname"]
674
+ },
675
+ card: {
676
+ pan: { at: ["cardNumber"], format: "digits" },
677
+ cvv: { at: ["cvv"], format: "digits" },
678
+ exp_month: { at: ["expDate"], format: "as_typed" },
679
+ exp_year: { at: ["expYear"], format: "as_typed" },
680
+ expiry: null,
681
+ last4: null,
682
+ leading: null,
683
+ brands: [{ at: ["cardSubType"], map: null }],
684
+ bins: null
685
+ },
686
+ merchant_key: null,
687
+ requires: [{ at: ["type"], one_of: ["CREDIT", "DEBIT", "ANYCARD"] }],
688
+ refuse: [
689
+ { at: ["savedPaymentData"], when: "present" },
690
+ { at: ["autoPayInfoList"], when: "present" },
691
+ { at: ["leaseId"], when: "present" },
692
+ { at: ["transactionId"], when: "present" },
693
+ { at: ["statusCode"], when: "present" },
694
+ { at: ["includeAutoPay"], when: "true" }
695
+ ],
696
+ payment_method: null,
697
+ amount: { at: ["amount"], kind: "decimal_string" },
698
+ currency: "USD",
699
+ response: { headers: ["content-type", "set-cookie", "x-csrf-token"], withhold: [] },
700
+ resends: [],
701
+ challenge: null,
702
+ facts: null,
703
+ bootstrap: "/robots.txt",
704
+ timeout_ms: 3e4,
705
+ charge_timing: "deferred",
706
+ once_per: null,
707
+ pending: [
708
+ "card.brands: the cardSubType value for each brand",
709
+ "card.exp_month and card.exp_year: how many digits expDate and expYear carry",
710
+ "whether cartId also rides in the body"
711
+ ]
712
+ },
713
+ {
714
+ id: "italo_booking_credit_card",
715
+ merchant: "Italo",
716
+ status: "observe",
717
+ evidence: [
718
+ "biglietti.italotreno.com public bundles (API client chunk 901970b7, payment step chunk 6297), read Sep 25, 2026: axios posts JSON, without withCredentials, to https://api-biglietti.italotreno.com/api/v1/booking/{bookingId}/payments/credit-card.",
719
+ "Headers: Content-Type application/json, Authorization Bearer <BIGSessionToken>, X-BIG-working-session-id.",
720
+ "Body: accountName, accountNumber (PAN, spaces stripped), CVV, expirationMonth (two-digit string), expirationYear (four-digit number), termsAndConditions true, StoreCreditCard (saves the card), creditCardId (saved cards), paymentMethodCode (VI, MC, AX, DC, JC, OT from payment.fns.cardType).",
721
+ "The answer carries validationMetadata.PaReq, a Nexi 3DS HTML snippet the page injects; the 3DS return and the commit are card-free."
722
+ ],
723
+ har: null,
724
+ checkout: "https://biglietti.italotreno.com",
725
+ checkout_frame: false,
726
+ frame: null,
727
+ endpoint: "https://api-biglietti.italotreno.com",
728
+ method: "POST",
729
+ path: "/api/v1/booking/:bookingId/payments/credit-card",
730
+ query: {},
731
+ navigation: null,
732
+ encoding: "json",
733
+ session: {
734
+ cookies: false,
735
+ cookie_names: null,
736
+ headers: [...BROWSER_HEADERS, "content-type", "authorization", "x-big-working-session-id"],
737
+ required: ["authorization", "x-big-working-session-id"]
738
+ },
739
+ card: {
740
+ pan: { at: ["accountNumber"], format: "digits" },
741
+ cvv: { at: ["CVV"], format: "digits" },
742
+ exp_month: { at: ["expirationMonth"], format: "mm" },
743
+ exp_year: { at: ["expirationYear"], format: "yyyy_int" },
744
+ expiry: null,
745
+ last4: null,
746
+ leading: null,
747
+ brands: [{ at: ["paymentMethodCode"], map: { visa: "VI", mastercard: "MC" } }],
748
+ bins: null
749
+ },
750
+ merchant_key: null,
751
+ requires: [{ at: ["termsAndConditions"], equals: true }],
752
+ refuse: [
753
+ { at: ["StoreCreditCard"], when: "true" },
754
+ { at: ["creditCardId"], when: "present" }
755
+ ],
756
+ payment_method: null,
757
+ amount: null,
758
+ currency: "EUR",
759
+ response: { headers: ["content-type"], withhold: [] },
760
+ resends: [],
761
+ challenge: null,
762
+ facts: null,
763
+ bootstrap: "/robots.txt",
764
+ timeout_ms: 3e4,
765
+ charge_timing: "at_request",
766
+ once_per: null,
767
+ pending: [
768
+ "whether the validationMetadata.PaReq answer carries the card number",
769
+ "whether expire is sent absent or as null"
770
+ ]
771
+ },
772
+ {
773
+ id: "universal_orlando_guest_checkout",
774
+ merchant: "Universal Orlando Resort",
775
+ status: "observe",
776
+ evidence: [
777
+ "www.universalorlando.com/web-store bundle (main-YEGFGVHU.js payCard and buildPaymentUrl), read Sep 25, 2026: Angular HttpClient posts JSON, without withCredentials, to https://secure-api.universalparks.com/instances/{instance-id}/commerce-checkout for a guest checkout.",
778
+ "Headers: Authorization Bearer, X-IBM-Client-ID, WCToken, WCTrustedToken, x-instance-id, x-resort-area-code, x-source-Id, X-UNIWebService-ApiKey, -AppVersion, -Device, -Platform, Akamai-BM-Telemetry.",
779
+ "Body: order_id, currency_code USD, amount (a string), number, cvv, expiration {month, year} as integers, save_to_wallet, cardinal-3d-secure {reference_id, jwt, window_size}, billing and contact fields.",
780
+ "A Cardinal challenge answers 400 CARDINAL_3DS_MORE_INFO_REQUIRED and re-posts the card with the Cardinal jwt, which the refuse rules stop."
781
+ ],
782
+ har: null,
783
+ checkout: "https://www.universalorlando.com",
784
+ checkout_frame: false,
785
+ frame: null,
786
+ endpoint: "https://secure-api.universalparks.com",
787
+ method: "POST",
788
+ path: "/instances/:instanceId/commerce-checkout",
789
+ query: {},
790
+ navigation: null,
791
+ encoding: "json",
792
+ session: {
793
+ cookies: false,
794
+ cookie_names: null,
795
+ headers: [
796
+ ...BROWSER_HEADERS,
797
+ "content-type",
798
+ "authorization",
799
+ "x-ibm-client-id",
800
+ "wctoken",
801
+ "wctrustedtoken",
802
+ "x-instance-id",
803
+ "x-resort-area-code",
804
+ "x-source-id",
805
+ "x-uniwebservice-apikey",
806
+ "x-uniwebservice-appversion",
807
+ "x-uniwebservice-device",
808
+ "x-uniwebservice-platform",
809
+ "akamai-bm-telemetry"
810
+ ],
811
+ required: ["authorization", "x-ibm-client-id", "wctoken", "wctrustedtoken"]
812
+ },
813
+ card: {
814
+ pan: { at: ["number"], format: "digits" },
815
+ cvv: { at: ["cvv"], format: "digits" },
816
+ exp_month: { at: ["expiration", "month"], format: "m_int" },
817
+ exp_year: { at: ["expiration", "year"], format: "yyyy_int" },
818
+ expiry: null,
819
+ last4: null,
820
+ leading: null,
821
+ brands: [],
822
+ bins: null
823
+ },
824
+ merchant_key: null,
825
+ requires: [
826
+ { at: ["currency_code"], equals: "USD" },
827
+ { at: ["order_id"], present: true }
828
+ ],
829
+ refuse: [
830
+ { at: ["save_to_wallet"], when: "true" },
831
+ { at: ["cardinal-3d-secure", "jwt"], when: "nonempty" },
832
+ { at: ["cardinal-3d-secure", "reference_id"], when: "nonempty" }
833
+ ],
834
+ payment_method: null,
835
+ amount: { at: ["amount"], kind: "decimal_string" },
836
+ currency: { at: ["currency_code"] },
837
+ response: { headers: ["content-type"], withhold: [] },
838
+ resends: [],
839
+ challenge: null,
840
+ facts: null,
841
+ bootstrap: "/robots.txt",
842
+ timeout_ms: 3e4,
843
+ charge_timing: "at_request",
844
+ once_per: null,
845
+ pending: ["how long a captured akamai-bm-telemetry header stays accepted"]
846
+ },
847
+ {
848
+ id: "target_optical_pi_add",
849
+ merchant: "Target Optical",
850
+ status: "observe",
851
+ evidence: [
852
+ "www.targetoptical.com HCL Commerce storefront scripts (CheckoutPayments.populateProtocolData, wc.service.invoke over $.ajax), read Sep 25, 2026: a same-origin form POST to /AjaxOrderChangeServicePIAddTO with X-Requested-With XMLHttpRequest and the WCS and Akamai cookies.",
853
+ "Body: storeId, catalogId, langId, payMethodId (brand code), piAmount (order total), billing_address_id, account (the PAN), cc_cvc, expire_month, expire_year, cc_brand, cc_nameoncard, requesttype ajax, authToken (WCS anti-CSRF), forterToken; saveCreditCard=on or creditCardToUse for a saved card.",
854
+ "The card is checked or authorized at this step; the later PIUpdate, OrderPrepare and SubmitOrder calls are card-free."
855
+ ],
856
+ har: null,
857
+ checkout: "https://www.targetoptical.com",
858
+ checkout_frame: false,
859
+ frame: null,
860
+ endpoint: "https://www.targetoptical.com",
861
+ method: "POST",
862
+ path: "/AjaxOrderChangeServicePIAddTO",
863
+ query: {},
864
+ navigation: null,
865
+ encoding: "form",
866
+ session: {
867
+ cookies: true,
868
+ cookie_names: null,
869
+ headers: [...BROWSER_HEADERS, "content-type", "x-requested-with"],
870
+ required: ["x-requested-with"]
871
+ },
872
+ card: {
873
+ pan: { at: "account", format: "digits" },
874
+ cvv: { at: "cc_cvc", format: "digits" },
875
+ exp_month: { at: "expire_month", format: "as_typed" },
876
+ exp_year: { at: "expire_year", format: "as_typed" },
877
+ expiry: null,
878
+ last4: null,
879
+ leading: null,
880
+ brands: [{ at: "cc_brand", map: null }, { at: "payMethodId", map: null }],
881
+ bins: null
882
+ },
883
+ merchant_key: null,
884
+ requires: [
885
+ { at: "requesttype", equals: "ajax" },
886
+ { at: "authToken", present: true },
887
+ { at: "piAmount", present: true }
888
+ ],
889
+ refuse: [
890
+ { at: "saveCreditCard", when: "present" },
891
+ { at: "creditCardToUse", when: "present" }
892
+ ],
893
+ payment_method: null,
894
+ amount: { at: "piAmount", kind: "decimal_string" },
895
+ currency: "USD",
896
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
897
+ resends: [],
898
+ challenge: null,
899
+ facts: null,
900
+ bootstrap: "/robots.txt",
901
+ timeout_ms: 3e4,
902
+ charge_timing: "deferred",
903
+ once_per: null,
904
+ pending: [
905
+ "card.brands: the cc_brand and payMethodId values for each brand",
906
+ "card.exp_month and card.exp_year: how many digits expire_month and expire_year carry",
907
+ "whether the visible account1_1 field is also sent"
908
+ ]
909
+ },
910
+ {
911
+ id: "opus_signup_payment",
912
+ merchant: "Opus Virtual Offices",
913
+ status: "observe",
914
+ evidence: [
915
+ "www.opusvirtualoffices.com signup payment step, read Sep 25, 2026: fetch posts new FormData(form#checkout-form) as multipart/form-data with the browser boundary and credentials: include to /api/signup/process/payment/; the only set header is Accept application/json.",
916
+ "Card fields: ccname, ccnumber (digits), expdatem (01 to 12), expdatey (four digits), ccv, CreditCardType (1 Visa, 2 Mastercard, 3 Discover, 4 Amex; the page takes Mastercard for 51 to 55 only).",
917
+ "Order fields: uid (the signup id from step 1), prepaymny (the term, from the page radio buttons in payment chunk 0wj2kykjptexg: 1 monthly, or 3, 6 or 11 months prepaid), payment_option CreditCard or Wire; no amount (the server prices the uid and the term).",
918
+ "The page also sets signedcompany, bcphone and bstate, and copies utm_* cookies and visitor_id (as opusrc) into the form.",
919
+ "The subscription renews to the card on file, which a single-use card declines."
920
+ ],
921
+ har: null,
922
+ checkout: "https://www.opusvirtualoffices.com",
923
+ checkout_frame: false,
924
+ frame: null,
925
+ endpoint: "https://www.opusvirtualoffices.com",
926
+ method: "POST",
927
+ path: "/api/signup/process/payment/",
928
+ query: {},
929
+ navigation: null,
930
+ encoding: "multipart",
931
+ session: {
932
+ cookies: true,
933
+ cookie_names: null,
934
+ headers: [...BROWSER_HEADERS, "content-type"],
935
+ required: []
936
+ },
937
+ card: {
938
+ pan: { at: "ccnumber", format: "digits" },
939
+ cvv: { at: "ccv", format: "digits" },
940
+ exp_month: { at: "expdatem", format: "mm" },
941
+ exp_year: { at: "expdatey", format: "yyyy" },
942
+ expiry: null,
943
+ last4: null,
944
+ leading: null,
945
+ brands: [{ at: "CreditCardType", map: { visa: "1", mastercard: "2" } }],
946
+ bins: { mastercard: ["51", "52", "53", "54", "55"] }
947
+ },
948
+ merchant_key: null,
949
+ requires: [
950
+ { at: "payment_option", equals: "CreditCard" },
951
+ { at: "uid", present: true },
952
+ { at: "prepaymny", one_of: ["1", "3", "6", "11"] }
953
+ ],
954
+ refuse: [],
955
+ payment_method: null,
956
+ amount: null,
957
+ currency: "USD",
958
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
959
+ resends: [],
960
+ challenge: null,
961
+ facts: null,
962
+ bootstrap: "/robots.txt",
963
+ timeout_ms: 3e4,
964
+ charge_timing: "recurring",
965
+ once_per: null,
966
+ pending: [
967
+ "the amount each prepaymny term charges, so the approval can show what the chosen term costs",
968
+ "a renewal policy: each renewal charges the card on file with no approval of its own"
969
+ ]
970
+ },
971
+ {
972
+ id: "delta_payment_details",
973
+ merchant: "Delta Air Lines",
974
+ status: "observe",
975
+ evidence: [
976
+ "www.delta.com completepurchase 26.9.8 (main.js) and booking-config-2670.json, read Sep 25, 2026: the review-pay page posts JSON with withCredentials to APP_PAYMENT_SUBMIT, https://enterprisepayments-api.delta.com/efop/paymentDetails, after a Luhn check.",
977
+ "Headers (addBFFHeaders): X-Adapter web, Content-Type and Accept application/json, cartId, appId, channelId, purchasePath, and TransactionId, currencyCode, paymentReferenceId and skymilesNbr when set.",
978
+ 'Body (pipe paymentFormValuesToSaveCreditCardRequestJSON): {cardDetails: {billingAdr, cardHolderName, cardType, expirationMonthNum, expirationYearNum, lastFourDigits, paymentCardNum, paymentCardSecurityCode, paymentType "Credit Card", savedFopId, ...}, paymentType "Credit Card"}; the expiry pipe pads the month to two digits and writes the year as 20YY.',
979
+ "cardType is the form's creditCardCode; Delta's own eligible-fop-current.json lists VI, CA (Mastercard), AX, DS, JC, DC and TP.",
980
+ "The answer returns a paymentReferenceId that 3DS (/3ds/initialize, /3ds/authenticate) and the purchase use, with no card. Saving the card to the wallet is a separate request (/wallet/paymentDetailsForWallet) the device never sends."
981
+ ],
982
+ har: null,
983
+ checkout: "https://www.delta.com",
984
+ checkout_frame: false,
985
+ frame: null,
986
+ endpoint: "https://enterprisepayments-api.delta.com",
987
+ method: "POST",
988
+ path: "/efop/paymentDetails",
989
+ query: {},
990
+ navigation: null,
991
+ encoding: "json",
992
+ session: {
993
+ cookies: true,
994
+ cookie_names: null,
995
+ headers: [
996
+ ...BROWSER_HEADERS,
997
+ "content-type",
998
+ "x-adapter",
999
+ "cartid",
1000
+ "appid",
1001
+ "channelid",
1002
+ "purchasepath",
1003
+ "transactionid",
1004
+ "currencycode",
1005
+ "paymentreferenceid",
1006
+ "skymilesnbr"
1007
+ ],
1008
+ required: ["x-adapter", "cartid"]
1009
+ },
1010
+ card: {
1011
+ pan: { at: ["cardDetails", "paymentCardNum"], format: "digits" },
1012
+ cvv: { at: ["cardDetails", "paymentCardSecurityCode"], format: "digits" },
1013
+ exp_month: { at: ["cardDetails", "expirationMonthNum"], format: "mm" },
1014
+ exp_year: { at: ["cardDetails", "expirationYearNum"], format: "yyyy" },
1015
+ expiry: null,
1016
+ last4: { at: ["cardDetails", "lastFourDigits"], format: "digits" },
1017
+ leading: null,
1018
+ brands: [{ at: ["cardDetails", "cardType"], map: { visa: "VI", mastercard: "CA" } }],
1019
+ bins: null
1020
+ },
1021
+ merchant_key: null,
1022
+ requires: [
1023
+ { at: ["paymentType"], equals: "Credit Card" },
1024
+ { at: ["cardDetails", "paymentType"], equals: "Credit Card" }
1025
+ ],
1026
+ refuse: [{ at: ["cardDetails", "savedFopId"], when: "nonempty" }],
1027
+ payment_method: null,
1028
+ amount: null,
1029
+ currency: "USD",
1030
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1031
+ resends: [],
1032
+ challenge: null,
1033
+ facts: null,
1034
+ bootstrap: "/robots.txt",
1035
+ timeout_ms: 3e4,
1036
+ charge_timing: "deferred",
1037
+ once_per: null,
1038
+ pending: [
1039
+ "the currency: the body names none, and a point of sale outside the US prices in its own"
1040
+ ]
1041
+ },
1042
+ {
1043
+ id: "hilton_create_reservation",
1044
+ merchant: "Hilton",
1045
+ status: "observe",
1046
+ evidence: [
1047
+ "Hilton's reservation app dx-res-ui (www.hilton.com/dx_reservation_app): payment page chunks 70b8777e (Aug 24) and 8a158c4c (Sep 8), _app 6b38675c and c2e248fe (Sep 15), read from archive.org copies Sep 25, 2026. Every Hilton brand property books through it, Hilton San Francisco Union Square (SFOFHHH) included.",
1048
+ "The GraphQL transport fetches POST ${DX_GRAPHQL_CLIENT_URI}?${new URLSearchParams({appName, appVersion, operationName, originalOpName, bl})} with body JSON.stringify({query, operationName, variables}): https://www.hilton.com/graphql/customer, appVersion dx-res-ui:<build>, which URLSearchParams writes as dx-res-ui%3A<build>.",
1049
+ "Headers: Content-Type application/json, dx-platform (web when the page sets none), visitorid, hltclientmessageid, and Authorization Bearer only when there is no webGuestMetadata cookie; a same-origin fetch, so the first-party cookies go with it.",
1050
+ "createReservation variables: {language, input: {totalAmountAfterTax (a number), currencyCode, guarantee: {guarMethodCode, paymentCard: {cardCode (from the typed digits), cardExpireDate, cardNumber, cvvString (only when the rate sets cvvRequired), cardholder fields, paymentId (a saved Honors card)}, deposit}, ...}, scaInput, mfaInput, enrollment, guestUpdate: {preferredAddress, updatePaymentMethod (saves the card to the account)}}.",
1051
+ "SCA (PSD2 or Adyen Protect) sends scaInput {returnUrl, browserInfo} on the first call, answers error 897 and sends createReservation again with {scaId, md, paRes} and the card; MFA (errors 995, 996) sends it again with mfaInput. Both are refused here, so only stays that need neither are paid.",
1052
+ "The body's query is the createReservation operationString with its two fragments, which the transport sends unchanged (z = {query, operationName, variables}). requires pins it to the text the archived Sep 15 _app chunk c2e248fe builds (merchant-card-endpoint-documents.js), so an argument written inline in the document or a second createReservation under an alias is refused."
1053
+ ],
1054
+ har: null,
1055
+ checkout: "https://www.hilton.com",
1056
+ checkout_frame: false,
1057
+ frame: null,
1058
+ endpoint: "https://www.hilton.com",
1059
+ method: "POST",
1060
+ path: "/graphql/customer",
1061
+ query: { appName: "dx-res-ui", appVersion: "dx-res-ui%3A:build", operationName: "createReservation", originalOpName: "createReservation", bl: ":language" },
1062
+ navigation: null,
1063
+ encoding: "json",
1064
+ session: {
1065
+ cookies: true,
1066
+ cookie_names: null,
1067
+ headers: [...BROWSER_HEADERS, "content-type", "authorization", "dx-platform", "visitorid", "hltclientmessageid"],
1068
+ required: ["dx-platform"]
1069
+ },
1070
+ card: {
1071
+ pan: { at: ["variables", "input", "guarantee", "paymentCard", "cardNumber"], format: "digits" },
1072
+ cvv: { at: ["variables", "input", "guarantee", "paymentCard", "cvvString"], format: "digits_if_asked" },
1073
+ exp_month: null,
1074
+ exp_year: null,
1075
+ expiry: { at: ["variables", "input", "guarantee", "paymentCard", "cardExpireDate"], format: "as_typed" },
1076
+ last4: null,
1077
+ leading: null,
1078
+ brands: [{ at: ["variables", "input", "guarantee", "paymentCard", "cardCode"], map: null }],
1079
+ bins: null
1080
+ },
1081
+ merchant_key: null,
1082
+ requires: [
1083
+ { at: ["operationName"], equals: "createReservation" },
1084
+ { at: ["query"], one_of: [...HILTON_CREATE_RESERVATION_DOCUMENTS] }
1085
+ ],
1086
+ refuse: [
1087
+ { at: ["variables", "input", "guarantee", "paymentCard", "paymentId"], when: "present" },
1088
+ { at: ["variables", "scaInput"], when: "nonempty" },
1089
+ { at: ["variables", "mfaInput"], when: "nonempty" },
1090
+ { at: ["variables", "guestUpdate", "updatePaymentMethod"], when: "true" }
1091
+ ],
1092
+ payment_method: null,
1093
+ amount: { at: ["variables", "input", "totalAmountAfterTax"], kind: "decimal_number" },
1094
+ currency: { at: ["variables", "input", "currencyCode"] },
1095
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1096
+ resends: [],
1097
+ challenge: null,
1098
+ facts: null,
1099
+ bootstrap: "/robots.txt",
1100
+ timeout_ms: 3e4,
1101
+ charge_timing: "deferred",
1102
+ once_per: null,
1103
+ pending: [
1104
+ "card.expiry: the form cardExpireDate is sent in",
1105
+ "card.brands: the cardCode value for each brand",
1106
+ "the query: whether createReservation adds language or other variables to it",
1107
+ "the createReservation GraphQL document the live build sends, which requires pins (it holds the archived Sep 15 build's): a document that is not pinned can inline guestUpdate.updatePaymentMethod, scaInput or mfaInput, or alias createReservation twice",
1108
+ GUARANTEE_RATES
1109
+ ]
1110
+ },
1111
+ {
1112
+ id: "ihg_hotel_reservation",
1113
+ merchant: "IHG Hotels & Resorts",
1114
+ status: "observe",
1115
+ evidence: [
1116
+ "IHG pay SPA pay-ng v14.144.1 (chunks 5PVRTMTV, 7U5FHKAZ and AO2OILHC, archive.org copies of Jul 24 and 27, 2026), read Sep 25, 2026; the Sep 5 Common Crawl copy of find-hotels v14.147.0 still sends checkout there.",
1117
+ 'ReservationService.createReservation posts the first reservation, JSON with withCredentials, to https://apis.ihg.com/reservations/v3/hotels: {id, imageStatus "COMMIT", segments: [{payment: {paymentByPoints, paymentInformation: {paymentCard}, guaranteeType, billingAddress}, ...}], userProfiles, hotels}.',
1118
+ "paymentCard (getPaymentInformation): {cardCode, expireDate `${expirationMonth}${expirationYear}` (MMYY), cardHolderName, cvvNumber: {token: <CVV>}, cardNumber: {token: <PAN, spaces stripped>}}, plus 3dsData after a PSD2 check.",
1119
+ "Headers: x-ihg-api-key (public client key), content-type application/json; charset=UTF-8, ihg-language, X-IHG-SSO-TOKEN, x-accept-currency (USD), IHG-SellSource, Forter-Token; interceptors add IHG-SessionId and IHG-TransactionId.",
1120
+ "Hotels that set isPaymentServiceInformationRequired first send the PAN to /payments/threedsecure/v1/token and /paymentSetup and put the result in 3dsData, which is refused here; China UnionPay and PayPal set cupTokenId and paymentSource, also refused."
1121
+ ],
1122
+ har: null,
1123
+ checkout: "https://www.ihg.com",
1124
+ checkout_frame: false,
1125
+ frame: null,
1126
+ endpoint: "https://apis.ihg.com",
1127
+ method: "POST",
1128
+ path: "/reservations/v3/hotels",
1129
+ query: {},
1130
+ navigation: null,
1131
+ encoding: "json",
1132
+ session: {
1133
+ cookies: true,
1134
+ cookie_names: null,
1135
+ headers: [
1136
+ ...BROWSER_HEADERS,
1137
+ "content-type",
1138
+ "x-ihg-api-key",
1139
+ "ihg-language",
1140
+ "x-ihg-sso-token",
1141
+ "x-accept-currency",
1142
+ "ihg-sellsource",
1143
+ "forter-token",
1144
+ "ihg-sessionid",
1145
+ "ihg-transactionid"
1146
+ ],
1147
+ required: ["x-ihg-api-key"]
1148
+ },
1149
+ card: {
1150
+ pan: { at: ["segments", 0, "payment", "paymentInformation", "paymentCard", "cardNumber", "token"], format: "digits" },
1151
+ cvv: { at: ["segments", 0, "payment", "paymentInformation", "paymentCard", "cvvNumber", "token"], format: "digits" },
1152
+ exp_month: null,
1153
+ exp_year: null,
1154
+ expiry: { at: ["segments", 0, "payment", "paymentInformation", "paymentCard", "expireDate"], format: "mmyy" },
1155
+ last4: null,
1156
+ leading: null,
1157
+ brands: [{ at: ["segments", 0, "payment", "paymentInformation", "paymentCard", "cardCode"], map: null }],
1158
+ bins: null
1159
+ },
1160
+ merchant_key: null,
1161
+ requires: [
1162
+ { at: ["imageStatus"], equals: "COMMIT" },
1163
+ { at: ["segments", 0, "payment", "guaranteeType"], equals: "STANDARD_GUARANTEE" }
1164
+ ],
1165
+ refuse: [
1166
+ { at: ["segments", 0, "payment", "paymentInformation", "paymentCard", "3dsData"], when: "present" },
1167
+ { at: ["segments", 0, "payment", "paymentInformation", "cupTokenId"], when: "present" },
1168
+ { at: ["segments", 0, "payment", "paymentInformation", "paymentSource"], when: "present" }
1169
+ ],
1170
+ payment_method: null,
1171
+ amount: null,
1172
+ currency: "USD",
1173
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1174
+ resends: [],
1175
+ challenge: null,
1176
+ facts: null,
1177
+ bootstrap: "/robots.txt",
1178
+ timeout_ms: 3e4,
1179
+ charge_timing: "deferred",
1180
+ once_per: null,
1181
+ pending: [
1182
+ "card.brands: the cardCode value for each brand",
1183
+ "card.expiry: that expirationMonth is always two digits, so expireDate is MMYY",
1184
+ "the currency: a hotel outside the US charges in its own, which the body names only as hotels[0].bookingCurrency when it differs from the shopper's",
1185
+ GUARANTEE_RATES
1186
+ ]
1187
+ },
1188
+ {
1189
+ id: "aa_process_payment",
1190
+ merchant: "American Airlines",
1191
+ status: "observe",
1192
+ evidence: [
1193
+ "www.aa.com checkout SPA, builds 26.11.12-radio.0 (Sep 2 to 12) and 26.12.0 (Sep 15 to 20), read from archive.org copies Sep 25, 2026: CREDIT_CARD skips checkout/initiate and executePayment posts the request with httpClient.post(bffServiceUrl + 'processpayment'), same origin, bffServiceUrl /ecommerce/checkout-services/.",
1194
+ "Body (Oi, Ja, wi, then finalizePaymentRequest): {formOfPayments: [{creditCard: {creditCardType (derived from the typed digits), cardNumber, expirationMonth, expirationYear, cvvCode, nameOnCard}, billingAddress, saveCardAttributes, formOfPaymentType 'CREDIT_CARD'}], email, tripInsurance, isRecurringPurchaseSelected, responseVersion 'v2', cartId, userInteractionType, browserDetails}.",
1195
+ "Headers: Angular's X-XSRF-TOKEN from the XSRF-TOKEN cookie and an Authorization header from the interceptors; the aa.com cookies go with the same-origin request.",
1196
+ "THREE_DS_IDENTIFY_SHOPPER or THREE_DS_CHALLENGE_SHOPPER runs adyen-web createFromAction and posts the whole request again, card included, with threeDSStep and threeDSecureInfo; both are refused here, so only payments with no 3DS step are paid."
1197
+ ],
1198
+ har: null,
1199
+ checkout: "https://www.aa.com",
1200
+ checkout_frame: false,
1201
+ frame: null,
1202
+ endpoint: "https://www.aa.com",
1203
+ method: "POST",
1204
+ path: "/ecommerce/checkout-services/processpayment",
1205
+ query: {},
1206
+ navigation: null,
1207
+ encoding: "json",
1208
+ session: {
1209
+ cookies: true,
1210
+ cookie_names: null,
1211
+ headers: [...BROWSER_HEADERS, "content-type", "authorization", "x-xsrf-token"],
1212
+ required: []
1213
+ },
1214
+ card: {
1215
+ pan: { at: ["formOfPayments", 0, "creditCard", "cardNumber"], format: "digits" },
1216
+ cvv: { at: ["formOfPayments", 0, "creditCard", "cvvCode"], format: "digits" },
1217
+ exp_month: { at: ["formOfPayments", 0, "creditCard", "expirationMonth"], format: "as_typed" },
1218
+ exp_year: { at: ["formOfPayments", 0, "creditCard", "expirationYear"], format: "as_typed" },
1219
+ expiry: null,
1220
+ last4: null,
1221
+ leading: null,
1222
+ brands: [{ at: ["formOfPayments", 0, "creditCard", "creditCardType"], map: null }],
1223
+ bins: null
1224
+ },
1225
+ merchant_key: null,
1226
+ requires: [
1227
+ { at: ["formOfPayments", 0, "formOfPaymentType"], equals: "CREDIT_CARD" },
1228
+ { at: ["responseVersion"], equals: "v2" },
1229
+ { at: ["cartId"], present: true }
1230
+ ],
1231
+ refuse: [
1232
+ { at: ["formOfPayments", 0, "threeDSStep"], when: "present" },
1233
+ { at: ["formOfPayments", 0, "threeDSecureInfo"], when: "present" },
1234
+ { at: ["formOfPayments", 0, "saveCardAttributes", "saveCardToAccount"], when: "true" },
1235
+ { at: ["isRecurringPurchaseSelected"], when: "true" }
1236
+ ],
1237
+ payment_method: null,
1238
+ amount: null,
1239
+ currency: "USD",
1240
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1241
+ resends: [],
1242
+ challenge: null,
1243
+ facts: null,
1244
+ bootstrap: "/robots.txt",
1245
+ timeout_ms: 3e4,
1246
+ charge_timing: "at_request",
1247
+ once_per: null,
1248
+ pending: [
1249
+ "card.brands: the creditCardType value for each brand",
1250
+ "card.exp_month and card.exp_year: the form the expiry parser writes them in",
1251
+ "which of authorization and x-xsrf-token the call requires",
1252
+ "whether some shoppers check out on checkout.aa.com, which this profile does not cover"
1253
+ ]
1254
+ },
1255
+ {
1256
+ id: "economybookings_vault",
1257
+ merchant: "EconomyBookings",
1258
+ status: "observe",
1259
+ evidence: [
1260
+ "www.economybookings.com checkout (_app-5ad4b35169e00887, chunks 3450 and 1658, flights payment-ef0a155c) and the vault's own vault.js v1.2 and app.610e8fa01edad0052179.js, read Sep 25, 2026: the page mounts VaultFields, one iframe per card field on https://vault.economybookings.com, and submit() hands the card number frame the booking ids.",
1261
+ "That frame posts the card with jQuery $.ajax, contentType application/json, no cookies and no auth header, to https://vault-api.economybookings.com/api/v1/vault: {cardNumber (digits), cvv, ccExpMonth (parseInt), ccExpYear (parseInt of the two-digit year), cardType ('visa', 'mastercard' or 'amex', from the typed digits), cardHolder, affiliateId, reservationId, requestId, carId, extras, transactionId when resubmitting, ...}.",
1262
+ "The answer is {status, transactionId, redirectUrl}; AwaitingAddressData sends saveAdditionalData with no card and then the card again with the transactionId, which is refused here. The vault charges the processor the booking is routed to from its own servers."
1263
+ ],
1264
+ har: null,
1265
+ checkout: "https://www.economybookings.com",
1266
+ checkout_frame: false,
1267
+ frame: "https://vault.economybookings.com",
1268
+ endpoint: "https://vault-api.economybookings.com",
1269
+ method: "POST",
1270
+ path: "/api/v1/vault",
1271
+ query: {},
1272
+ navigation: null,
1273
+ encoding: "json",
1274
+ session: {
1275
+ cookies: false,
1276
+ cookie_names: null,
1277
+ headers: [...BROWSER_HEADERS, "content-type"],
1278
+ required: []
1279
+ },
1280
+ card: {
1281
+ pan: { at: ["cardNumber"], format: "digits" },
1282
+ cvv: { at: ["cvv"], format: "digits" },
1283
+ exp_month: { at: ["ccExpMonth"], format: "m_int" },
1284
+ exp_year: { at: ["ccExpYear"], format: "yy_int" },
1285
+ expiry: null,
1286
+ last4: null,
1287
+ leading: null,
1288
+ brands: [{ at: ["cardType"], map: { visa: "visa", mastercard: "mastercard" } }],
1289
+ bins: null
1290
+ },
1291
+ merchant_key: null,
1292
+ requires: [
1293
+ { at: ["affiliateId"], equals: "economybookings" },
1294
+ { at: ["reservationId"], present: true }
1295
+ ],
1296
+ refuse: [{ at: ["transactionId"], when: "nonempty" }],
1297
+ payment_method: null,
1298
+ amount: null,
1299
+ currency: "USD",
1300
+ response: { headers: ["content-type"], withhold: [] },
1301
+ resends: [],
1302
+ challenge: null,
1303
+ facts: null,
1304
+ bootstrap: "/robots.txt",
1305
+ timeout_ms: 3e4,
1306
+ charge_timing: "at_request",
1307
+ once_per: null,
1308
+ pending: [
1309
+ "whether an AwaitingAddressData answer already holds an authorization, since the resubmission it asks for is refused here",
1310
+ "the currency: the booking is priced in the shopper's chosen currency, which the body does not name"
1311
+ ]
1312
+ },
1313
+ {
1314
+ id: "synxis_the_verb_hotel",
1315
+ merchant: "The Verb Hotel",
1316
+ status: "observe",
1317
+ evidence: [
1318
+ "www.theverbhotel.com books on the SynXis Booking Engine (gc.synxis.com/rez.aspx?Hotel=61148 answers 302 to be.synxis.com), chain 16362, hotel 61148. SynXis build ad0d448a02da67422922 (client bundle and checkout chunk 6, deployed Sep 25, 2026), read Sep 25, 2026.",
1319
+ "The confirm loop sends each room through route updateReservation: POST https://be.synxis.com/gw/itinerary/v1/updateReservation, Content-Type application/json; charset=utf-8, Accept application/json,application/x-javascript, Context BE, Accept-Language, ActivityID (sbeSessionID), Authorization only for a signed-in member, credentials include (apisession, sbeSessionID, Imperva and AWSALB cookies).",
1320
+ 'Body: {version, Chain: {id}, Reservation: {action "Confirmed", Hotel: {id}, GuestList: [{Role "Primary", PaymentList: [{role "Primary", type "CreditCard", PaymentCard: {cardCode (VI, MC, AX, DS...), cardNumber, cardSecurityCode, expireDate "MM/YY", cardHolder}}], ...}], ...}, Source: {BrowserDetails}}; the expiry input is masked 99/99.',
1321
+ "The hotel's configuration shows no cvvCode field, so cardSecurityCode is sent empty; a later room sends the PaymentToken SynXis returned, and a PSD2 hotel sends consentToStoreCardData, both refused here.",
1322
+ "A RedirectHtml answer with VendorStatus Redirected sends the page through a vendor or 3DS step and then itinerary/v2/ReservationPaymentCallback, with no card."
1323
+ ],
1324
+ har: null,
1325
+ checkout: "https://be.synxis.com",
1326
+ checkout_frame: false,
1327
+ frame: null,
1328
+ endpoint: "https://be.synxis.com",
1329
+ method: "POST",
1330
+ path: "/gw/itinerary/v1/updateReservation",
1331
+ query: {},
1332
+ navigation: null,
1333
+ encoding: "json",
1334
+ session: {
1335
+ cookies: true,
1336
+ cookie_names: null,
1337
+ headers: [...BROWSER_HEADERS, "content-type", "context", "activityid", "authorization"],
1338
+ required: ["context", "activityid"]
1339
+ },
1340
+ card: {
1341
+ pan: { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "cardNumber"], format: "digits" },
1342
+ cvv: { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "cardSecurityCode"], format: "digits_if_asked" },
1343
+ exp_month: null,
1344
+ exp_year: null,
1345
+ expiry: { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "expireDate"], format: "mm/yy" },
1346
+ last4: null,
1347
+ leading: null,
1348
+ brands: [{ at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "cardCode"], map: { visa: "VI", mastercard: "MC" } }],
1349
+ bins: null
1350
+ },
1351
+ merchant_key: null,
1352
+ requires: [
1353
+ { at: ["Chain", "id"], one_of: [16362, "16362"] },
1354
+ { at: ["Reservation", "Hotel", "id"], one_of: [61148, "61148"] },
1355
+ { at: ["Reservation", "action"], equals: "Confirmed" },
1356
+ { at: ["Reservation", "GuestList", 0, "Role"], equals: "Primary" },
1357
+ { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "type"], equals: "CreditCard" }
1358
+ ],
1359
+ refuse: [
1360
+ { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "PaymentToken"], when: "nonempty" },
1361
+ { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "consentToStoreCardData"], when: "true" },
1362
+ { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "FintechPaymentProvider"], when: "nonempty" },
1363
+ { at: ["Reservation", "GuestList", 0, "PaymentList", 0, "PaymentCard", "InstallmentOptionId"], when: "nonempty" }
1364
+ ],
1365
+ payment_method: null,
1366
+ amount: null,
1367
+ currency: "USD",
1368
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1369
+ resends: [],
1370
+ challenge: null,
1371
+ facts: null,
1372
+ bootstrap: "/robots.txt",
1373
+ timeout_ms: 3e4,
1374
+ charge_timing: "deferred",
1375
+ once_per: null,
1376
+ pending: [
1377
+ "that the primary guest is GuestList[0] and its card PaymentList[0]",
1378
+ "whether the hotel's rates take a deposit at booking or only hold the card",
1379
+ GUARANTEE_RATES
1380
+ ]
1381
+ },
1382
+ {
1383
+ id: "runsignup_register_pay",
1384
+ merchant: "RunSignUp",
1385
+ status: "observe",
1386
+ evidence: [
1387
+ "RunSignUp charges cards through Adyen from its own servers (its posts of Mar 2021, Aug 2021, Nov 2022 and Apr 2025); the browser never calls a processor. Read Sep 25, 2026.",
1388
+ "The pay step is /Race/Register/?regToken=<session>&raceId=<id>&track=pay (46 archived captures of that URL; no capture of the page itself survives). RunSignUp's archived card form posts form.serialize() with jQuery $.ajax to its own URL: payment[cardNumber], payment[cardExpiresMonth] and payment[cardExpiresYear] (menus), payment[cvv] and billing fields.",
1389
+ "The Aug 2023 help screenshot shows the same form: card number, Month and Year menus, CVV, billing fields, Save my credit card, and Confirm Payment."
1390
+ ],
1391
+ har: null,
1392
+ checkout: "https://runsignup.com",
1393
+ checkout_frame: false,
1394
+ frame: null,
1395
+ endpoint: "https://runsignup.com",
1396
+ method: "POST",
1397
+ path: "/Race/Register/",
1398
+ query: { regToken: ":regToken", raceId: ":raceId", track: "pay" },
1399
+ navigation: null,
1400
+ encoding: "form",
1401
+ session: {
1402
+ cookies: true,
1403
+ cookie_names: null,
1404
+ headers: [...BROWSER_HEADERS, "content-type", "x-requested-with"],
1405
+ required: []
1406
+ },
1407
+ card: {
1408
+ pan: { at: "payment[cardNumber]", format: "digits" },
1409
+ cvv: { at: "payment[cvv]", format: "digits" },
1410
+ exp_month: { at: "payment[cardExpiresMonth]", format: "as_typed" },
1411
+ exp_year: { at: "payment[cardExpiresYear]", format: "as_typed" },
1412
+ expiry: null,
1413
+ last4: null,
1414
+ leading: null,
1415
+ brands: [],
1416
+ bins: null
1417
+ },
1418
+ merchant_key: null,
1419
+ requires: [],
1420
+ refuse: [],
1421
+ payment_method: null,
1422
+ amount: null,
1423
+ currency: "USD",
1424
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1425
+ resends: [],
1426
+ challenge: null,
1427
+ facts: null,
1428
+ bootstrap: "/robots.txt",
1429
+ timeout_ms: 3e4,
1430
+ charge_timing: "at_request",
1431
+ once_per: null,
1432
+ pending: [
1433
+ "that Confirm Payment posts the form as an XHR to its own pay step URL, not as a document",
1434
+ "the card field names today: the four above come from an archived form",
1435
+ "the name of the Save my credit card field, for a refuse rule",
1436
+ "card.exp_month and card.exp_year: the values the Month and Year menus send"
1437
+ ]
1438
+ },
1439
+ {
1440
+ id: "mindbody_explore_billing",
1441
+ merchant: "Mindbody",
1442
+ status: "observe",
1443
+ evidence: [
1444
+ "Mindbody Explore (www.mindbodyonline.com/explore, main.e3d3d61c built Sep 18, 2026, chunk 5.54c48f67), read Sep 25, 2026: the studios' own sites (Yoga Flow SF, Black Swan Yoga) sell through Mindbody, and on Explore a new card goes through addBillingInfo.",
1445
+ "addBillingInfo posts {data: {type 'userBilling', attributes}} (the form values and primaryCard false, keys in snake case) to https://prod-mkt-gateway.mindbody.io/v1/user/billing with Content-Type and Accept application/vnd.api+json, X-MB-APP-BUILD, X-MB-APP-NAME, X-MB-APP-VERSION, X-MB-USER-SESSION-ID, X-MB-ON-BEHALF-OF for a dependent, and Authorization Bearer for the signed-in person; a cross-site fetch, so no cookies.",
1446
+ "The card form (AddEditCardForm) takes card_number, cvv, expiration_month, expiration_year and a billing address. The order then pays with the stored card by id (/payments, /compute_total, /process with an SCA redirect), with no card.",
1447
+ "The studios' Branded Web widgets check out in CartV2 on go.mindbodyonline.com, whose add-card call is a Next.js server action no public capture shows; it is not this profile."
1448
+ ],
1449
+ har: null,
1450
+ checkout: "https://www.mindbodyonline.com",
1451
+ checkout_frame: false,
1452
+ frame: null,
1453
+ endpoint: "https://prod-mkt-gateway.mindbody.io",
1454
+ method: "POST",
1455
+ path: "/v1/user/billing",
1456
+ query: {},
1457
+ navigation: null,
1458
+ encoding: "json_api",
1459
+ session: {
1460
+ cookies: false,
1461
+ cookie_names: null,
1462
+ headers: [
1463
+ ...BROWSER_HEADERS,
1464
+ "content-type",
1465
+ "authorization",
1466
+ "x-mb-app-build",
1467
+ "x-mb-app-name",
1468
+ "x-mb-app-version",
1469
+ "x-mb-user-session-id",
1470
+ "x-mb-on-behalf-of"
1471
+ ],
1472
+ required: ["authorization", "x-mb-app-name", "x-mb-user-session-id"]
1473
+ },
1474
+ card: {
1475
+ pan: { at: ["data", "attributes", "card_number"], format: "digits" },
1476
+ cvv: { at: ["data", "attributes", "cvv"], format: "digits" },
1477
+ exp_month: { at: ["data", "attributes", "expiration_month"], format: "as_typed" },
1478
+ exp_year: { at: ["data", "attributes", "expiration_year"], format: "as_typed" },
1479
+ expiry: null,
1480
+ last4: null,
1481
+ leading: null,
1482
+ brands: [],
1483
+ bins: null
1484
+ },
1485
+ merchant_key: null,
1486
+ requires: [
1487
+ { at: ["data", "type"], equals: "userBilling" },
1488
+ { at: ["data", "attributes", "primary_card"], equals: false }
1489
+ ],
1490
+ refuse: [],
1491
+ payment_method: null,
1492
+ amount: null,
1493
+ currency: "USD",
1494
+ response: { headers: ["content-type"], withhold: [] },
1495
+ resends: [],
1496
+ challenge: null,
1497
+ facts: null,
1498
+ bootstrap: "/robots.txt",
1499
+ timeout_ms: 3e4,
1500
+ charge_timing: "deferred",
1501
+ once_per: null,
1502
+ pending: [
1503
+ "the attribute names addBillingInfo writes, and whether card_number is sent without the spaces the input shows",
1504
+ "card.exp_month and card.exp_year: the values the expiry menus send",
1505
+ "card on file: this request saves the card to the person's Mindbody account and the order charges it by id, which the card-on-file disclosure must cover"
1506
+ ]
1507
+ },
1508
+ {
1509
+ id: "lookfantastic_place_order",
1510
+ merchant: "LookFantastic",
1511
+ status: "observe",
1512
+ evidence: [
1513
+ "THG Ingenuity's shared checkout (checkout.lookfantastic.com, build main.212010dd79e9cb4669a1 of May 2026 and its 673 chunks; the Sep 2025 build has the same shape), read Sep 25, 2026.",
1514
+ "placeOrderViaApiPoller fetches POST /checkout-api/v2/order/place-order once with credentials same-origin and headers Content-Type application/json, sessionId and x-thg-client checkout-web-js, then polls /checkout-api/v2/order/status with no card.",
1515
+ "Body (getPlaceOrderBody): {fullName, payerName, contactNumber, deliveryAddress, emailAddress, optedIntoMarketing, payment: {creditCardNumber, nameOnCard, expirationMonth, expirationYear, cv2, method 'new-credit-card', billingAddress, consentToReuse}, residentId, hasDigitalConsent, deliverySelection, consentGiven, reCaptchaToken, giftCards}.",
1516
+ "Two other card paths reach the same endpoint with no card number: BR-DGE hosted fields (cardDataProvider BRIDGE and a token) and Adyen V2 (method adyen_v2); neither passes this profile's rules. Checkout.com runs only 3DS in the page."
1517
+ ],
1518
+ har: null,
1519
+ checkout: "https://checkout.lookfantastic.com",
1520
+ checkout_frame: false,
1521
+ frame: null,
1522
+ endpoint: "https://checkout.lookfantastic.com",
1523
+ method: "POST",
1524
+ path: "/checkout-api/v2/order/place-order",
1525
+ query: {},
1526
+ navigation: null,
1527
+ encoding: "json",
1528
+ session: {
1529
+ cookies: true,
1530
+ cookie_names: null,
1531
+ headers: [...BROWSER_HEADERS, "content-type", "x-thg-client", "sessionid"],
1532
+ required: ["x-thg-client", "sessionid"]
1533
+ },
1534
+ card: {
1535
+ pan: { at: ["payment", "creditCardNumber"], format: "digits" },
1536
+ cvv: { at: ["payment", "cv2"], format: "digits" },
1537
+ exp_month: { at: ["payment", "expirationMonth"], format: "as_typed" },
1538
+ exp_year: { at: ["payment", "expirationYear"], format: "as_typed" },
1539
+ expiry: null,
1540
+ last4: null,
1541
+ leading: null,
1542
+ brands: [],
1543
+ bins: null
1544
+ },
1545
+ merchant_key: null,
1546
+ requires: [{ at: ["payment", "method"], equals: "new-credit-card" }],
1547
+ refuse: [
1548
+ { at: ["payment", "consentToReuse"], when: "true" },
1549
+ { at: ["payment", "cardDataProvider"], when: "present" },
1550
+ { at: ["reCaptchaToken"], when: "nonempty" }
1551
+ ],
1552
+ payment_method: null,
1553
+ amount: null,
1554
+ currency: "GBP",
1555
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1556
+ resends: [],
1557
+ challenge: null,
1558
+ facts: null,
1559
+ bootstrap: "/robots.txt",
1560
+ timeout_ms: 3e4,
1561
+ charge_timing: "at_request",
1562
+ once_per: null,
1563
+ pending: [
1564
+ "which card path lookfantastic.com has on: its own card fields (this profile), BR-DGE hosted fields or Adyen V2",
1565
+ "card.exp_month and card.exp_year: the form expirationMonth and expirationYear are sent in",
1566
+ "the currency: the order is priced in the shopper's currency_V6 cookie, which the body does not name",
1567
+ "whether the site fills reCaptchaToken, and a policy for a captcha token in the card request"
1568
+ ]
1569
+ },
1570
+ // ── Merchants whose own page encrypts the card under the merchant's key ──────
1571
+ // Each ships in observe with pending items: until a HAR pins a key the device has
1572
+ // no key to encrypt under, and the requests around the card request (a second
1573
+ // token call, a page navigation, a re-sent ciphertext) are not in this mode yet.
1574
+ {
1575
+ id: "hyatt_card_token",
1576
+ merchant: "Hyatt",
1577
+ status: "observe",
1578
+ evidence: [
1579
+ "www.hyatt.com payment app bundles main.c587efa6.js (its SHA-384 is the 2026-03-03 payment page SRI hash) and main.8992684e.js (2026-08-27), read Sep 25, 2026 from web.archive.org: on mount the scapegoat client GETs /v1/cc/secure/key for {publicKey: PEM}; Book My Stay posts same-origin JSON with Content-Type and Accept application/json to /v1/cc/secure/tokens and, in parallel, /v1/cvv/secure/tokens.",
1580
+ "Card body: {pan, type, expYear, expMonth}. pan is node-forge encode64 of RSA-OAEP with md SHA-256 and mgf1 md SHA-1 (module wGyh) over the card number field, type is VS, MC, AX, DS, JC, DC or UP (map {AX: amex, DC: diners, DS: discover, JC: jcb, MC: mastercard, UP: unionpay, VS: visa}), expYear is 20YY and expMonth MM from the MM / YY field. The answer is {Token}.",
1581
+ "Then form.submit() navigates to /{locale}/book/makeReservation with token, cvv (the security code token), encrypted-card-number, credit-card-type, expiry-month and expiry-year. No third-party payment code and no frame load on the page (CSP default-src none, connect-src self)."
1582
+ ],
1583
+ har: null,
1584
+ checkout: "https://www.hyatt.com",
1585
+ checkout_frame: false,
1586
+ frame: null,
1587
+ endpoint: "https://www.hyatt.com",
1588
+ method: "POST",
1589
+ path: "/v1/cc/secure/tokens",
1590
+ query: {},
1591
+ navigation: null,
1592
+ encoding: "json",
1593
+ session: { cookies: true, cookie_names: null, headers: [...BROWSER_HEADERS, "content-type"], required: [] },
1594
+ card: {
1595
+ pan: { at: ["pan"], format: "encrypted", plaintext: "digits" },
1596
+ cvv: null,
1597
+ exp_month: { at: ["expMonth"], format: "mm" },
1598
+ exp_year: { at: ["expYear"], format: "yyyy" },
1599
+ expiry: null,
1600
+ last4: null,
1601
+ leading: null,
1602
+ brands: [{ at: ["type"], map: { visa: "VS", mastercard: "MC", amex: "AX", discover: "DS" } }],
1603
+ bins: null
1604
+ },
1605
+ merchant_key: { scheme: "rsa_oaep", hash: "SHA-256", mgf1: "SHA-1", kid_at: null, keys: [] },
1606
+ requires: [],
1607
+ refuse: [],
1608
+ payment_method: null,
1609
+ amount: null,
1610
+ currency: "USD",
1611
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1612
+ resends: [],
1613
+ challenge: null,
1614
+ facts: null,
1615
+ bootstrap: "/robots.txt",
1616
+ timeout_ms: 3e4,
1617
+ charge_timing: "deferred",
1618
+ once_per: null,
1619
+ pending: [
1620
+ "merchant_key.keys: the key /v1/cc/secure/key serves, pinned by the SHA-256 of its SubjectPublicKeyInfo",
1621
+ "the security code goes to /v1/cvv/secure/tokens in a request of its own (hyatt_cvv_token), sent beside this one, and one approval does not cover both yet",
1622
+ "makeReservation is a page navigation that carries encrypted-card-number, credit-card-type, expiry-month and expiry-year of the typed card, which this mode does not rewrite: whether Hyatt books from the token alone",
1623
+ "whether the card number is encrypted as typed, with spaces",
1624
+ "the booking currency: the request names none, so the profile takes stays priced in US dollars only"
1625
+ ]
1626
+ },
1627
+ {
1628
+ id: "hyatt_cvv_token",
1629
+ merchant: "Hyatt",
1630
+ status: "observe",
1631
+ evidence: [
1632
+ "The same Hyatt bundles: Book My Stay posts {cvv} to /v1/cvv/secure/tokens beside the card token call, cvv being the same RSA-OAEP (SHA-256, MGF1-SHA-1) base64 over the security code made on the field blur. The answer is {EncryptedCVV}, which the page puts in the book form as cvv.",
1633
+ "A saved-card booking posts the same body to the same path, for a card Hyatt already holds."
1634
+ ],
1635
+ har: null,
1636
+ checkout: "https://www.hyatt.com",
1637
+ checkout_frame: false,
1638
+ frame: null,
1639
+ endpoint: "https://www.hyatt.com",
1640
+ method: "POST",
1641
+ path: "/v1/cvv/secure/tokens",
1642
+ query: {},
1643
+ navigation: null,
1644
+ encoding: "json",
1645
+ session: { cookies: true, cookie_names: null, headers: [...BROWSER_HEADERS, "content-type"], required: [] },
1646
+ card: {
1647
+ pan: null,
1648
+ cvv: { at: ["cvv"], format: "encrypted", plaintext: "digits" },
1649
+ exp_month: null,
1650
+ exp_year: null,
1651
+ expiry: null,
1652
+ last4: null,
1653
+ leading: null,
1654
+ brands: [],
1655
+ bins: null
1656
+ },
1657
+ merchant_key: { scheme: "rsa_oaep", hash: "SHA-256", mgf1: "SHA-1", kid_at: null, keys: [] },
1658
+ requires: [],
1659
+ refuse: [],
1660
+ payment_method: null,
1661
+ amount: null,
1662
+ currency: "USD",
1663
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1664
+ resends: [],
1665
+ challenge: null,
1666
+ facts: null,
1667
+ bootstrap: "/robots.txt",
1668
+ timeout_ms: 3e4,
1669
+ charge_timing: "deferred",
1670
+ once_per: null,
1671
+ pending: [
1672
+ "merchant_key.keys: the key /v1/cc/secure/key serves, pinned by the SHA-256 of its SubjectPublicKeyInfo",
1673
+ "one approval for both token calls: this one carries the security code of the card hyatt_card_token carries",
1674
+ "a saved-card booking posts the same body here for a card Hyatt holds, so the request alone does not say whose code it is",
1675
+ "makeReservation is a page navigation this mode does not rewrite (see hyatt_card_token)"
1676
+ ]
1677
+ },
1678
+ {
1679
+ id: "lifemiles_process_payment",
1680
+ merchant: "LifeMiles",
1681
+ status: "observe",
1682
+ evidence: [
1683
+ "website-generic-checkout-ui build d31b93cfe651e5f66a0c on the public CloudFront CDN, read Sep 25, 2026: the card page www.lifemiles.com/checkout-lifemiles posts JSON through axios, without withCredentials, to https://api.lifemiles.com/svc/checkout-new-flow-process-payment (env-config confirmationGeneric), with Accept and Content-Type application/json, realm lifemiles and Authorization Bearer <session token>.",
1684
+ 'Body: {partner, language, processType, checkoutId, channel, country, stationId, ipAddress, currency, membershipNumber, deviceFingerPrintId, transactionId, selectedFlexCurve, saveCard, fop: {type: CC, amount: "0", unit: D, parameter: [{code, value}]}}. CCNUMCARD, CCCODCVV and CCFECEXP (the expiry as MMYYYY) are node-rsa pkcs1_oaep SHA-512 base64 under the cardKey in env-config.js; CCBIN is the first 7 digits of the number in the clear; CCFRANCHISE is VI, CA, AX, DC or DS (chunk 481 module 90156); CCTOKEN marks a saved card.',
1685
+ "cardKey is one RSA-2048 key (e = 65537) in the generic checkout, Club LifeMiles hub, member direct and air redemption env-config.js (last modified 2026-02-26). The 3DS calls after this request (payment/processor, payflow/3ds/enrollment and validate) carry only paymentId and checkoutId.",
1686
+ "Multiply Miles (partner mymlm) posts the typed number in the clear to /svc/credit-card-on-file-bin-not-saved before Pay, and Club LifeMiles (clubl) renews monthly, so neither partner is reviewed here."
1687
+ ],
1688
+ har: null,
1689
+ checkout: "https://www.lifemiles.com",
1690
+ checkout_frame: false,
1691
+ frame: null,
1692
+ endpoint: "https://api.lifemiles.com",
1693
+ method: "POST",
1694
+ path: "/svc/checkout-new-flow-process-payment",
1695
+ query: {},
1696
+ navigation: null,
1697
+ encoding: "json",
1698
+ session: { cookies: false, cookie_names: null, headers: [...BROWSER_HEADERS, "content-type", "authorization", "realm"], required: ["authorization", "realm"] },
1699
+ card: {
1700
+ pan: { at: ["fop", "parameter", { code: "CCNUMCARD" }, "value"], format: "encrypted", plaintext: "digits" },
1701
+ cvv: { at: ["fop", "parameter", { code: "CCCODCVV" }, "value"], format: "encrypted", plaintext: "digits" },
1702
+ exp_month: null,
1703
+ exp_year: null,
1704
+ expiry: { at: ["fop", "parameter", { code: "CCFECEXP" }, "value"], format: "encrypted", plaintext: "MMYYYY" },
1705
+ last4: null,
1706
+ leading: { at: ["fop", "parameter", { code: "CCBIN" }, "value"], digits: 7 },
1707
+ brands: [{ at: ["fop", "parameter", { code: "CCFRANCHISE" }, "value"], map: { visa: "VI", mastercard: "CA", amex: "AX", discover: "DS" } }],
1708
+ bins: null
1709
+ },
1710
+ merchant_key: {
1711
+ scheme: "rsa_oaep",
1712
+ hash: "SHA-512",
1713
+ mgf1: "SHA-512",
1714
+ kid_at: null,
1715
+ keys: [{
1716
+ kid: null,
1717
+ spki: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdiBpp7T3C7Ac6R+FTYimtCNzmQLRrCPVLrUZ2wjhjrGMsfeLt4EPbqw168mtJahzysvB+bjB5XZPiAci//nn+pDQK+x9iXS2Lb0RrTDwS8ey39THybEGw0295g4kQO8ZAw5uc9p+hkVcKwgOIkG5fe8GyQlFMhoySQhj/L75BGsbIbojybsW9AJ9K1KKy0CGCiCre6L5x9kuKfkFR0Q7khWTncmKenbkPOvgQXCg5r4+6Nk15imaiR4O1HKHInkPRSdu19kBYBqVXxXm3lYtY3NCOtPS0irRrAdi4UfT46/81ugvEm7y2xuFv32MSJIAzpAC4jHbB+HUD4sJQpVsQIDAQAB",
1718
+ sha256: "dc2a6ee45bf21c652dc3286244fa80e455200181e71a6b34d9d9a525a4ecfe79"
1719
+ }]
1720
+ },
1721
+ requires: [
1722
+ { at: ["fop", "type"], equals: "CC" },
1723
+ { at: ["partner"], one_of: ["lmbex", "avanc", "buylm", "tsflm"] }
1724
+ ],
1725
+ refuse: [
1726
+ { at: ["saveCard"], when: "true" },
1727
+ { at: ["fop", "parameter", { code: "CCTOKEN" }], when: "present" }
1728
+ ],
1729
+ payment_method: null,
1730
+ amount: null,
1731
+ currency: { at: ["currency"] },
1732
+ response: { headers: ["content-type"], withhold: [] },
1733
+ resends: [],
1734
+ challenge: null,
1735
+ facts: null,
1736
+ bootstrap: "/robots.txt",
1737
+ timeout_ms: 3e4,
1738
+ charge_timing: "at_request",
1739
+ once_per: null,
1740
+ pending: [
1741
+ "merchant_key.keys: that api.lifemiles.com decrypts with the cardKey its env-config.js carries (the key pinned here), which only a HAR or a person-run purchase shows",
1742
+ "whether the Akamai bot defense in front of api.lifemiles.com takes the relay's request (a plain GET to www.lifemiles.com answered 403 from Akamai)"
1743
+ ]
1744
+ },
1745
+ {
1746
+ id: "southwest_booking_confirmation",
1747
+ merchant: "Southwest Airlines",
1748
+ status: "observe",
1749
+ evidence: [
1750
+ "www.southwest.com air-booking-v2 14.0.3 bundles and bootstrap data.js, read Sep 25, 2026: processBookingPurchase posts same-origin JSON through use-http to /api/air-booking/v1/air-booking/page/air/booking/confirmation (points, companion and certificate bookings use /confirmation-secure), with x-api-key, x-app-id, x-app-version, x-channel-id and x-user-experience-id.",
1751
+ "The body carries creditCard {cityTown, country, countryCode, encryptedCardNumber, expiration, firstNameOnCard, lastNameOnCard, phoneNumber, provinceRegion, state, streetAddress, streetAddressSecond, zipCode, zipCodeRequired, creditCardType, description, descriptionEnabled, primary, securityCode} and paymentMethodSelected creditcard. expiration is the form value with / made -, securityCode is in the clear.",
1752
+ "encryptedCardNumber is jose EncryptJWT({creditCardNumbers: {cardNumber1: number}}) with protected header {alg, enc: A256CBC-HS512, kid, typ: JWT}, under the key with use enc from the cyberJwks JWKS (https://jwksapi.prod0.tok.jwks-prod.prod.southwest.com/jwks), alg taken from the JWK. creditCardType is VISA, RRVISA, MASTERCARD, AMEX, DISCOVER, DINERS or UATP."
1753
+ ],
1754
+ har: null,
1755
+ checkout: "https://www.southwest.com",
1756
+ checkout_frame: false,
1757
+ frame: null,
1758
+ endpoint: "https://www.southwest.com",
1759
+ method: "POST",
1760
+ path: "/api/air-booking/v1/air-booking/page/air/booking/confirmation",
1761
+ query: {},
1762
+ navigation: null,
1763
+ encoding: "json",
1764
+ session: {
1765
+ cookies: true,
1766
+ cookie_names: null,
1767
+ headers: [...BROWSER_HEADERS, "content-type", "x-api-key", "x-app-id", "x-app-version", "x-channel-id", "x-user-experience-id"],
1768
+ required: ["x-api-key", "x-channel-id"]
1769
+ },
1770
+ card: {
1771
+ pan: { at: ["creditCard", "encryptedCardNumber"], format: "encrypted", plaintext: { claims: ["creditCardNumbers", "cardNumber1"] } },
1772
+ cvv: { at: ["creditCard", "securityCode"], format: "digits" },
1773
+ exp_month: null,
1774
+ exp_year: null,
1775
+ expiry: { at: ["creditCard", "expiration"], format: "MM-YY" },
1776
+ last4: null,
1777
+ leading: null,
1778
+ brands: [{ at: ["creditCard", "creditCardType"], map: { visa: "VISA", mastercard: "MASTERCARD", amex: "AMEX", discover: "DISCOVER" } }],
1779
+ bins: null
1780
+ },
1781
+ merchant_key: { scheme: "jwe", alg: null, enc: "A256CBC-HS512", header: { typ: "JWT" }, keys: [] },
1782
+ requires: [{ at: ["paymentMethodSelected"], equals: "creditcard" }],
1783
+ refuse: [
1784
+ { at: ["creditCard", "descriptionEnabled"], when: "true" },
1785
+ { at: ["creditCard", "primary"], when: "true" },
1786
+ { at: ["savedCreditCardSelected"], when: "true" },
1787
+ { at: ["savedCreditCardToken"], when: "nonempty" },
1788
+ { at: ["selectedCardInfo"], when: "nonempty" },
1789
+ { at: ["fundsCoverTotalFlight"], when: "true" }
1790
+ ],
1791
+ payment_method: null,
1792
+ amount: null,
1793
+ currency: "USD",
1794
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1795
+ resends: [],
1796
+ challenge: null,
1797
+ facts: null,
1798
+ bootstrap: "/robots.txt",
1799
+ timeout_ms: 3e4,
1800
+ charge_timing: "at_request",
1801
+ once_per: null,
1802
+ pending: [
1803
+ "merchant_key.keys: the key the cyberJwks JWKS marks use enc, pinned with its kid by the SHA-256 of its SubjectPublicKeyInfo",
1804
+ "merchant_key.alg: the alg the JWK names, which the page writes into the protected header",
1805
+ "card.expiry: whether expiration carries MM-YY or MM-YYYY",
1806
+ "card.brands: whether the form sends RRVISA for a Rapid Rewards Visa, where the map writes VISA",
1807
+ "a trip insurance purchase sends the same encrypted number to /api/payments/v2/fund-tokens, a second card request this profile does not take",
1808
+ "the page re-encrypts and posts again when the answer names an expired key, which asks for a second approval"
1809
+ ]
1810
+ },
1811
+ {
1812
+ id: "united_card_token",
1813
+ merchant: "United Airlines",
1814
+ status: "observe",
1815
+ evidence: [
1816
+ "www.united.com main.4c89ef80de310ae7ad9d.js (the build a public urlscan.io scan of the live home page loaded on 2026-09-17) and its chunks 1402 and 91359, read Sep 25, 2026 from web.archive.org: for a guest the checkout page GETs /api/User/PublicKeyNonPCI and keeps the Keys[] entry with a PEM PublicKey and its Kid.",
1817
+ "On Buy now getCardTokenSaga posts JSON to /api/user/accountNumberTokenOAEP with X-Authorization-api bearer and Accept-Language: {ProfileCreditCard: {AccountNumberEncrypted, Name, Code, ExpirationDate: MM/YY, SecurityCode}, Kid}. AccountNumberEncrypted is base64 of RSA-OAEP with md SHA-1 (vendor module 53965) over the card number without spaces; SecurityCode is in the clear; Code is the card type GetCreditCardType answered. The answer carries AccountNumberToken and PersistentToken.",
1818
+ "The purchase saga waits for that token, then posts CompletePurchase with AccountNumberEncrypted again, AccountNumberLastFourDigits and the tokens; Lookup3DS sends the ciphertext again. GetCreditCardType and GetSecureCardToken receive the first 10 digits of the typed number."
1819
+ ],
1820
+ har: null,
1821
+ checkout: "https://www.united.com",
1822
+ checkout_frame: false,
1823
+ frame: null,
1824
+ endpoint: "https://www.united.com",
1825
+ method: "POST",
1826
+ path: "/api/user/accountNumberTokenOAEP",
1827
+ query: {},
1828
+ navigation: null,
1829
+ encoding: "json",
1830
+ session: { cookies: true, cookie_names: null, headers: [...BROWSER_HEADERS, "content-type", "x-authorization-api"], required: ["x-authorization-api"] },
1831
+ card: {
1832
+ pan: { at: ["ProfileCreditCard", "AccountNumberEncrypted"], format: "encrypted", plaintext: "digits" },
1833
+ cvv: { at: ["ProfileCreditCard", "SecurityCode"], format: "digits" },
1834
+ exp_month: null,
1835
+ exp_year: null,
1836
+ expiry: { at: ["ProfileCreditCard", "ExpirationDate"], format: "MM/YY" },
1837
+ last4: null,
1838
+ leading: null,
1839
+ brands: [{ at: ["ProfileCreditCard", "Code"], map: null }],
1840
+ bins: null
1841
+ },
1842
+ merchant_key: { scheme: "rsa_oaep", hash: "SHA-1", mgf1: "SHA-1", kid_at: ["Kid"], keys: [] },
1843
+ requires: [],
1844
+ refuse: [],
1845
+ payment_method: null,
1846
+ amount: null,
1847
+ currency: "USD",
1848
+ response: { headers: ["content-type", "set-cookie"], withhold: [] },
1849
+ resends: [],
1850
+ challenge: null,
1851
+ facts: null,
1852
+ bootstrap: "/robots.txt",
1853
+ timeout_ms: 3e4,
1854
+ charge_timing: "deferred",
1855
+ once_per: null,
1856
+ pending: [
1857
+ "merchant_key.keys: the key /api/User/PublicKeyNonPCI serves and its Kid, pinned by the SHA-256 of its SubjectPublicKeyInfo",
1858
+ "card.brands: the Code GetCreditCardType answers for each network",
1859
+ "card.expiry: whether ExpirationDate carries MM/YY or MM/YYYY",
1860
+ "CompletePurchase and Lookup3DS send AccountNumberEncrypted again with the typed card, and its last four digits, which this mode does not rewrite: whether United charges from AccountNumberToken alone",
1861
+ "GetCreditCardType and GetSecureCardToken receive the first 10 digits of the typed card, so the card type and the Cardinal token describe it and not the real card"
1862
+ ]
1863
+ },
1864
+ // ── Document form navigations: the page submits a form that navigates ──────
1865
+ {
1866
+ id: "myrec_athens_clarke_payment",
1867
+ merchant: "Athens-Clarke County Leisure Services",
1868
+ status: "observe",
1869
+ evidence: [
1870
+ `secure.myrecdept.com/auth/default.aspx?did=54048 (MyRec), archive.org captures of 2026-03-20 (200, "Online Payment for Athens-Clarke County Leisure Services") and 2026-05-29, read Sep 25, 2026: an ASP.NET WebForms page whose form aspnetForm posts back to ./default.aspx?did=54048&authkey=<a GUID per checkout> as a full document navigation; the card fields and the submit button sit outside the page's only UpdatePanel, and no async trigger is registered.`,
1871
+ "Card fields: ctl00$Content$txtAccountNumber (maxlength 16), ctl00$Content$txtCvvNumber (maxlength 4), ctl00$Content$lstExpMonth (01 to 12) and ctl00$Content$lstExpYear (four digits), beside the billing name, address, email and phone, __EVENTTARGET, __VIEWSTATE, __VIEWSTATEGENERATOR, __EVENTVALIDATION, and the button that posted: ctl00$Content$btnSubmitPayment, or ctl00$Content$btnCancel.",
1872
+ "A visible reCAPTCHA v2 checkbox sits above the submit button on every capture since 2025-09, so a solved g-recaptcha-response rides in the same body. The page expires after 15 minutes.",
1873
+ "MyRec charges the department's gateway (Authorize.Net here, by the page path) from its own servers; the browser never contacts a processor. The same page serves every MyRec department, each under its own did."
1874
+ ],
1875
+ har: null,
1876
+ checkout: "https://secure.myrecdept.com",
1877
+ checkout_frame: false,
1878
+ frame: null,
1879
+ endpoint: "https://secure.myrecdept.com",
1880
+ method: "POST",
1881
+ path: "/auth/default.aspx",
1882
+ query: { did: "54048", authkey: ":authkey" },
1883
+ navigation: "top",
1884
+ encoding: "form",
1885
+ session: {
1886
+ cookies: true,
1887
+ cookie_names: ["ASP.NET_SessionId"],
1888
+ headers: [...BROWSER_HEADERS, ...NAVIGATION_HEADERS],
1889
+ required: []
1890
+ },
1891
+ card: {
1892
+ pan: { at: "ctl00$Content$txtAccountNumber", format: "digits" },
1893
+ cvv: { at: "ctl00$Content$txtCvvNumber", format: "digits" },
1894
+ exp_month: { at: "ctl00$Content$lstExpMonth", format: "mm" },
1895
+ exp_year: { at: "ctl00$Content$lstExpYear", format: "yyyy" },
1896
+ expiry: null,
1897
+ last4: null,
1898
+ leading: null,
1899
+ brands: [],
1900
+ bins: null
1901
+ },
1902
+ merchant_key: null,
1903
+ requires: [
1904
+ { at: "ctl00$Content$btnSubmitPayment", present: true },
1905
+ { at: "__EVENTTARGET", equals: "" },
1906
+ { at: "__VIEWSTATE", present: true },
1907
+ { at: "__EVENTVALIDATION", present: true }
1908
+ ],
1909
+ refuse: [
1910
+ { at: "ctl00$Content$btnCancel", when: "present" },
1911
+ { at: "g-recaptcha-response", when: "nonempty" }
1912
+ ],
1913
+ payment_method: null,
1914
+ amount: null,
1915
+ currency: "USD",
1916
+ response: { headers: ["content-type", "location", "set-cookie"], withhold: [] },
1917
+ resends: [],
1918
+ challenge: null,
1919
+ facts: null,
1920
+ bootstrap: "/robots.txt",
1921
+ timeout_ms: 6e4,
1922
+ charge_timing: "at_request",
1923
+ once_per: { query: "authkey" },
1924
+ pending: [
1925
+ "a policy for the solved reCAPTCHA token the page sends in the same body (refused until then), and whether MyRec checks it against the address that posts",
1926
+ "which cookies the postback needs: ASP.NET_SessionId, another, or none",
1927
+ "the answer to the card postback (a redirect, a processing page or the form again) and how long it takes: the page says processing can take 1 to 2 minutes, longer than the 60 second session",
1928
+ "that the bootstrap path answers without a server error"
1929
+ ]
1930
+ },
1931
+ {
1932
+ id: "ladot_etims_guest_permit",
1933
+ merchant: "City of Los Angeles guest parking permits",
1934
+ status: "observe",
1935
+ evidence: [
1936
+ "wmq.etimspayments.com/pbw/include/la/rppguestinput.jsp (Conduent eTIMS Pay By Web), fetched Sep 25, 2026: the guest permit account check posts to /pbw/rppGuestInputAction.doh with hidden clientcode=17 and clientAccount=9, the Los Angeles client account that citations and permit renewals share. No payment SDK or processor frame on any Los Angeles page.",
1937
+ "shared/js/common.js (live, and the same function in the 2005 to 2012 archived copies) clears document.submitForm.cardNumber: eTIMS collects the card in its own form, submitForm, field cardNumber.",
1938
+ "Archived Los Angeles visits (2024-12-04, 2025-09-15) run input.jsp, inputAction.doh, virtualMerchantPBWSubmitPage.doh and confirmAction.doh; Arlington County's renewal journey on the same template runs rppVirtualMerchantSubmitAction.doh, the permit action this profile pins by analogy. eTIMS then charges Elavon Virtual Merchant from its own server.",
1939
+ "Every eTIMS form posts as a document navigation, bound to the HttpOnly JSESSIONID cookie each page sets."
1940
+ ],
1941
+ har: null,
1942
+ checkout: "https://wmq.etimspayments.com",
1943
+ checkout_frame: false,
1944
+ frame: null,
1945
+ endpoint: "https://wmq.etimspayments.com",
1946
+ method: "POST",
1947
+ path: "/pbw/rppVirtualMerchantSubmitAction.doh",
1948
+ query: {},
1949
+ navigation: "top",
1950
+ encoding: "form",
1951
+ session: {
1952
+ cookies: true,
1953
+ cookie_names: ["JSESSIONID"],
1954
+ headers: [...BROWSER_HEADERS, ...NAVIGATION_HEADERS],
1955
+ required: []
1956
+ },
1957
+ card: {
1958
+ pan: { at: "cardNumber", format: "digits" },
1959
+ cvv: { at: "cvv", format: "digits" },
1960
+ exp_month: { at: "expMonth", format: "as_typed" },
1961
+ exp_year: { at: "expYear", format: "as_typed" },
1962
+ expiry: null,
1963
+ last4: null,
1964
+ leading: null,
1965
+ brands: [],
1966
+ bins: null
1967
+ },
1968
+ merchant_key: null,
1969
+ requires: [{ at: "clientcode", equals: "17" }],
1970
+ refuse: [],
1971
+ payment_method: null,
1972
+ amount: null,
1973
+ currency: "USD",
1974
+ response: { headers: ["content-type", "location", "set-cookie"], withhold: [] },
1975
+ resends: [],
1976
+ challenge: null,
1977
+ facts: null,
1978
+ bootstrap: "/robots.txt",
1979
+ timeout_ms: 6e4,
1980
+ charge_timing: "at_request",
1981
+ once_per: { cookie: "JSESSIONID" },
1982
+ pending: [
1983
+ "the guest permit card action: rppVirtualMerchantSubmitAction.doh is the renewal action on the same template, and the guest flow's own is not archived",
1984
+ "card.cvv, card.exp_month and card.exp_year: only cardNumber has been seen, so these names stand in until a HAR shows the real ones and the expiry's digit counts",
1985
+ "whether the card form carries clientcode 17, and which of its buttons post to the same action",
1986
+ "which request carries the card: the submit action or the confirmAction.doh step after it",
1987
+ "that the Virtual Merchant step does not hand the browser to Converge's hosted page, which would make this Converge processor work instead",
1988
+ "the answer to the card post, and whether it echoes the card",
1989
+ "whether JSESSIONID changes after each payment, which once_per relies on: a session that outlives one payment would refuse the next permit paid in it as the same checkout",
1990
+ "that the bootstrap path answers without a server error"
1991
+ ]
1992
+ },
1993
+ {
1994
+ id: "sfmta_etims_citation",
1995
+ merchant: "San Francisco parking citations",
1996
+ status: "observe",
1997
+ evidence: [
1998
+ "sfmta.com/onlinecitation answers 301 to wmq.etimspayments.com/pbw/include/sanfrancisco/input.jsp (fetched Sep 25, 2026): Conduent eTIMS Pay By Web, clientcode 19. The lookup form posts to /pbw/inputAction.doh behind an image CAPTCHA, and the results form posts to /pbw/selectAction.doh with paymentMethod=card.",
1999
+ "shared/js/common.js clears document.submitForm.cardNumber: eTIMS collects the card in its own form, submitForm, field cardNumber, beside the name on the card, billing address, expiry month (05 - May), expiry year and security code, as a public San Francisco automation example lists them.",
2000
+ "Archived San Francisco visits (2023-10 to 2026-04) run input.jsp, inputAction.doh, selectAction.doh, then transFirstPBWMultiStepSubmitPage.doh (the card, after about 40 seconds of typing) and confirmAction.doh, the review step that completes the charge. eTIMS charges TransFirst Transaction Express from its own server.",
2001
+ "Every eTIMS form posts as a document navigation, bound to the HttpOnly JSESSIONID cookie each page sets."
2002
+ ],
2003
+ har: null,
2004
+ checkout: "https://wmq.etimspayments.com",
2005
+ checkout_frame: false,
2006
+ frame: null,
2007
+ endpoint: "https://wmq.etimspayments.com",
2008
+ method: "POST",
2009
+ path: "/pbw/transFirstPBWMultiStepSubmitPage.doh",
2010
+ query: {},
2011
+ navigation: "top",
2012
+ encoding: "form",
2013
+ session: {
2014
+ cookies: true,
2015
+ cookie_names: ["JSESSIONID"],
2016
+ headers: [...BROWSER_HEADERS, ...NAVIGATION_HEADERS],
2017
+ required: []
2018
+ },
2019
+ card: {
2020
+ pan: { at: "cardNumber", format: "digits" },
2021
+ cvv: { at: "cvv", format: "digits" },
2022
+ exp_month: { at: "expMonth", format: "as_typed" },
2023
+ exp_year: { at: "expYear", format: "as_typed" },
2024
+ expiry: null,
2025
+ last4: null,
2026
+ leading: null,
2027
+ brands: [],
2028
+ bins: null
2029
+ },
2030
+ merchant_key: null,
2031
+ requires: [{ at: "clientcode", equals: "19" }],
2032
+ refuse: [],
2033
+ payment_method: null,
2034
+ amount: null,
2035
+ currency: "USD",
2036
+ response: { headers: ["content-type", "location", "set-cookie"], withhold: [] },
2037
+ resends: [],
2038
+ challenge: null,
2039
+ facts: null,
2040
+ bootstrap: "/robots.txt",
2041
+ timeout_ms: 6e4,
2042
+ charge_timing: "deferred",
2043
+ once_per: { cookie: "JSESSIONID" },
2044
+ pending: [
2045
+ "card.cvv, card.exp_month and card.exp_year: only cardNumber has been seen, so these names stand in until a HAR shows the real ones and the expiry's digit counts",
2046
+ "whether the card form carries clientcode 19, and which of its buttons post to the same action",
2047
+ "whether the review page or confirmAction.doh, which the agent submits after the card, carries or echoes the card",
2048
+ "the answer to the card post, and whether it echoes the card",
2049
+ "whether JSESSIONID changes after each payment, which once_per relies on: a session that outlives one payment would refuse the next citation paid in it as the same checkout",
2050
+ "that the bootstrap path answers without a server error"
2051
+ ]
2052
+ },
2053
+ {
2054
+ id: "dedge_hotel_card_form",
2055
+ merchant: "D-EDGE hotel booking",
2056
+ status: "observe",
2057
+ evidence: [
2058
+ "HUMA Kotor Bay Hotel & Villas books through D-EDGE (secure-hotel-booking.com/d-edge/HUMA-Kotor-Bay-Hotel-Villas/J6UZ); D-EDGE's payment hub on pay.secure-hotel-booking.com (main.bundle.js, Last-Modified 2026-09-10) renders a CreditCard provider as an iframe at window.cardFrameUrl, card.secure-hotel-booking.com/<the session uuid>.",
2059
+ "The card frame, PciCardForm (archive.org 2026-07-06, 2026-07-23 and 2026-09-04), posts form action /validate/<session uuid>/Default natively, as a navigation of the iframe: hidden Id (the same uuid), CardNumber (maxlength 22), Month (1 to 12, no leading zero), Year (four digits), Cvv (maxlength 4) and CardHolderName. No processor SDK in the frame; D-EDGE charges from its own servers (WSPay for this hotel, by its terms).",
2060
+ "The frame also posts the card's first 8 digits to /cardCss/<session uuid>/Default to pick the card logo, which carries no full card and is not this request.",
2061
+ "secure-hotel-booking.com sits behind DataDome. The same card frame serves every D-EDGE hotel, so the profile names D-EDGE rather than one hotel; its currency is HUMA's."
2062
+ ],
2063
+ har: null,
2064
+ checkout: "https://card.secure-hotel-booking.com",
2065
+ checkout_frame: false,
2066
+ frame: null,
2067
+ endpoint: "https://card.secure-hotel-booking.com",
2068
+ method: "POST",
2069
+ path: "/validate/:session/Default",
2070
+ query: {},
2071
+ navigation: "frame",
2072
+ encoding: "form",
2073
+ session: {
2074
+ cookies: true,
2075
+ cookie_names: ["datadome"],
2076
+ headers: [...BROWSER_HEADERS, ...NAVIGATION_HEADERS],
2077
+ required: []
2078
+ },
2079
+ card: {
2080
+ pan: { at: "CardNumber", format: "digits" },
2081
+ cvv: { at: "Cvv", format: "digits" },
2082
+ exp_month: { at: "Month", format: "m" },
2083
+ exp_year: { at: "Year", format: "yyyy" },
2084
+ expiry: null,
2085
+ last4: null,
2086
+ leading: null,
2087
+ brands: [],
2088
+ bins: null
2089
+ },
2090
+ merchant_key: null,
2091
+ requires: [{ at: "Id", equals_path: "session" }],
2092
+ refuse: [],
2093
+ payment_method: null,
2094
+ amount: null,
2095
+ currency: "EUR",
2096
+ response: { headers: ["content-type", "location", "set-cookie"], withhold: [] },
2097
+ resends: [],
2098
+ challenge: null,
2099
+ facts: null,
2100
+ bootstrap: "/robots.txt",
2101
+ timeout_ms: 6e4,
2102
+ charge_timing: "at_request",
2103
+ once_per: { path: "session" },
2104
+ pending: [
2105
+ "HUMA's own payment step: whether its pay hub offers this card frame or a WSPay redirect (a HAR of one real booking's payment step)",
2106
+ "which cookies the card post needs, with DataDome in front of the host",
2107
+ "whether CardNumber is posted as digits or with the spaces the field allows",
2108
+ "the answer to the card post (a redirect, a 3DS step or a page), and whether a 3DS step sends the card again",
2109
+ "whether D-EDGE charges the card at this step or holds it for the hotel to charge later",
2110
+ "that the bootstrap path answers without a server error"
2111
+ ]
2112
+ },
2113
+ {
2114
+ id: "nic_common_checkout_payment",
2115
+ merchant: "NIC government checkout (Tyler Technologies)",
2116
+ status: "observe",
2117
+ evidence: [
2118
+ "NJMCdirect (NJ Judiciary municipal court payments) sends the browser to NIC Common Checkout, securecheckout.cdc.nicusa.com: the court portal opens CommonCheckPage/Default.aspx?token=<checkout GUID> top-level, a cookie check (?chkCap=Y) answers 302 to /Checkout/Payment?token=<GUID>, and that page sets the HttpOnly token, lang and ASP.NET_SessionId cookies and the F5 cookie TS011b1edc. The archive pairs about 40 checkout tokens with the court's return URL for the same GUID (status success, failure, duplicate or cancel).",
2119
+ "The archived NJ payment page (2026-07-03, structure only): form#paymentForm posts to /Checkout/Payment/ProcessPaymentEntry with Token, PayType (CC only for NJ), CustomerInfo.*, Phone, Email, the card in plain fields CCCardNumber (maxlength 16), CCCardType (VISA, MAST, AMEX, DISC and others, from NIC's BIN lookup), CCExpirationMonth (01 to 12), CCExpirationYear (four digits), CCCardCVV and CCNameOnCard, and the empty ACH, subscriber, PayPal, Gov2Go and Apple Pay fields. No anti-forgery token: the session cookies are the only binding.",
2120
+ "NIC's paymentjs bundle (sha256 f5733609dfe35a6b6ccd24478aacc123e1b7d8817e971275b94e6b80332530d0, byte-identical live, in the archive and on the Florida host): on #submitPayment it appends a hidden submitPayment and submits the form, a top-level navigation. The same URL takes cancelPayment and the PayPal and Gov2Go launch buttons. The Braintree and Tyler Orion drop-ins in the bundle are off for NJ. The only other card-adjacent calls, BinLookup and GetFee, send the first 11 digits and are not this request.",
2121
+ "NIC charges the card from its own servers and sends the browser to the agency's return URL with the checkout token; the agency records the payment inside that return. The amount is set by the agency when it creates the token (NJ folds a non-refundable 3% processing fee into it), so the body names none. The same page serves about 18 state agencies; the ones with NIC's reCAPTCHA put g-recaptcha-response in the body, which this profile refuses."
2122
+ ],
2123
+ har: null,
2124
+ checkout: "https://securecheckout.cdc.nicusa.com",
2125
+ checkout_frame: false,
2126
+ frame: null,
2127
+ endpoint: "https://securecheckout.cdc.nicusa.com",
2128
+ method: "POST",
2129
+ path: "/Checkout/Payment/ProcessPaymentEntry",
2130
+ query: {},
2131
+ navigation: "top",
2132
+ encoding: "form",
2133
+ session: {
2134
+ cookies: true,
2135
+ cookie_names: ["token", "lang", "ASP.NET_SessionId", "TS01*"],
2136
+ headers: [...BROWSER_HEADERS, ...NAVIGATION_HEADERS],
2137
+ required: []
2138
+ },
2139
+ card: {
2140
+ pan: { at: "CCCardNumber", format: "digits" },
2141
+ cvv: { at: "CCCardCVV", format: "digits" },
2142
+ exp_month: { at: "CCExpirationMonth", format: "mm" },
2143
+ exp_year: { at: "CCExpirationYear", format: "yyyy" },
2144
+ expiry: null,
2145
+ last4: null,
2146
+ leading: null,
2147
+ brands: [{ at: "CCCardType", map: { visa: "VISA", mastercard: "MAST", amex: "AMEX", discover: "DISC" } }],
2148
+ bins: null
2149
+ },
2150
+ merchant_key: null,
2151
+ requires: [
2152
+ { at: "PayType", equals: "CC" },
2153
+ { at: "Token", matches: "guid" },
2154
+ { at: "Token", equals_cookie: "token" },
2155
+ { at: "submitPayment", equals: "" }
2156
+ ],
2157
+ refuse: [
2158
+ { at: "cancelPayment", when: "present" },
2159
+ { at: "launchPayPal", when: "present" },
2160
+ { at: "launchGov2Go", when: "present" },
2161
+ { at: "launchGov2GoPay", when: "present" },
2162
+ { at: "ait", when: "present" },
2163
+ { at: "g-recaptcha-response", when: "present" },
2164
+ { at: "CCCardNumberBIN", when: "present" },
2165
+ { at: "SubUserName", when: "nonempty" },
2166
+ { at: "SubPassword", when: "nonempty" },
2167
+ { at: "ACHRoutingNumber", when: "nonempty" },
2168
+ { at: "ACHAccountNumber", when: "nonempty" },
2169
+ { at: "ACHAccountNumberVerify", when: "nonempty" },
2170
+ { at: "ACHNameOnAccount", when: "nonempty" },
2171
+ { at: "ACHBankType", when: "present" },
2172
+ { at: "AuthorizeAch", when: "true" },
2173
+ { at: "PayPalToken", when: "nonempty" },
2174
+ { at: "PaymentApiToken", when: "nonempty" },
2175
+ { at: "Gov2GoPaymentApiPaymentType", when: "nonempty" },
2176
+ { prefix: "ApplePay.PaymentAccount.", when: "nonempty" },
2177
+ { prefix: "EmbeddedCheckoutPayment", when: "present" },
2178
+ { prefix: "braintree", when: "present" }
2179
+ ],
2180
+ payment_method: null,
2181
+ amount: null,
2182
+ currency: "USD",
2183
+ response: { headers: ["content-type", "location", "set-cookie"], withhold: [] },
2184
+ resends: [],
2185
+ challenge: null,
2186
+ facts: null,
2187
+ bootstrap: "/Checkout/bundles/html5SiteLayout",
2188
+ timeout_ms: 6e4,
2189
+ charge_timing: "at_request",
2190
+ once_per: { at: "Token" },
2191
+ pending: [
2192
+ "the answer to a paid post (a 302 to NIC's receipt or straight to the agency's return URL) and to a decline (a redirect back or the form again, and whether it echoes the card, a masked card or the expiry)",
2193
+ "every cookie the checkout sets after its cookie check (each TS01 name, whether lang matters)",
2194
+ "the CCCardType codes NIC's BIN lookup answers for Visa, Mastercard, Amex and Discover cards, credit and debit",
2195
+ "whether NIC's F5 front door and fraud rules accept the post from the relay's address and TLS with cookies minted in the agent's browser, and whether they read Origin, Referer or the sec-fetch headers",
2196
+ "the session's idle timeout, which bounds the approval",
2197
+ "Tyler Technologies' PCI attestation of compliance for this checkout host",
2198
+ "the form action's letter case: the server answers any case, and this profile pauses only the archived spelling, so a page that posts another spelling is not paused"
2199
+ ]
2200
+ },
2201
+ {
2202
+ id: "fastspring_popup_rekordcloud",
2203
+ merchant: "Lexicon",
2204
+ status: "observe",
2205
+ evidence: [
2206
+ "www.lexicondj.com/scripts/checkout.js (sha256 9802ab20), read Sep 26, 2026: loads FastSpring SBL 1.0.5 with data-storefront rekordcloud.onfastspring.com/popup-lexicon; FastSpring is the merchant of record and opens its popup checkout in a cross-origin iframe at https://rekordcloud.onfastspring.com/popup-lexicon/session/{sessionId}.",
2207
+ "FastSpring shared checkout app on cdn.onfastspring.com (foundation.checkout.js sha256 5334d89b doPayment, common.js sha256 21a031fd interceptor), read Sep 25 and 26, 2026: AngularJS $http posts JSON to <session URL>/payment with X-Session-Token from the page's session JSON, never a cookie.",
2208
+ "Body: contact, card {year, month, number (digits in groups of four, one space between), security}, sepa, ach, pad, bacs, upi, cpfNumber, dniNumber, paymentType, subscribe, captcha {token, captchaType}, recipientSelected, autoRenew (when the renewal checkbox shows), fingerprint; PayPal, wallets and bank debits post the same URL with another paymentType.",
2209
+ "Answers: next error, redirect (success), 3ds (Adyen form in the popup), ebanx-3ds (BRL: the page reads the typed card again and posts it to <session URL>/3ds/return-ebanx), antom-3ds and APM redirects.",
2210
+ "The popup document (script#viewdata) and each JSON answer carry order.totalWithTaxValue, order.currency, production, session.captchaRequired, hasRecurring and the selected items with their renewal (future).",
2211
+ "foundation.checkout.js (sha256 5334d89b), read Sep 30, 2026: the page also reads its order from PUT <session URL>/items/<path> (changeQuantity, onAutoRenewChange), POST <session URL>/items/<path> (changeQuantity for an unselected item, acceptOffer) and POST <session URL>/spi/sheerid; the contact fields post /customer/recognize/background when they lose focus (the interceptor drops /background), and the page reads nothing from that answer.",
2212
+ "Turnstile: renderCaptchaWidget runs when the card option is chosen (choosePaymentOptionWithFields), and turnstileCallback stores the token then, so a body holds a token minted before Pay.",
2213
+ "Research: processors-wave2/items/fastspring/DESIGN.md; the popup HTML itself needs a live session and was not read."
2214
+ ],
2215
+ har: null,
2216
+ checkout: "https://rekordcloud.onfastspring.com",
2217
+ checkout_frame: true,
2218
+ frame: null,
2219
+ endpoint: "https://rekordcloud.onfastspring.com",
2220
+ method: "POST",
2221
+ path: "/popup-lexicon/session/:sessionId/payment",
2222
+ query: {},
2223
+ navigation: null,
2224
+ encoding: "json",
2225
+ session: {
2226
+ cookies: false,
2227
+ cookie_names: null,
2228
+ headers: [...BROWSER_HEADERS, "content-type", "x-session-token"],
2229
+ required: ["x-session-token"]
2230
+ },
2231
+ card: {
2232
+ pan: { at: ["card", "number"], format: "grouped4" },
2233
+ cvv: { at: ["card", "security"], format: "digits" },
2234
+ exp_month: { at: ["card", "month"], format: "as_typed" },
2235
+ exp_year: { at: ["card", "year"], format: "as_typed" },
2236
+ expiry: null,
2237
+ last4: null,
2238
+ leading: null,
2239
+ brands: [],
2240
+ bins: null
2241
+ },
2242
+ merchant_key: null,
2243
+ requires: [{ at: ["paymentType"], equals: "card" }],
2244
+ refuse: [
2245
+ // Brazil's EBANX route sends the typed card a second time.
2246
+ { at: ["cpfNumber"], when: "nonempty" },
2247
+ { at: ["dniNumber"], when: "nonempty" },
2248
+ // A gift licenses someone else.
2249
+ { at: ["recipientSelected"], when: "true" },
2250
+ { at: ["recipient"], when: "present" },
2251
+ // FastSpring keeps the card for renewals, which waits on the card-on-file decision.
2252
+ { at: ["autoRenew"], when: "true" },
2253
+ // An invoiced buyer's purchase order, another checkout than this one.
2254
+ { at: ["customerReference"], when: "present" },
2255
+ // Another payment method's details beside the card.
2256
+ { at: ["sepa", "iban"], when: "nonempty" },
2257
+ { at: ["ach", "accountNum"], when: "nonempty" },
2258
+ { at: ["pad", "accountNum"], when: "nonempty" },
2259
+ { at: ["bacs", "accountNumber"], when: "nonempty" },
2260
+ { at: ["googlepay"], when: "nonempty" },
2261
+ { at: ["paymentKlarna"], when: "nonempty" },
2262
+ { at: ["mercadopago"], when: "nonempty" }
2263
+ ],
2264
+ payment_method: { at: ["paymentType"], card: ["card"] },
2265
+ amount: null,
2266
+ currency: "USD",
2267
+ response: { headers: ["content-type"], withhold: [{ at: ["next"], equals: "ebanx-3ds" }] },
2268
+ resends: [{ method: "POST", path: "/popup-lexicon/session/:sessionId/3ds/return-ebanx", query: {} }],
2269
+ // The device may send a body holding a Turnstile token for 240 seconds after the create. The
2270
+ // page minted the token when the card option rendered, before the pause, so this cannot keep
2271
+ // a slow approval from sending an expired one (pending below).
2272
+ challenge: { at: ["captcha", "token"], ttl_ms: 24e4 },
2273
+ facts: {
2274
+ reader: "fastspring_session",
2275
+ // Every answer FastSpring's page reads its order from (refreshPageResponse and
2276
+ // refreshOrderResponse): the popup document, the one-segment calls (/tax, /coupons,
2277
+ // /locale, /exempt), an item's quantity, renewal or add-on change, and a verified discount.
2278
+ responses: [
2279
+ { method: "GET", path: "/popup-lexicon/session/:sessionId", query: {}, format: "html" },
2280
+ { method: "POST", path: "/popup-lexicon/session/:sessionId/:step", query: {}, format: "json" },
2281
+ { method: "GET", path: "/popup-lexicon/session/:sessionId/:step", query: {}, format: "json" },
2282
+ { method: "PUT", path: "/popup-lexicon/session/:sessionId/items/:item", query: {}, format: "json" },
2283
+ { method: "POST", path: "/popup-lexicon/session/:sessionId/items/:item", query: {}, format: "json" },
2284
+ { method: "POST", path: "/popup-lexicon/session/:sessionId/spi/sheerid", query: {}, format: "json" }
2285
+ ],
2286
+ // Customer recognition, posted when a contact field loses focus: the page reads nothing from its answer.
2287
+ inert: [{ method: "POST", path: "/popup-lexicon/session/:sessionId/customer/recognize", query: {} }],
2288
+ bind: "sessionId"
2289
+ },
2290
+ bootstrap: "/robots.txt",
2291
+ timeout_ms: 3e4,
2292
+ // Most of Lexicon's catalog renews monthly. The facts read refuses each renewing
2293
+ // order, and the worst case stays here until a purchase proves that read.
2294
+ charge_timing: "recurring",
2295
+ once_per: null,
2296
+ pending: [
2297
+ "which controller the popup runs (foundation or pinhole) and the exact /payment body it sends",
2298
+ "the popup document URL and the paths of the JSON answers that carry its session state",
2299
+ "whether /payment needs the AWSALB and AWSALBCORS cookies, Origin or Referer",
2300
+ "whether an X-XSRF-TOKEN header rides along",
2301
+ "whether rekordcloud sets session.captchaRequired",
2302
+ "how old a Turnstile token is when the card request pauses: the page mints it when the card option renders, so a body can still reach FastSpring expired inside the 240 seconds (a captcha error, nothing charged, a spent approval) until a preparation flow approves before Pay",
2303
+ "card.number sent grouped or plain; month and year widths",
2304
+ "the session currency for US buyers",
2305
+ "a card-on-file policy for the monthly plans and the add-on, before any order that renews"
2306
+ ]
2307
+ }
2308
+ ]);
2309
+ function wireProjection(profiles) {
2310
+ return deepFreeze(profiles.map(({ evidence, bootstrap, timeout_ms, ...profile }) => ({ ...profile })));
2311
+ }
2312
+ function clientProjection(profiles) {
2313
+ return deepFreeze(profiles.map(({ evidence, ...profile }) => ({ ...profile })));
2314
+ }
2315
+ var MERCHANT_CARD_ENDPOINT_RECOGNIZERS = /* @__PURE__ */ wireProjection(MERCHANT_CARD_ENDPOINT_PROFILES);
2316
+ var MERCHANT_CARD_ENDPOINT_CLIENT_TABLE = /* @__PURE__ */ clientProjection(MERCHANT_CARD_ENDPOINT_PROFILES);
2317
+ function merchantCardEndpointById(id) {
2318
+ return typeof id === "string" ? MERCHANT_CARD_ENDPOINT_CLIENT_TABLE.find((entry) => entry.id === id) ?? null : null;
2319
+ }
2320
+ function trustedMerchantCardEndpoint(idOrEntry) {
2321
+ const profile = merchantCardEndpointById(typeof idOrEntry === "string" ? idOrEntry : idOrEntry?.id);
2322
+ if (!profile)
2323
+ throw new Error("Refusing to pay: unknown merchant card endpoint.");
2324
+ return profile;
2325
+ }
2326
+ function merchantCardEndpointRecognizerById(id) {
2327
+ return typeof id === "string" ? MERCHANT_CARD_ENDPOINT_RECOGNIZERS.find((entry) => entry.id === id) ?? null : null;
2328
+ }
2329
+ function merchantCardEndpointLive(idOrEntry) {
2330
+ const profile = merchantCardEndpointById(typeof idOrEntry === "string" ? idOrEntry : idOrEntry?.id);
2331
+ return !!profile && profile.status === "enabled" && profile.pending.length === 0 && typeof profile.har === "string" && profile.har !== "" && profile.charge_timing !== "recurring" && (profile.merchant_key == null || Array.isArray(profile.merchant_key.keys) && profile.merchant_key.keys.length > 0);
2332
+ }
2333
+ // src/sha256.js
2334
+ var K = /* @__PURE__ */ new Uint32Array([
2335
+ 1116352408,
2336
+ 1899447441,
2337
+ 3049323471,
2338
+ 3921009573,
2339
+ 961987163,
2340
+ 1508970993,
2341
+ 2453635748,
2342
+ 2870763221,
2343
+ 3624381080,
2344
+ 310598401,
2345
+ 607225278,
2346
+ 1426881987,
2347
+ 1925078388,
2348
+ 2162078206,
2349
+ 2614888103,
2350
+ 3248222580,
2351
+ 3835390401,
2352
+ 4022224774,
2353
+ 264347078,
2354
+ 604807628,
2355
+ 770255983,
2356
+ 1249150122,
2357
+ 1555081692,
2358
+ 1996064986,
2359
+ 2554220882,
2360
+ 2821834349,
2361
+ 2952996808,
2362
+ 3210313671,
2363
+ 3336571891,
2364
+ 3584528711,
2365
+ 113926993,
2366
+ 338241895,
2367
+ 666307205,
2368
+ 773529912,
2369
+ 1294757372,
2370
+ 1396182291,
2371
+ 1695183700,
2372
+ 1986661051,
2373
+ 2177026350,
2374
+ 2456956037,
2375
+ 2730485921,
2376
+ 2820302411,
2377
+ 3259730800,
2378
+ 3345764771,
2379
+ 3516065817,
2380
+ 3600352804,
2381
+ 4094571909,
2382
+ 275423344,
2383
+ 430227734,
2384
+ 506948616,
2385
+ 659060556,
2386
+ 883997877,
2387
+ 958139571,
2388
+ 1322822218,
2389
+ 1537002063,
2390
+ 1747873779,
2391
+ 1955562222,
2392
+ 2024104815,
2393
+ 2227730452,
2394
+ 2361852424,
2395
+ 2428436474,
2396
+ 2756734187,
2397
+ 3204031479,
2398
+ 3329325298
2399
+ ]);
2400
+ var rotr = (x, n) => x >>> n | x << 32 - n;
2401
+ function utf8(text) {
2402
+ const out = [];
2403
+ for (const char of text) {
2404
+ let cp = char.codePointAt(0);
2405
+ if (cp >= 55296 && cp <= 57343)
2406
+ cp = 65533;
2407
+ if (cp < 128)
2408
+ out.push(cp);
2409
+ else if (cp < 2048)
2410
+ out.push(192 | cp >> 6, 128 | cp & 63);
2411
+ else if (cp < 65536)
2412
+ out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
2413
+ else
2414
+ out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
2415
+ }
2416
+ return Uint8Array.from(out);
2417
+ }
2418
+ function sha256Hex(input) {
2419
+ const isBytes2 = Object.prototype.toString.call(input) === "[object Uint8Array]";
2420
+ if (typeof input !== "string" && !isBytes2)
2421
+ throw new TypeError("sha256Hex takes a string or a Uint8Array.");
2422
+ const bytes = typeof input === "string" ? utf8(input) : input;
2423
+ const length = bytes.length;
2424
+ const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
2425
+ padded.set(bytes);
2426
+ padded[length] = 128;
2427
+ const view = new DataView(padded.buffer);
2428
+ view.setUint32(padded.length - 8, Math.floor(length / 536870912));
2429
+ view.setUint32(padded.length - 4, length << 3 >>> 0);
2430
+ const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
2431
+ const W = new Uint32Array(64);
2432
+ for (let offset = 0; offset < padded.length; offset += 64) {
2433
+ for (let t = 0; t < 16; t++)
2434
+ W[t] = view.getUint32(offset + t * 4);
2435
+ for (let t = 16; t < 64; t++) {
2436
+ const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
2437
+ const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
2438
+ W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
2439
+ }
2440
+ 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];
2441
+ for (let t = 0; t < 64; t++) {
2442
+ const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
2443
+ const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
2444
+ h = g;
2445
+ g = f;
2446
+ f = e;
2447
+ e = d + t1 >>> 0;
2448
+ d = c;
2449
+ c = b;
2450
+ b = a;
2451
+ a = t1 + t2 >>> 0;
2452
+ }
2453
+ H[0] += a;
2454
+ H[1] += b;
2455
+ H[2] += c;
2456
+ H[3] += d;
2457
+ H[4] += e;
2458
+ H[5] += f;
2459
+ H[6] += g;
2460
+ H[7] += h;
2461
+ }
2462
+ return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
2463
+ }
2464
+ // src/merchant-card-endpoint-keys.js
2465
+ var MerchantKeyError = class extends Error {
2466
+ constructor(reason) {
2467
+ super("Refusing to encrypt the card under the merchant's key.");
2468
+ this.name = "MerchantKeyError";
2469
+ this.code = "merchant_key_refused";
2470
+ this.reason = reason;
2471
+ }
2472
+ };
2473
+ var refuse = (reason) => new MerchantKeyError(reason);
2474
+ var MERCHANT_KEY_HASHES = /* @__PURE__ */ Object.freeze({ "SHA-1": 20, "SHA-256": 32, "SHA-512": 64 });
2475
+ var MERCHANT_KEY_JWE_ALGS = /* @__PURE__ */ Object.freeze({ "RSA-OAEP": "SHA-1", "RSA-OAEP-256": "SHA-256" });
2476
+ var MERCHANT_KEY_JWE_ENCS = /* @__PURE__ */ Object.freeze({
2477
+ "A256CBC-HS512": Object.freeze({ key: 64, iv: 16, tag: 32 }),
2478
+ A256GCM: Object.freeze({ key: 32, iv: 12, tag: 16 })
2479
+ });
2480
+ var MERCHANT_KEY_SIZES = /* @__PURE__ */ Object.freeze([256, 384, 512]);
2481
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2482
+ var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2483
+ var SHA256_HEX = /^[0-9a-f]{64}$/;
2484
+ var RSA_ALGORITHM = [48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0];
2485
+ var MAX_ENCRYPTED_LENGTH = 4096;
2486
+ var isBytes = (value) => Object.prototype.toString.call(value) === "[object Uint8Array]";
2487
+ function encodeBase64(bytes, url = false) {
2488
+ const alphabet = url ? BASE64URL_ALPHABET : BASE64_ALPHABET;
2489
+ let out = "";
2490
+ for (let i = 0; i < bytes.length; i += 3) {
2491
+ const a = bytes[i];
2492
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
2493
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
2494
+ const triple = a << 16 | b << 8 | c;
2495
+ out += alphabet[triple >> 18 & 63] + alphabet[triple >> 12 & 63];
2496
+ if (i + 1 < bytes.length)
2497
+ out += alphabet[triple >> 6 & 63];
2498
+ else if (!url)
2499
+ out += "=";
2500
+ if (i + 2 < bytes.length)
2501
+ out += alphabet[triple & 63];
2502
+ else if (!url)
2503
+ out += "=";
2504
+ }
2505
+ return out;
2506
+ }
2507
+ function decodeBase64(text, url = false) {
2508
+ if (typeof text !== "string" || text.length === 0 || text.length > MAX_ENCRYPTED_LENGTH * 2)
2509
+ return null;
2510
+ const alphabet = url ? BASE64URL_ALPHABET : BASE64_ALPHABET;
2511
+ const body = url ? text : text.replace(/={1,2}$/, "");
2512
+ if (!url && text.length % 4 !== 0)
2513
+ return null;
2514
+ if (body.length % 4 === 1)
2515
+ return null;
2516
+ const out = new Uint8Array(Math.floor(body.length * 3 / 4));
2517
+ let bits = 0;
2518
+ let value = 0;
2519
+ let at = 0;
2520
+ for (let i = 0; i < body.length; i += 1) {
2521
+ const digit = alphabet.indexOf(body[i]);
2522
+ if (digit === -1)
2523
+ return null;
2524
+ value = value << 6 | digit;
2525
+ bits += 6;
2526
+ if (bits >= 8) {
2527
+ bits -= 8;
2528
+ out[at++] = value >> bits & 255;
2529
+ value &= (1 << bits) - 1;
2530
+ }
2531
+ }
2532
+ return encodeBase64(out, url) === text ? out : null;
2533
+ }
2534
+ var utf8Bytes = (text) => new TextEncoder().encode(text);
2535
+ function readElement(der, at, tag) {
2536
+ if (at + 2 > der.length || der[at] !== tag)
2537
+ throw refuse("key");
2538
+ let length = der[at + 1];
2539
+ let start = at + 2;
2540
+ if (length & 128) {
2541
+ const count = length & 127;
2542
+ if (count === 0 || count > 2 || start + count > der.length || der[start] === 0)
2543
+ throw refuse("key");
2544
+ length = count === 1 ? der[start] : der[start] * 256 + der[start + 1];
2545
+ if (length < 128)
2546
+ throw refuse("key");
2547
+ start += count;
2548
+ }
2549
+ if (start + length > der.length)
2550
+ throw refuse("key");
2551
+ return { start, end: start + length };
2552
+ }
2553
+ function readPositiveInteger(der, at) {
2554
+ const { start, end } = readElement(der, at, 2);
2555
+ if (end === start || der[start] & 128)
2556
+ throw refuse("key");
2557
+ if (der[start] === 0 && (end - start === 1 || !(der[start + 1] & 128)))
2558
+ throw refuse("key");
2559
+ return { bytes: der.subarray(der[start] === 0 ? start + 1 : start, end), end };
2560
+ }
2561
+ function toBigInt(bytes) {
2562
+ let hex = "";
2563
+ for (let i = 0; i < bytes.length; i += 1)
2564
+ hex += (bytes[i] < 16 ? "0" : "") + bytes[i].toString(16);
2565
+ return BigInt(`0x${hex || "0"}`);
2566
+ }
2567
+ function toBytes(value, size) {
2568
+ const hex = value.toString(16).padStart(size * 2, "0");
2569
+ if (hex.length !== size * 2)
2570
+ throw refuse("text");
2571
+ const out = new Uint8Array(size);
2572
+ for (let i = 0; i < size; i += 1)
2573
+ out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
2574
+ return out;
2575
+ }
2576
+ function modPow(base, exponent, modulus) {
2577
+ let result = 1n;
2578
+ let square = base % modulus;
2579
+ for (let rest = exponent; rest > 0n; rest >>= 1n) {
2580
+ if (rest & 1n)
2581
+ result = result * square % modulus;
2582
+ square = square * square % modulus;
2583
+ }
2584
+ return result;
2585
+ }
2586
+ function parseMerchantKey(key) {
2587
+ const spki = key && typeof key === "object" ? key.spki : void 0;
2588
+ const pin = key && typeof key === "object" ? key.sha256 : void 0;
2589
+ if (typeof spki !== "string" || spki.length > 1024 || typeof pin !== "string" || !SHA256_HEX.test(pin))
2590
+ throw refuse("key");
2591
+ const der = decodeBase64(spki);
2592
+ if (!der || sha256Hex(der) !== pin)
2593
+ throw refuse("key");
2594
+ const outer = readElement(der, 0, 48);
2595
+ if (outer.end !== der.length || outer.start + RSA_ALGORITHM.length > der.length)
2596
+ throw refuse("key");
2597
+ if (!RSA_ALGORITHM.every((byte, i) => der[outer.start + i] === byte))
2598
+ throw refuse("key");
2599
+ const bits = readElement(der, outer.start + RSA_ALGORITHM.length, 3);
2600
+ if (bits.end !== outer.end || bits.start === bits.end || der[bits.start] !== 0)
2601
+ throw refuse("key");
2602
+ const rsa = readElement(der, bits.start + 1, 48);
2603
+ if (rsa.end !== bits.end)
2604
+ throw refuse("key");
2605
+ const modulus = readPositiveInteger(der, rsa.start);
2606
+ const exponent = readPositiveInteger(der, modulus.end);
2607
+ if (exponent.end !== rsa.end)
2608
+ throw refuse("key");
2609
+ const n = modulus.bytes;
2610
+ if (!MERCHANT_KEY_SIZES.includes(n.length) || !(n[0] & 128) || !(n[n.length - 1] & 1))
2611
+ throw refuse("key");
2612
+ const e = exponent.bytes;
2613
+ if (e.length !== 3 || e[0] !== 1 || e[1] !== 0 || e[2] !== 1)
2614
+ throw refuse("key");
2615
+ return Object.freeze({ n: toBigInt(n), size: n.length });
2616
+ }
2617
+ function subtleOf(crypto) {
2618
+ if (!crypto || !crypto.subtle || typeof crypto.getRandomValues !== "function")
2619
+ throw refuse("crypto");
2620
+ return crypto.subtle;
2621
+ }
2622
+ async function digest(subtle, hash, bytes) {
2623
+ try {
2624
+ return new Uint8Array(await subtle.digest(hash, bytes));
2625
+ }
2626
+ catch {
2627
+ throw refuse("crypto");
2628
+ }
2629
+ }
2630
+ async function mgf1(subtle, hash, seed, length) {
2631
+ const out = new Uint8Array(length);
2632
+ const input = new Uint8Array(seed.length + 4);
2633
+ input.set(seed);
2634
+ try {
2635
+ for (let counter = 0, filled = 0; filled < length; counter += 1) {
2636
+ input[seed.length] = counter >>> 24 & 255;
2637
+ input[seed.length + 1] = counter >>> 16 & 255;
2638
+ input[seed.length + 2] = counter >>> 8 & 255;
2639
+ input[seed.length + 3] = counter & 255;
2640
+ const block = await digest(subtle, hash, input);
2641
+ out.set(block.subarray(0, Math.min(block.length, length - filled)), filled);
2642
+ filled += block.length;
2643
+ block.fill(0);
2644
+ }
2645
+ return out;
2646
+ }
2647
+ finally {
2648
+ input.fill(0);
2649
+ }
2650
+ }
2651
+ function randomBytes(crypto, length) {
2652
+ const out = new Uint8Array(length);
2653
+ try {
2654
+ crypto.getRandomValues(out);
2655
+ }
2656
+ catch {
2657
+ throw refuse("random");
2658
+ }
2659
+ return out;
2660
+ }
2661
+ async function rsaOaepEncrypt(key, message, { hash, mgf1: maskHash }, crypto, seed) {
2662
+ const subtle = subtleOf(crypto);
2663
+ const { n, size } = parseMerchantKey(key);
2664
+ const hLen = MERCHANT_KEY_HASHES[hash];
2665
+ if (!hLen || !MERCHANT_KEY_HASHES[maskHash])
2666
+ throw refuse("key");
2667
+ if (!isBytes(message) || message.length > size - 2 * hLen - 2)
2668
+ throw refuse("text");
2669
+ const db = new Uint8Array(size - hLen - 1);
2670
+ const em = new Uint8Array(size);
2671
+ let seedBytes = null;
2672
+ let dbMask = null;
2673
+ let seedMask = null;
2674
+ try {
2675
+ db.set(await digest(subtle, hash, new Uint8Array(0)));
2676
+ db[db.length - message.length - 1] = 1;
2677
+ db.set(message, db.length - message.length);
2678
+ if (seed !== void 0) {
2679
+ if (!isBytes(seed) || seed.length !== hLen)
2680
+ throw refuse("random");
2681
+ seedBytes = Uint8Array.from(seed);
2682
+ }
2683
+ else {
2684
+ seedBytes = randomBytes(crypto, hLen);
2685
+ }
2686
+ dbMask = await mgf1(subtle, maskHash, seedBytes, db.length);
2687
+ for (let i = 0; i < db.length; i += 1)
2688
+ db[i] ^= dbMask[i];
2689
+ seedMask = await mgf1(subtle, maskHash, db, hLen);
2690
+ for (let i = 0; i < hLen; i += 1)
2691
+ seedBytes[i] ^= seedMask[i];
2692
+ em.set(seedBytes, 1);
2693
+ em.set(db, 1 + hLen);
2694
+ return toBytes(modPow(toBigInt(em), 65537n, n), size);
2695
+ }
2696
+ finally {
2697
+ db.fill(0);
2698
+ em.fill(0);
2699
+ if (seedBytes)
2700
+ seedBytes.fill(0);
2701
+ if (dbMask)
2702
+ dbMask.fill(0);
2703
+ if (seedMask)
2704
+ seedMask.fill(0);
2705
+ }
2706
+ }
2707
+ function merchantJweHeader(spec, kid) {
2708
+ return JSON.stringify({ alg: spec.alg, enc: spec.enc, kid, ...spec.header });
2709
+ }
2710
+ async function aesCbcHmacSeal(subtle, contentKey, iv, text, aad) {
2711
+ let macKey;
2712
+ let encKey;
2713
+ try {
2714
+ macKey = await subtle.importKey("raw", contentKey.subarray(0, 32), { name: "HMAC", hash: "SHA-512" }, false, ["sign"]);
2715
+ encKey = await subtle.importKey("raw", contentKey.subarray(32, 64), { name: "AES-CBC" }, false, ["encrypt"]);
2716
+ }
2717
+ catch {
2718
+ throw refuse("crypto");
2719
+ }
2720
+ let ciphertext;
2721
+ try {
2722
+ ciphertext = new Uint8Array(await subtle.encrypt({ name: "AES-CBC", iv }, encKey, text));
2723
+ }
2724
+ catch {
2725
+ throw refuse("crypto");
2726
+ }
2727
+ const al = new Uint8Array(8);
2728
+ const bits = aad.length * 8;
2729
+ new DataView(al.buffer).setUint32(0, Math.floor(bits / 4294967296));
2730
+ new DataView(al.buffer).setUint32(4, bits >>> 0);
2731
+ const macInput = new Uint8Array(aad.length + iv.length + ciphertext.length + 8);
2732
+ macInput.set(aad, 0);
2733
+ macInput.set(iv, aad.length);
2734
+ macInput.set(ciphertext, aad.length + iv.length);
2735
+ macInput.set(al, aad.length + iv.length + ciphertext.length);
2736
+ let mac;
2737
+ try {
2738
+ mac = new Uint8Array(await subtle.sign("HMAC", macKey, macInput));
2739
+ }
2740
+ catch {
2741
+ throw refuse("crypto");
2742
+ }
2743
+ const tag = mac.slice(0, 32);
2744
+ mac.fill(0);
2745
+ return { ciphertext, tag };
2746
+ }
2747
+ async function aesGcmSeal(subtle, contentKey, iv, text, aad) {
2748
+ try {
2749
+ const key = await subtle.importKey("raw", contentKey, { name: "AES-GCM" }, false, ["encrypt"]);
2750
+ const sealed = new Uint8Array(await subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad, tagLength: 128 }, key, text));
2751
+ return { ciphertext: sealed.slice(0, sealed.length - 16), tag: sealed.slice(sealed.length - 16) };
2752
+ }
2753
+ catch {
2754
+ throw refuse("crypto");
2755
+ }
2756
+ }
2757
+ async function jweCompactEncrypt(key, text, spec, kid, crypto) {
2758
+ const subtle = subtleOf(crypto);
2759
+ const hash = MERCHANT_KEY_JWE_ALGS[spec && spec.alg];
2760
+ const enc = MERCHANT_KEY_JWE_ENCS[spec && spec.enc];
2761
+ if (!hash || !enc || typeof kid !== "string" || kid === "")
2762
+ throw refuse("key");
2763
+ if (!isBytes(text))
2764
+ throw refuse("text");
2765
+ const header = encodeBase64(utf8Bytes(merchantJweHeader(spec, kid)), true);
2766
+ const aad = utf8Bytes(header);
2767
+ const contentKey = randomBytes(crypto, enc.key);
2768
+ try {
2769
+ const wrapped = await rsaOaepEncrypt(key, contentKey, { hash, mgf1: hash }, crypto);
2770
+ const iv = randomBytes(crypto, enc.iv);
2771
+ const { ciphertext, tag } = spec.enc === "A256GCM" ? await aesGcmSeal(subtle, contentKey, iv, text, aad) : await aesCbcHmacSeal(subtle, contentKey, iv, text, aad);
2772
+ return [header, encodeBase64(wrapped, true), encodeBase64(iv, true), encodeBase64(ciphertext, true), encodeBase64(tag, true)].join(".");
2773
+ }
2774
+ finally {
2775
+ contentKey.fill(0);
2776
+ }
2777
+ }
2778
+ function readCompactJwe(value) {
2779
+ if (typeof value !== "string" || value.length > MAX_ENCRYPTED_LENGTH)
2780
+ return null;
2781
+ const parts = value.split(".");
2782
+ if (parts.length !== 5)
2783
+ return null;
2784
+ const bytes = parts.map((part) => decodeBase64(part, true));
2785
+ if (bytes.some((part) => part === null))
2786
+ return null;
2787
+ let headerText;
2788
+ let header;
2789
+ try {
2790
+ headerText = new TextDecoder("utf-8", { fatal: true }).decode(bytes[0]);
2791
+ header = JSON.parse(headerText);
2792
+ }
2793
+ catch {
2794
+ return null;
2795
+ }
2796
+ if (!header || typeof header !== "object" || Array.isArray(header) || JSON.stringify(header) !== headerText)
2797
+ return null;
2798
+ return { header, headerText, encryptedKey: bytes[1], iv: bytes[2], ciphertext: bytes[3], tag: bytes[4] };
2799
+ }
2800
+ function allowedSizes(merchantKey) {
2801
+ const sizes = [];
2802
+ for (const key of merchantKey.keys) {
2803
+ try {
2804
+ sizes.push(parseMerchantKey(key).size);
2805
+ }
2806
+ catch {
2807
+ }
2808
+ }
2809
+ return merchantKey.keys.length ? sizes : MERCHANT_KEY_SIZES;
2810
+ }
2811
+ function encryptedValueFits(merchantKey, value) {
2812
+ if (!merchantKey || typeof value !== "string" || value.length > MAX_ENCRYPTED_LENGTH)
2813
+ return false;
2814
+ const sizes = allowedSizes(merchantKey);
2815
+ if (merchantKey.scheme === "rsa_oaep") {
2816
+ const bytes = decodeBase64(value);
2817
+ return !!bytes && sizes.includes(bytes.length);
2818
+ }
2819
+ if (merchantKey.scheme !== "jwe")
2820
+ return false;
2821
+ const jwe = readCompactJwe(value);
2822
+ const enc = MERCHANT_KEY_JWE_ENCS[merchantKey.enc];
2823
+ if (!jwe || !enc)
2824
+ return false;
2825
+ const { header } = jwe;
2826
+ const kids = merchantKey.keys.map((key) => key.kid);
2827
+ const alg = merchantKey.alg === null ? header.alg : merchantKey.alg;
2828
+ if (!Object.prototype.hasOwnProperty.call(MERCHANT_KEY_JWE_ALGS, alg) || typeof header.kid !== "string" || header.kid === "" || kids.length && !kids.includes(header.kid))
2829
+ return false;
2830
+ if (jwe.headerText !== merchantJweHeader({ ...merchantKey, alg }, header.kid))
2831
+ return false;
2832
+ if (!sizes.includes(jwe.encryptedKey.length) || jwe.iv.length !== enc.iv || jwe.tag.length !== enc.tag || jwe.ciphertext.length === 0)
2833
+ return false;
2834
+ return merchantKey.enc === "A256GCM" || jwe.ciphertext.length % 16 === 0;
2835
+ }
2836
+ // src/merchant-card-endpoint-facts.js
2837
+ var TWO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set(["usd", "eur", "gbp", "cad", "aud"]);
2838
+ var MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX = 1024 * 1024;
2839
+ var MAX_DEPTH = 8;
2840
+ var MAX_ITEMS = 256;
2841
+ var ROUNDING_MARGIN = 1;
2842
+ var fail = (refusal) => ({ ok: false, refusal });
2843
+ var VIEWDATA = /<script\b[^>]*\bid\s*=\s*["']viewdata["'][^>]*>([\s\S]*?)<\/script\s*>/gi;
2844
+ var ANGULAR_PREFIX = /^\)\]\}',?\n/;
2845
+ function parseJson(text) {
2846
+ try {
2847
+ return JSON.parse(text);
2848
+ }
2849
+ catch {
2850
+ return void 0;
2851
+ }
2852
+ }
2853
+ function fastspringState(format, text) {
2854
+ if (format === "html") {
2855
+ const scripts = [...text.matchAll(VIEWDATA)];
2856
+ if (scripts.length !== 1)
2857
+ return { refusal: "the checkout page does not hold exactly one order record" };
2858
+ const state2 = parseJson(scripts[0][1]);
2859
+ return isPlainObject(state2) ? { state: state2 } : { refusal: "the checkout page's order record is not a JSON object" };
2860
+ }
2861
+ const state = parseJson(text.replace(ANGULAR_PREFIX, ""));
2862
+ if (!isPlainObject(state))
2863
+ return { none: true };
2864
+ return isPlainObject(state.order) && isPlainObject(state.session) ? { state } : { none: true };
2865
+ }
2866
+ function selectedRenewals(groups, depth, counter) {
2867
+ if (!Array.isArray(groups) || depth > MAX_DEPTH)
2868
+ return null;
2869
+ const out = [];
2870
+ for (const group of groups) {
2871
+ if (!isPlainObject(group) || group.items !== void 0 && !Array.isArray(group.items))
2872
+ return null;
2873
+ for (const item of group.items ?? []) {
2874
+ counter.items += 1;
2875
+ if (!isPlainObject(item) || counter.items > MAX_ITEMS)
2876
+ return null;
2877
+ const nested = selectedRenewals(item.groups ?? [], depth + 1, counter);
2878
+ if (nested === null)
2879
+ return null;
2880
+ out.push({ selected: item.selected === true, future: item.future ? true : null, groups: nested });
2881
+ }
2882
+ }
2883
+ return [{ items: out }];
2884
+ }
2885
+ function fastspringProjection(state) {
2886
+ const order = state.order;
2887
+ if (!isPlainObject(order))
2888
+ return null;
2889
+ const groups = selectedRenewals(order.groups ?? [], 0, { items: 0 });
2890
+ if (groups === null)
2891
+ return null;
2892
+ return {
2893
+ order: { totalWithTaxValue: order.totalWithTaxValue, currency: order.currency, groups },
2894
+ production: state.production,
2895
+ hasRecurring: state.hasRecurring === void 0 ? false : state.hasRecurring,
2896
+ forceFuture: state.forceFuture === void 0 ? false : state.forceFuture
2897
+ };
2898
+ }
2899
+ function anySelectedRenewal(groups) {
2900
+ return groups.some((group) => group.items.some((item) => item.selected && item.future === true || anySelectedRenewal(item.groups)));
2901
+ }
2902
+ function minorUnits(value) {
2903
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
2904
+ return null;
2905
+ const match = /^(\d{1,12})(?:\.(\d{1,2}))?$/.exec(String(value));
2906
+ return match ? Number(match[1]) * 100 + Number((match[2] ?? "").padEnd(2, "0")) : null;
2907
+ }
2908
+ function fastspringFacts(state, merchant) {
2909
+ const unreadable = `${merchant}'s order record is not in the layout Agentcard reviewed`;
2910
+ const projection = isPlainObject(state) ? fastspringProjection(state) : null;
2911
+ if (!projection || JSON.stringify(projection) !== JSON.stringify(state))
2912
+ return fail(unreadable);
2913
+ const { totalWithTaxValue, currency, groups } = projection.order;
2914
+ if (typeof currency !== "string" || !/^[A-Z]{3}$/.test(currency))
2915
+ return fail(unreadable);
2916
+ if (!TWO_DECIMAL_CURRENCIES.has(currency.toLowerCase()))
2917
+ return fail(`${merchant}'s order is in a currency this build does not read`);
2918
+ const amountCents = minorUnits(totalWithTaxValue);
2919
+ if (amountCents === null)
2920
+ return fail(`${merchant}'s order total is not a whole number of cents`);
2921
+ if (typeof projection.production !== "boolean" || typeof projection.hasRecurring !== "boolean" || typeof projection.forceFuture !== "boolean") {
2922
+ return fail(unreadable);
2923
+ }
2924
+ const recurring = projection.hasRecurring || projection.forceFuture || anySelectedRenewal(groups);
2925
+ return { ok: true, facts: { amount: { amountCents, currency: currency.toLowerCase() }, live: projection.production, recurring } };
2926
+ }
2927
+ var READERS = /* @__PURE__ */ Object.freeze({
2928
+ fastspring_session: Object.freeze({ state: fastspringState, project: fastspringProjection, facts: fastspringFacts })
2929
+ });
2930
+ var MERCHANT_CARD_ENDPOINT_FACT_READERS = /* @__PURE__ */ Object.freeze(Object.keys(READERS));
2931
+ function cardPlaceholders(p, cardUrl) {
2932
+ const match = urlTemplateMatch({ path: p.path, query: p.query }, cardUrl);
2933
+ return match && match.origin === p.endpoint ? match.placeholders : null;
2934
+ }
2935
+ function boundShape(p, card, shapes, exchange) {
2936
+ const bind = p.facts.bind;
2937
+ for (const shape of shapes) {
2938
+ if (shape.method !== exchange.method.toUpperCase())
2939
+ continue;
2940
+ const match = urlTemplateMatch(shape, exchange.url);
2941
+ if (match && typeof card[bind] === "string" && match.placeholders[bind] === card[bind])
2942
+ return shape;
2943
+ }
2944
+ return null;
2945
+ }
2946
+ function shapeFor(p, cardUrl, exchange) {
2947
+ if (!p.facts || !isPlainObject(exchange) || typeof exchange.method !== "string" || typeof exchange.url !== "string")
2948
+ return null;
2949
+ const card = cardPlaceholders(p, cardUrl);
2950
+ const url = strictUrl(exchange.url);
2951
+ if (!card || !url || url.origin !== p.endpoint)
2952
+ return null;
2953
+ return boundShape(p, card, p.facts.responses, exchange);
2954
+ }
2955
+ function sessionPath(p, card) {
2956
+ const segments = p.path.split("/");
2957
+ const at = segments.indexOf(`:${p.facts.bind}`);
2958
+ if (at < 1)
2959
+ return null;
2960
+ const written = segments.slice(0, at + 1).map((segment) => segment.startsWith(":") ? card[segment.slice(1)] : segment);
2961
+ return written.every((segment) => typeof segment === "string") ? written.join("/") : null;
2962
+ }
2963
+ function merchantCardEndpointFactsRole(profile, cardUrl, exchange) {
2964
+ const p = trustedMerchantCardEndpoint(profile);
2965
+ if (!p.facts || !isPlainObject(exchange) || typeof exchange.method !== "string" || typeof exchange.url !== "string")
2966
+ return null;
2967
+ const card = cardPlaceholders(p, cardUrl);
2968
+ const session = card ? sessionPath(p, card) : null;
2969
+ let url;
2970
+ try {
2971
+ url = new URL(exchange.url);
2972
+ }
2973
+ catch {
2974
+ return null;
2975
+ }
2976
+ if (!session || url.origin !== p.endpoint || url.pathname !== session && !url.pathname.startsWith(`${session}/`))
2977
+ return null;
2978
+ if (exchange.method.toUpperCase() === p.method && exchange.url === cardUrl)
2979
+ return "card";
2980
+ if (shapeFor(p, cardUrl, exchange))
2981
+ return "record";
2982
+ if (boundShape(p, card, p.facts.inert, exchange))
2983
+ return "inert";
2984
+ return "unread";
2985
+ }
2986
+ function projectMerchantCardEndpointFacts(profile, cardUrl, exchange) {
2987
+ const p = trustedMerchantCardEndpoint(profile);
2988
+ const shape = shapeFor(p, cardUrl, exchange);
2989
+ if (!shape)
2990
+ return { refusal: `the response is not ${p.merchant}'s record of this checkout` };
2991
+ if (!Number.isInteger(exchange.status) || exchange.status < 200 || exchange.status > 299)
2992
+ return { refusal: `${p.merchant}'s record of this checkout did not load` };
2993
+ if (typeof exchange.body !== "string" || exchange.body.length > MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX)
2994
+ return { refusal: `${p.merchant}'s record of this checkout could not be read` };
2995
+ const reader = READERS[p.facts.reader];
2996
+ const read = reader.state(shape.format, exchange.body);
2997
+ if (read.none)
2998
+ return { none: true };
2999
+ if (read.refusal)
3000
+ return { refusal: read.refusal };
3001
+ const state = reader.project(read.state);
3002
+ if (!state)
3003
+ return { refusal: `${p.merchant}'s order record is not in the layout Agentcard reviewed` };
3004
+ return { record: { method: shape.method, url: exchange.url, state } };
3005
+ }
3006
+ function readMerchantCardEndpointFacts(profile, cardUrl, record) {
3007
+ const p = trustedMerchantCardEndpoint(profile);
3008
+ if (!p.facts)
3009
+ return fail(`Agentcard reads no order record for ${p.merchant}`);
3010
+ if (!isPlainObject(record) || Object.keys(record).sort().join(",") !== "method,state,url")
3011
+ return fail(`the order record for ${p.merchant} is malformed`);
3012
+ if (!shapeFor(p, cardUrl, record))
3013
+ return fail(`the order record is not ${p.merchant}'s record of this checkout`);
3014
+ return READERS[p.facts.reader].facts(record.state, p.merchant);
3015
+ }
3016
+ function merchantCardEndpointFactsRefusal(profile, facts, agent) {
3017
+ const p = trustedMerchantCardEndpoint(profile);
3018
+ const refuse2 = (code, reason) => ({ code, reason });
3019
+ if (!isPlainObject(facts) || !isPlainObject(facts.amount) || !isPlainObject(agent))
3020
+ return refuse2("malformed", `the order record for ${p.merchant} is malformed`);
3021
+ if (facts.recurring !== false)
3022
+ return refuse2("recurring", `${p.merchant}'s order renews on a schedule, and each renewal would charge the card with no approval of its own`);
3023
+ if (facts.live !== agent.live)
3024
+ return agent.live ? refuse2("test_checkout", `${p.merchant}'s checkout is in test mode`) : refuse2("live_checkout", `${p.merchant}'s checkout is live`);
3025
+ if (typeof agent.currency !== "string" || facts.amount.currency !== agent.currency.toLowerCase()) {
3026
+ return refuse2("currency", `${p.merchant}'s order is in another currency than the one approved`);
3027
+ }
3028
+ if (!Number.isSafeInteger(agent.amountCents) || Math.abs(facts.amount.amountCents - agent.amountCents) > ROUNDING_MARGIN) {
3029
+ return refuse2("amount", `${p.merchant}'s own total is not the amount the cardholder would be asked to approve`);
3030
+ }
3031
+ return null;
3032
+ }
3033
+ // src/merchant-card-endpoints.js
3034
+ var MERCHANT_CARD_ENDPOINT_LIMITS = /* @__PURE__ */ Object.freeze({
3035
+ bodyBytes: 128 * 1024,
3036
+ answerBytes: 256 * 1024,
3037
+ urlLength: 8192,
3038
+ headerValueBytes: 8 * 1024,
3039
+ headerBytes: 32 * 1024,
3040
+ cookies: 64
3041
+ });
3042
+ var NEVER_FROM_CAPTURE = /^(?:cookie|cookie2|set-cookie|origin|referer|host|content-length|transfer-encoding|connection|keep-alive|upgrade|te|trailer|expect|proxy-.*|:.*)$/;
3043
+ var PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
3044
+ var COOKIE_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,256}$/;
3045
+ var COOKIE_VALUE = /^(?:[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*|"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*")$/;
3046
+ var BOUNDARY = /^[A-Za-z0-9'()+_,\-./:=?]{1,70}$/;
3047
+ var BUNDLE_MEMBERS = /* @__PURE__ */ new Set(["url", "page_origin", "navigation", "headers", "cookies", "referer", "body"]);
3048
+ var SHAPES = /* @__PURE__ */ Object.freeze({ guid: /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/ });
3049
+ var NAVIGATION_FORM_TYPE = "application/x-www-form-urlencoded";
3050
+ function utf8Length(text) {
3051
+ let bytes = 0;
3052
+ for (let i = 0; i < text.length; i += 1) {
3053
+ const unit = text.charCodeAt(i);
3054
+ if (unit < 128)
3055
+ bytes += 1;
3056
+ else if (unit < 2048)
3057
+ bytes += 2;
3058
+ else if (unit >= 55296 && unit <= 56319 && i + 1 < text.length) {
3059
+ bytes += 4;
3060
+ i += 1;
3061
+ }
3062
+ else
3063
+ bytes += 3;
3064
+ }
3065
+ return bytes;
3066
+ }
3067
+ function cardBrand(number) {
3068
+ if (number.startsWith("4"))
3069
+ return "visa";
3070
+ const two = Number(number.slice(0, 2));
3071
+ const four = Number(number.slice(0, 4));
3072
+ if (two >= 51 && two <= 55 || four >= 2221 && four <= 2720)
3073
+ return "mastercard";
3074
+ if (/^3[47]/.test(number))
3075
+ return "amex";
3076
+ if (/^(?:6011|65|64[4-9])/.test(number))
3077
+ return "discover";
3078
+ return null;
3079
+ }
3080
+ function cardFacts(card) {
3081
+ if (!isPlainObject(card))
3082
+ throw new SubstitutionError("the card is missing");
3083
+ const number = typeof card.number === "string" ? card.number : "";
3084
+ if (!/^\d{12,19}$/.test(number))
3085
+ throw new SubstitutionError("the card number is not 12 to 19 digits");
3086
+ const cvc = typeof card.cvc === "string" ? card.cvc : "";
3087
+ if (!/^\d{3,4}$/.test(cvc))
3088
+ throw new SubstitutionError("the card security code is not 3 or 4 digits");
3089
+ const monthText = typeof card.month === "number" || typeof card.month === "string" ? String(card.month) : "";
3090
+ if (!/^\d{1,2}$/.test(monthText) || Number(monthText) < 1 || Number(monthText) > 12)
3091
+ throw new SubstitutionError("the card expiry month is not 1 to 12");
3092
+ const yearText = typeof card.year === "number" || typeof card.year === "string" ? String(card.year) : "";
3093
+ if (!/^(?:\d{2}|\d{4})$/.test(yearText))
3094
+ throw new SubstitutionError("the card expiry year is not 2 or 4 digits");
3095
+ const year = yearText.length === 2 ? 2e3 + Number(yearText) : Number(yearText);
3096
+ return { number, cvc, month: Number(monthText), year, brand: cardBrand(number) };
3097
+ }
3098
+ var digitForms = (digit) => `(?:${digit}|\\\\u003${digit}|%3${digit}|&#0*${48 + Number(digit)};|&#x0*3${digit};)`;
3099
+ var SEPARATOR = String.raw `(?:\\(?:[/\\"bfnrt]|u(?:00(?:[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f])|0[1-9a-f][0-9a-f]{2}|[1-9a-f][0-9a-f]{3}))` + String.raw `|%(?:[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f])` + String.raw `|&(?:#0*(?:[0-9]|[1-3][0-9]|4[0-7]|5[89]|6[0-4]|9[1-6]|12[3-9]|1[3-9][0-9]|[2-9][0-9]{2}|[1-9][0-9]{3,6})|#x0*(?:[0-9a-f]|[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f]|[1-9a-f][0-9a-f]{2,5})|[a-z][a-z0-9]{1,31});` + String.raw `|[^0-9A-Za-z\\%&]|%(?![0-9a-f]{2})|&(?!#?x?[0-9a-z]+;))`;
3100
+ function digitRun(digits) {
3101
+ return new RegExp([...digits].map(digitForms).join(`${SEPARATOR}*`), "gi");
3102
+ }
3103
+ function countRuns(text, digits) {
3104
+ return (text.match(digitRun(digits)) ?? []).length;
3105
+ }
3106
+ var CODE_KEY = /(?:cvv2?|cvc2?|cvn|csc|cid|ccv|security[\s_-]?code|card[\s_-]?code|verification[\s_-]?(?:code|value))(?:["']|%22|\\")?\s*(?::|=|%3[aAdD])\s*(?:["']|%22|\\")?\s*(\d{3,4})(?!\d)/gi;
3107
+ var CODE_NAME = /(?:cvv2?|cvc2?|cvn|csc|cid(?![a-z])|ccv|security[\s_-]?code|card[\s_-]?code|verification[\s_-]?(?:code|value))/gi;
3108
+ var CODE_WINDOW = 96;
3109
+ function codeRun(cvc) {
3110
+ return new RegExp(`(?:^|[^0-9])${[...cvc].map(digitForms).join("")}(?![0-9])`, "i");
3111
+ }
3112
+ function codeNearKey(text, code) {
3113
+ for (const key of text.matchAll(CODE_NAME)) {
3114
+ const from = Math.max(0, key.index - CODE_WINDOW);
3115
+ const to = Math.min(text.length, key.index + key[0].length + CODE_WINDOW);
3116
+ let near = text.slice(from, to);
3117
+ if (from > 0 && /[0-9]/.test(text[from - 1]))
3118
+ near = near.replace(/^[0-9]+/, "");
3119
+ if (to < text.length && /[0-9]/.test(text[to]))
3120
+ near = near.replace(/[0-9]+$/, "");
3121
+ if (code.test(near))
3122
+ return true;
3123
+ for (const [run] of near.replace(BASE64_ESCAPE, unescapeBase64).matchAll(CODE_BASE64_RUN)) {
3124
+ for (let skip = 0; skip < 4; skip += 1)
3125
+ if (code.test(base64Bytes(run, skip).replace(/\0/g, "")))
3126
+ return true;
3127
+ }
3128
+ }
3129
+ return false;
3130
+ }
3131
+ function onlyOnce(variants, name) {
3132
+ if (variants === 0)
3133
+ return `${name} is missing`;
3134
+ if (variants > 1)
3135
+ return `${name} appears more than once`;
3136
+ return null;
3137
+ }
3138
+ function multipartBoundary(contentType) {
3139
+ const match = typeof contentType === "string" ? /^multipart\/form-data;\s*boundary=(?:"([^"]*)"|([^\s;"]*))$/i.exec(contentType) : null;
3140
+ const boundary = match ? match[1] ?? match[2] : null;
3141
+ return boundary && BOUNDARY.test(boundary) ? boundary : null;
3142
+ }
3143
+ function jsonBody(p) {
3144
+ return p.encoding === "json" || p.encoding === "json_api";
3145
+ }
3146
+ function contentTypeRefusal(p, contentType) {
3147
+ if (typeof contentType !== "string")
3148
+ return `the card request to ${p.merchant} names no content type`;
3149
+ const ok = p.encoding === "json" ? /^application\/json(?:;\s*charset=utf-8)?$/i.test(contentType) : p.encoding === "json_api" ? /^application\/vnd\.api\+json(?:;\s*charset=utf-8)?$/i.test(contentType) : p.encoding === "form" ? /^application\/x-www-form-urlencoded(?:;\s*charset=utf-8)?$/i.test(contentType) : multipartBoundary(contentType) !== null;
3150
+ return ok ? null : `the card request to ${p.merchant} is not sent as ${p.encoding}`;
3151
+ }
3152
+ function merchantCardEndpointRequestOrigin(profile) {
3153
+ const p = trustedMerchantCardEndpoint(profile);
3154
+ return p.frame ?? p.checkout;
3155
+ }
3156
+ function forbiddenKeyIn(value, depth = 0) {
3157
+ if (depth > 64)
3158
+ return true;
3159
+ if (Array.isArray(value))
3160
+ return value.some((item) => forbiddenKeyIn(item, depth + 1));
3161
+ if (isPlainObject(value))
3162
+ return Object.keys(value).some((key) => FORBIDDEN_KEYS.has(key) || forbiddenKeyIn(value[key], depth + 1));
3163
+ return false;
3164
+ }
3165
+ function parseForm(body) {
3166
+ const fields = [];
3167
+ if (body === "")
3168
+ return { fields };
3169
+ let offset = 0;
3170
+ for (const pair of body.split("&")) {
3171
+ const mark = pair.indexOf("=");
3172
+ if (mark <= 0)
3173
+ return { refusal: "the paused form body has a field with no name or no value" };
3174
+ try {
3175
+ fields.push({
3176
+ name: decodeURIComponent(pair.slice(0, mark).replace(/\+/g, " ")),
3177
+ value: decodeURIComponent(pair.slice(mark + 1).replace(/\+/g, " ")),
3178
+ start: offset + mark + 1,
3179
+ end: offset + pair.length
3180
+ });
3181
+ }
3182
+ catch {
3183
+ return { refusal: "the paused form body is not valid form encoding" };
3184
+ }
3185
+ offset += pair.length + 1;
3186
+ }
3187
+ return { fields };
3188
+ }
3189
+ function parseMultipart(body, boundary) {
3190
+ const CRLF = "\r\n";
3191
+ const dash = `--${boundary}`;
3192
+ const delimiter = `${CRLF}${dash}`;
3193
+ if (!body.startsWith(`${dash}${CRLF}`))
3194
+ return { refusal: "the paused multipart body does not open with its declared boundary" };
3195
+ const fields = [];
3196
+ let position = dash.length + CRLF.length;
3197
+ for (;;) {
3198
+ const headerEnd = body.indexOf(`${CRLF}${CRLF}`, position);
3199
+ if (headerEnd === -1)
3200
+ return { refusal: "a part of the paused multipart body has no header end" };
3201
+ const start = headerEnd + 4;
3202
+ const end = body.indexOf(delimiter, start);
3203
+ if (end === -1)
3204
+ return { refusal: "a part of the paused multipart body is not closed by its boundary" };
3205
+ let name = null;
3206
+ for (const line of body.slice(position, headerEnd).split(CRLF)) {
3207
+ const colon = line.indexOf(":");
3208
+ const header = colon > 0 ? line.slice(0, colon).trim().toLowerCase() : "";
3209
+ const value = colon > 0 ? line.slice(colon + 1).trim() : "";
3210
+ if (header === "content-disposition" && name === null) {
3211
+ const disposition = /^form-data; name="([^"\\\r\n]*)"$/.exec(value);
3212
+ if (!disposition)
3213
+ return { refusal: "a part of the paused multipart body is not a plain form field" };
3214
+ name = disposition[1];
3215
+ }
3216
+ else if (header !== "content-type" || !/^text\/plain(?:;\s*charset=utf-8)?$/i.test(value)) {
3217
+ return { refusal: "a part of the paused multipart body is not a text field" };
3218
+ }
3219
+ }
3220
+ if (name === null)
3221
+ return { refusal: "a part of the paused multipart body names no field" };
3222
+ fields.push({ name, value: body.slice(start, end), start, end });
3223
+ position = end + delimiter.length;
3224
+ if (body.startsWith("--", position)) {
3225
+ const rest = body.slice(position + 2);
3226
+ return rest === "" || rest === CRLF ? { fields } : { refusal: "the paused multipart body carries bytes after its closing boundary" };
3227
+ }
3228
+ if (!body.startsWith(CRLF, position))
3229
+ return { refusal: "a boundary in the paused multipart body is not followed by a line break" };
3230
+ position += CRLF.length;
3231
+ }
3232
+ }
3233
+ function sameName(name, wanted) {
3234
+ return !FORBIDDEN_KEYS.has(name) && (name.toLowerCase() === wanted.toLowerCase() || name.toUpperCase() === wanted.toUpperCase());
3235
+ }
3236
+ function isSelector(step) {
3237
+ if (!isPlainObject(step))
3238
+ return false;
3239
+ const keys = Object.keys(step);
3240
+ return keys.length === 1 && !FORBIDDEN_KEYS.has(keys[0]) && typeof step[keys[0]] === "string" && step[keys[0]] !== "";
3241
+ }
3242
+ var hasSelector = (at) => Array.isArray(at) && at.some(isSelector);
3243
+ function selectAt(root, at) {
3244
+ let node = root;
3245
+ const path = [];
3246
+ for (const step of at) {
3247
+ if (isSelector(step)) {
3248
+ const [[name, wanted]] = Object.entries(step);
3249
+ if (!Array.isArray(node))
3250
+ return { found: false };
3251
+ const hits = [];
3252
+ node.forEach((item, index) => {
3253
+ if (isPlainObject(item) && Object.prototype.hasOwnProperty.call(item, name) && item[name] === wanted)
3254
+ hits.push(index);
3255
+ });
3256
+ if (hits.length !== 1)
3257
+ return { found: false };
3258
+ path.push(hits[0]);
3259
+ node = node[hits[0]];
3260
+ }
3261
+ else {
3262
+ const next = jsonAt(node, [step]);
3263
+ if (!next.found)
3264
+ return { found: false };
3265
+ path.push(step);
3266
+ node = next.value;
3267
+ }
3268
+ }
3269
+ return { found: true, value: node, path };
3270
+ }
3271
+ function selectVariants(root, at) {
3272
+ let nodes = [root];
3273
+ for (const step of at) {
3274
+ if (!isSelector(step)) {
3275
+ nodes = nodes.flatMap((node) => valuesIgnoringCase(node, [step]));
3276
+ continue;
3277
+ }
3278
+ const [[name, wanted]] = Object.entries(step);
3279
+ const next = [];
3280
+ for (const node of nodes) {
3281
+ if (!Array.isArray(node))
3282
+ continue;
3283
+ for (const item of node) {
3284
+ if (isPlainObject(item) && Object.keys(item).some((key) => sameName(key, name) && typeof item[key] === "string" && (item[key].toLowerCase() === wanted.toLowerCase() || item[key].toUpperCase() === wanted.toUpperCase())))
3285
+ next.push(item);
3286
+ }
3287
+ }
3288
+ nodes = next;
3289
+ }
3290
+ return nodes;
3291
+ }
3292
+ function describeAt(at) {
3293
+ if (!hasSelector(at))
3294
+ return describePath(at);
3295
+ return at.map((step) => isSelector(step) ? `[${Object.keys(step)[0]}=${Object.values(step)[0]}]` : typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "");
3296
+ }
3297
+ function jsonReader(parsed) {
3298
+ return {
3299
+ parsed,
3300
+ exact: (at) => hasSelector(at) ? selectAt(parsed, at) : jsonAt(parsed, at),
3301
+ variants: (at) => hasSelector(at) ? selectVariants(parsed, at) : valuesIgnoringCase(parsed, at),
3302
+ label: describeAt,
3303
+ prefixed: (prefix) => Object.keys(parsed).filter((key) => startsWithIgnoringCase(key, prefix)).map((key) => parsed[key]),
3304
+ scalars: () => jsonScalars(parsed)
3305
+ };
3306
+ }
3307
+ function fieldReader(fields) {
3308
+ return {
3309
+ fields,
3310
+ exact: (at) => {
3311
+ const hits = fields.filter((field) => field.name === at);
3312
+ return hits.length === 1 ? { found: true, value: hits[0].value, field: hits[0] } : { found: false };
3313
+ },
3314
+ variants: (at) => fields.filter((field) => sameName(field.name, at)).map((field) => field.value),
3315
+ label: (at) => at,
3316
+ prefixed: (prefix) => fields.filter((field) => startsWithIgnoringCase(field.name, prefix)).map((field) => field.value),
3317
+ scalars: () => fields.map((field) => field.value)
3318
+ };
3319
+ }
3320
+ function startsWithIgnoringCase(name, prefix) {
3321
+ return !FORBIDDEN_KEYS.has(name) && name.length >= prefix.length && sameName(name.slice(0, prefix.length), prefix);
3322
+ }
3323
+ function jsonScalars(value, out = [], depth = 0) {
3324
+ if (depth > 64)
3325
+ return out;
3326
+ if (typeof value === "string" || typeof value === "number")
3327
+ out.push(value);
3328
+ else if (Array.isArray(value))
3329
+ for (const item of value)
3330
+ jsonScalars(item, out, depth + 1);
3331
+ else if (isPlainObject(value))
3332
+ for (const key of Object.keys(value))
3333
+ jsonScalars(value[key], out, depth + 1);
3334
+ return out;
3335
+ }
3336
+ function readBody(p, body, contentType) {
3337
+ if (typeof body !== "string")
3338
+ return { refusal: "the paused body is not text" };
3339
+ if (utf8Length(body) > MERCHANT_CARD_ENDPOINT_LIMITS.bodyBytes)
3340
+ return { refusal: "the paused body is larger than a card request may be" };
3341
+ const typeRefusal = contentTypeRefusal(p, contentType);
3342
+ if (typeRefusal)
3343
+ return { refusal: typeRefusal };
3344
+ if (jsonBody(p)) {
3345
+ let parsed2;
3346
+ try {
3347
+ parsed2 = JSON.parse(body);
3348
+ }
3349
+ catch {
3350
+ return { refusal: "the paused body is not JSON" };
3351
+ }
3352
+ if (!isPlainObject(parsed2))
3353
+ return { refusal: "the paused body is not a JSON object" };
3354
+ if (!roundTrips(parsed2, body))
3355
+ return { refusal: "the paused body does not round-trip through JSON, so its rules cannot be read" };
3356
+ if (forbiddenKeyIn(parsed2))
3357
+ return { refusal: "the paused body names a prototype key" };
3358
+ return { reader: jsonReader(parsed2) };
3359
+ }
3360
+ const parsed = p.encoding === "form" ? parseForm(body) : parseMultipart(body, multipartBoundary(contentType));
3361
+ if (parsed.refusal)
3362
+ return parsed;
3363
+ if (parsed.fields.some((field) => FORBIDDEN_KEYS.has(field.name)))
3364
+ return { refusal: "the paused body names a prototype key" };
3365
+ return { reader: fieldReader(parsed.fields) };
3366
+ }
3367
+ var SLOT_NAMES = /* @__PURE__ */ Object.freeze({
3368
+ pan: "the card number",
3369
+ cvv: "the security code",
3370
+ exp_month: "the expiry month",
3371
+ exp_year: "the expiry year",
3372
+ expiry: "the expiry date",
3373
+ last4: "the card number's last four digits",
3374
+ leading: "the card number's leading digits",
3375
+ brand: "the card network code"
3376
+ });
3377
+ var EXPIRY_FORMATS = /* @__PURE__ */ Object.freeze({
3378
+ mmyy: /^(?:0[1-9]|1[0-2])\d{2}$/,
3379
+ "mm/yy": /^(?:0[1-9]|1[0-2])\/\d{2}$/,
3380
+ "mm/yyyy": /^(?:0[1-9]|1[0-2])\/\d{4}$/,
3381
+ "yyyy-mm": /^\d{4}-(?:0[1-9]|1[0-2])$/,
3382
+ "MM/YY": /^(?:0[1-9]|1[0-2])\/\d{2}$/,
3383
+ "MM-YY": /^(?:0[1-9]|1[0-2])-\d{2}$/,
3384
+ MMYYYY: /^(?:0[1-9]|1[0-2])\d{4}$/
3385
+ });
3386
+ var AS_TYPED_EXPIRY_FORMATS = /* @__PURE__ */ Object.freeze(["mmyy", "mm/yy", "mm/yyyy", "yyyy-mm"]);
3387
+ function expiryForm(spec, value) {
3388
+ if (typeof value !== "string")
3389
+ return null;
3390
+ const forms = spec.format === "as_typed" ? AS_TYPED_EXPIRY_FORMATS : [spec.format];
3391
+ return forms.find((form) => Object.prototype.hasOwnProperty.call(EXPIRY_FORMATS, form) && EXPIRY_FORMATS[form].test(value)) ?? null;
3392
+ }
3393
+ function expiryText(form, facts) {
3394
+ const mm = String(facts.month).padStart(2, "0");
3395
+ const yy = String(facts.year % 100).padStart(2, "0");
3396
+ if (form === "mmyy")
3397
+ return `${mm}${yy}`;
3398
+ if (form === "mm/yy" || form === "MM/YY")
3399
+ return `${mm}/${yy}`;
3400
+ if (form === "MM-YY")
3401
+ return `${mm}-${yy}`;
3402
+ if (form === "mm/yyyy")
3403
+ return `${mm}/${facts.year}`;
3404
+ if (form === "MMYYYY")
3405
+ return `${mm}${facts.year}`;
3406
+ return `${facts.year}-${mm}`;
3407
+ }
3408
+ function cardSlots(p) {
3409
+ const c = p.card;
3410
+ const expiry = c.expiry ? [["expiry", c.expiry]] : [["exp_month", c.exp_month], ["exp_year", c.exp_year]];
3411
+ return [
3412
+ ["pan", c.pan],
3413
+ ["cvv", c.cvv],
3414
+ ...expiry,
3415
+ ["last4", c.last4],
3416
+ ["leading", c.leading],
3417
+ ...c.brands.map((brand) => ["brand", brand])
3418
+ ].filter(([, spec]) => spec);
3419
+ }
3420
+ function codeNotAsked(reader, spec, variants) {
3421
+ if (spec.format !== "digits_if_asked")
3422
+ return false;
3423
+ if (variants.length === 0)
3424
+ return true;
3425
+ const at = reader.exact(spec.at);
3426
+ return variants.length === 1 && at.found && at.value === "";
3427
+ }
3428
+ function groupedFour(digits) {
3429
+ return digits.replace(/(.{4})/g, "$1 ").trim();
3430
+ }
3431
+ function panDigits(spec, value) {
3432
+ return spec.format === "grouped4" && typeof value === "string" ? value.replace(/ /g, "") : value;
3433
+ }
3434
+ function placeholderFits(p, slot, spec, value) {
3435
+ if (spec.format === "encrypted")
3436
+ return encryptedValueFits(p.merchant_key, value);
3437
+ const text = typeof value === "string";
3438
+ const int = Number.isInteger(value);
3439
+ switch (slot) {
3440
+ case "leading":
3441
+ return text && value.length === spec.digits && /^\d+$/.test(value);
3442
+ case "pan": {
3443
+ if (!text)
3444
+ return false;
3445
+ const digits = panDigits(spec, value);
3446
+ return /^\d{12,19}$/.test(digits) && (digits === value || value === groupedFour(digits));
3447
+ }
3448
+ case "cvv":
3449
+ return text && /^\d{3,4}$/.test(value);
3450
+ case "exp_month":
3451
+ if (spec.format === "mm")
3452
+ return text && /^(?:0[1-9]|1[0-2])$/.test(value);
3453
+ if (spec.format === "m")
3454
+ return text && /^(?:[1-9]|1[0-2])$/.test(value);
3455
+ if (spec.format === "m_int")
3456
+ return int && value >= 1 && value <= 12;
3457
+ return spec.format === "as_typed" && (text && /^(?:0?[1-9]|1[0-2])$/.test(value) || int && value >= 1 && value <= 12);
3458
+ case "exp_year":
3459
+ if (spec.format === "yyyy")
3460
+ return text && /^\d{4}$/.test(value);
3461
+ if (spec.format === "yyyy_int")
3462
+ return int && value >= 1e3 && value <= 9999;
3463
+ if (spec.format === "yy_int")
3464
+ return int && value >= 0 && value <= 99;
3465
+ return spec.format === "as_typed" && (text && /^(?:\d{2}|\d{4})$/.test(value) || int && (value >= 0 && value <= 99 || value >= 1e3 && value <= 9999));
3466
+ case "expiry":
3467
+ return expiryForm(spec, value) !== null;
3468
+ case "last4":
3469
+ return text && /^\d{4}$/.test(value);
3470
+ case "brand":
3471
+ return spec.map ? Object.values(spec.map).includes(value) : text && value !== "";
3472
+ default:
3473
+ return false;
3474
+ }
3475
+ }
3476
+ var DECIMAL_AMOUNT = /^\d{1,9}(?:\.\d{1,2})?$/;
3477
+ function isNothing(value) {
3478
+ return value === null || value === "" || Array.isArray(value) && value.length === 0 || isPlainObject(value) && Object.keys(value).length === 0;
3479
+ }
3480
+ function clearCardNumber(texts) {
3481
+ for (const text of texts) {
3482
+ for (const [run] of text.matchAll(/[0-9](?:[ -]?[0-9])*/g)) {
3483
+ const digits = run.replace(/[ -]/g, "");
3484
+ if (digits.length < 13 || digits.length > 19 || !/^[2-6]/.test(digits))
3485
+ continue;
3486
+ let sum = 0;
3487
+ for (let i = 0; i < digits.length; i += 1) {
3488
+ let digit = Number(digits[digits.length - 1 - i]);
3489
+ if (i % 2 === 1)
3490
+ digit = digit * 2 > 9 ? digit * 2 - 9 : digit * 2;
3491
+ sum += digit;
3492
+ }
3493
+ if (sum % 10 === 0)
3494
+ return digits;
3495
+ }
3496
+ }
3497
+ return null;
3498
+ }
3499
+ var bodyTexts = (reader, body) => [body, ...reader.fields ? reader.fields.map((field) => field.value) : []];
3500
+ var pinnedKids = (merchantKey) => merchantKey.keys.map((key) => key.kid);
3501
+ function rulesRefusal(p, reader, body) {
3502
+ const request = `the paused body is not ${p.merchant}'s card request`;
3503
+ for (const [slot, spec] of cardSlots(p)) {
3504
+ const label = reader.label(spec.at);
3505
+ const variants = reader.variants(spec.at);
3506
+ if (slot === "cvv" && codeNotAsked(reader, spec, variants))
3507
+ continue;
3508
+ const once = onlyOnce(variants.length, label);
3509
+ if (once)
3510
+ return `${request}: ${once}`;
3511
+ const at = reader.exact(spec.at);
3512
+ if (!at.found)
3513
+ return `${request}: ${label} is not spelled as reviewed`;
3514
+ if (!placeholderFits(p, slot, spec, at.value))
3515
+ return `${request}: ${label} is not ${SLOT_NAMES[slot]} in the reviewed form`;
3516
+ }
3517
+ const merchantKey = p.merchant_key;
3518
+ if (merchantKey && merchantKey.scheme === "rsa_oaep" && merchantKey.kid_at) {
3519
+ const label = reader.label(merchantKey.kid_at);
3520
+ const once = onlyOnce(reader.variants(merchantKey.kid_at).length, label);
3521
+ if (once)
3522
+ return `${request}: ${once}`;
3523
+ const at = reader.exact(merchantKey.kid_at);
3524
+ const kids = pinnedKids(merchantKey);
3525
+ if (!at.found || typeof at.value !== "string" || at.value === "" || kids.length > 0 && !kids.includes(at.value)) {
3526
+ return `${request}: ${label} does not name a key Agentcard pinned for ${p.merchant}`;
3527
+ }
3528
+ }
3529
+ const typed = p.card.pan && ["digits", "grouped4"].includes(p.card.pan.format) ? panDigits(p.card.pan, reader.exact(p.card.pan.at).value) : null;
3530
+ if (typed !== null) {
3531
+ if (countRuns(body, typed) !== 1) {
3532
+ return `${request}: the card number appears outside ${reader.label(p.card.pan.at)}`;
3533
+ }
3534
+ }
3535
+ else if (clearCardNumber(bodyTexts(reader, body)) !== null) {
3536
+ return `${request}: a card number appears in the clear`;
3537
+ }
3538
+ if (p.card.last4 && (typed === null || reader.exact(p.card.last4.at).value !== typed.slice(-4))) {
3539
+ return `${request}: ${reader.label(p.card.last4.at)} is not the last four digits of ${p.card.pan ? reader.label(p.card.pan.at) : SLOT_NAMES.pan}`;
3540
+ }
3541
+ for (const rule of p.requires) {
3542
+ const label = reader.label(rule.at);
3543
+ const variants = reader.variants(rule.at);
3544
+ const at = reader.exact(rule.at);
3545
+ const holds = (value) => "equals" in rule ? value === rule.equals : "one_of" in rule ? rule.one_of.includes(value) : "matches" in rule ? typeof value === "string" && Object.prototype.hasOwnProperty.call(SHAPES, rule.matches) && SHAPES[rule.matches].test(value) : !isNothing(value);
3546
+ if (!at.found || variants.length !== 1 || !holds(at.value))
3547
+ return `${request}: ${label} is not what the review requires`;
3548
+ }
3549
+ for (const rule of p.refuse) {
3550
+ const values = "prefix" in rule ? reader.prefixed(rule.prefix) : reader.variants(rule.at);
3551
+ const fires = rule.when === "present" ? values.length > 0 : rule.when === "nonempty" ? values.some((value) => !isNothing(value)) : jsonBody(p) ? values.some((value) => value !== false && value !== null) : values.some((value) => value !== "" && value !== "false");
3552
+ if (fires)
3553
+ return `the paused body asks ${p.merchant} for more than this purchase: ${"prefix" in rule ? `a field starting ${rule.prefix}` : reader.label(rule.at)}`;
3554
+ }
3555
+ if (p.amount) {
3556
+ const label = reader.label(p.amount.at);
3557
+ const at = reader.exact(p.amount.at);
3558
+ const fits = p.amount.kind === "decimal_string" ? typeof at.value === "string" && DECIMAL_AMOUNT.test(at.value) : p.amount.kind === "decimal_number" ? jsonBody(p) && typeof at.value === "number" && DECIMAL_AMOUNT.test(String(at.value)) : jsonBody(p) ? Number.isSafeInteger(at.value) && at.value >= 0 : typeof at.value === "string" && /^\d{1,12}$/.test(at.value);
3559
+ if (!at.found || reader.variants(p.amount.at).length !== 1 || !fits)
3560
+ return `${request}: the amount at ${label} is not a ${p.amount.kind.replace("_", " ")}`;
3561
+ }
3562
+ if (isPlainObject(p.currency)) {
3563
+ const at = reader.exact(p.currency.at);
3564
+ if (!at.found || reader.variants(p.currency.at).length !== 1 || typeof at.value !== "string" || !/^[A-Z]{3}$/.test(at.value)) {
3565
+ return `${request}: ${reader.label(p.currency.at)} is not a currency code`;
3566
+ }
3567
+ }
3568
+ return null;
3569
+ }
3570
+ function merchantCardEndpointBodyRefusal(profile, body, contentType) {
3571
+ const p = trustedMerchantCardEndpoint(profile);
3572
+ const read = readBody(p, body, contentType);
3573
+ return read.refusal ?? rulesRefusal(p, read.reader, body);
3574
+ }
3575
+ function merchantCardEndpointPasses(profile, body, contentType) {
3576
+ const p = trustedMerchantCardEndpoint(profile);
3577
+ if (!p.payment_method)
3578
+ return false;
3579
+ const read = readBody(p, body, contentType);
3580
+ if (read.refusal)
3581
+ return false;
3582
+ const at = read.reader.exact(p.payment_method.at);
3583
+ if (read.reader.variants(p.payment_method.at).length !== 1 || !at.found || typeof at.value !== "string" || at.value === "")
3584
+ return false;
3585
+ const method = at.value.toLowerCase();
3586
+ return !p.payment_method.card.some((value) => value.toLowerCase() === method);
3587
+ }
3588
+ function merchantCardEndpointSessionTtl(profile, body, contentType) {
3589
+ const p = trustedMerchantCardEndpoint(profile);
3590
+ const read = readBody(p, body, contentType);
3591
+ const refusal = read.refusal ?? rulesRefusal(p, read.reader, body);
3592
+ if (refusal)
3593
+ throw new Error(`Refusing to pay: ${refusal}.`);
3594
+ if (!p.challenge)
3595
+ return null;
3596
+ return read.reader.variants(p.challenge.at).some((value) => !isNothing(value)) ? p.challenge.ttl_ms : null;
3597
+ }
3598
+ function merchantCardEndpointAmount(profile, body, contentType) {
3599
+ const p = trustedMerchantCardEndpoint(profile);
3600
+ const read = readBody(p, body, contentType);
3601
+ const refusal = read.refusal ?? rulesRefusal(p, read.reader, body);
3602
+ if (refusal)
3603
+ throw new Error(`Refusing to pay: ${refusal}.`);
3604
+ const currency = (typeof p.currency === "string" ? p.currency : read.reader.exact(p.currency.at).value).toLowerCase();
3605
+ if (!p.amount)
3606
+ return { amountCents: null, currency };
3607
+ const value = read.reader.exact(p.amount.at).value;
3608
+ if (p.amount.kind === "minor_int")
3609
+ return { amountCents: Number(value), currency };
3610
+ if (!TWO_DECIMAL_CURRENCIES.has(currency))
3611
+ throw new Error(`Refusing to pay: ${p.merchant}'s amount is in a currency this build does not read.`);
3612
+ const [whole, fraction = ""] = String(value).split(".");
3613
+ return { amountCents: Number(whole) * 100 + Number(fraction.padEnd(2, "0")), currency };
3614
+ }
3615
+ var WRITTEN_DIGIT = String.raw `(?:[0-9]|\\u003[0-9]|%3[0-9]|&#0*(?:4[89]|5[0-7]);|&#x0*3[0-9];)`;
3616
+ var WRITTEN_RUN = /* @__PURE__ */ new RegExp(`${WRITTEN_DIGIT}+(?:${SEPARATOR}+${WRITTEN_DIGIT}+)*`, "gi");
3617
+ var RUN_TOKEN = /* @__PURE__ */ new RegExp(`(${WRITTEN_DIGIT})|${SEPARATOR}`, "giy");
3618
+ function writtenDigit(token) {
3619
+ if (token.length === 1)
3620
+ return token;
3621
+ if (/^&#[0-9]/.test(token))
3622
+ return String.fromCharCode(Number(token.slice(2, -1)));
3623
+ return token.endsWith(";") ? token[token.length - 2] : token[token.length - 1];
3624
+ }
3625
+ function luhnValid(digits) {
3626
+ let sum = 0;
3627
+ for (let i = 0; i < digits.length; i += 1) {
3628
+ let digit = digits.charCodeAt(digits.length - 1 - i) - 48;
3629
+ if (i % 2 === 1) {
3630
+ digit *= 2;
3631
+ if (digit > 9)
3632
+ digit -= 9;
3633
+ }
3634
+ sum += digit;
3635
+ }
3636
+ return sum % 10 === 0;
3637
+ }
3638
+ function writesCardNumber(text) {
3639
+ for (const [run] of text.matchAll(WRITTEN_RUN)) {
3640
+ const groups = [];
3641
+ let group = "";
3642
+ for (const token of run.matchAll(RUN_TOKEN)) {
3643
+ if (token[1] !== void 0)
3644
+ group += writtenDigit(token[1]);
3645
+ else if (group !== "") {
3646
+ groups.push(group);
3647
+ group = "";
3648
+ }
3649
+ }
3650
+ if (group !== "")
3651
+ groups.push(group);
3652
+ for (let first = 0; first < groups.length; first += 1) {
3653
+ let digits = "";
3654
+ for (let last = first; last < groups.length && digits.length + groups[last].length <= 19; last += 1) {
3655
+ digits += groups[last];
3656
+ if (digits.length >= 12 && luhnValid(digits))
3657
+ return true;
3658
+ }
3659
+ }
3660
+ }
3661
+ return false;
3662
+ }
3663
+ function cardNumberValues(p, body, contentType) {
3664
+ const at = (p.card.pan ?? p.card.cvv).at;
3665
+ if (jsonBody(p)) {
3666
+ let parsed2;
3667
+ try {
3668
+ parsed2 = JSON.parse(body);
3669
+ }
3670
+ catch {
3671
+ return [];
3672
+ }
3673
+ if (!isPlainObject(parsed2))
3674
+ return [];
3675
+ return hasSelector(at) ? selectVariants(parsed2, at) : valuesIgnoringCase(parsed2, at);
3676
+ }
3677
+ const parsed = p.encoding === "form" ? parseForm(body) : parseMultipart(body, multipartBoundary(contentType));
3678
+ return parsed.refusal ? [] : parsed.fields.filter((field) => sameName(field.name, at)).map((field) => field.value);
3679
+ }
3680
+ function navigationPostCarriesCard(p, body, contentType) {
3681
+ const read = readBody(p, body, contentType);
3682
+ if (read.refusal)
3683
+ return true;
3684
+ const { reader } = read;
3685
+ if ([p.card.pan, p.card.cvv].some((spec) => spec && reader.variants(spec.at).some((value) => !isNothing(value))))
3686
+ return true;
3687
+ return reader.scalars().some((value) => (String(value).replace(/[\s.-]/g, "").match(/\d+/g) ?? []).some((run) => run.length >= 12 && run.length <= 19 && luhnValid(run)));
3688
+ }
3689
+ function merchantCardEndpointCarriesCard(profile, body, contentType) {
3690
+ const p = trustedMerchantCardEndpoint(profile);
3691
+ if (typeof body !== "string" || body === "" || utf8Length(body) > MERCHANT_CARD_ENDPOINT_LIMITS.bodyBytes)
3692
+ return true;
3693
+ if (cardNumberValues(p, body, contentType).some((value) => !isNothing(value)) || writesCardNumber(body))
3694
+ return true;
3695
+ return p.navigation !== null && navigationPostCarriesCard(p, body, contentType);
3696
+ }
3697
+ function endpointMatches(p, rawUrl, method, opts) {
3698
+ if (typeof rawUrl !== "string" || rawUrl.length > MERCHANT_CARD_ENDPOINT_LIMITS.urlLength)
3699
+ return false;
3700
+ if (typeof method !== "string" || method.toUpperCase() !== p.method)
3701
+ return false;
3702
+ if (rawUrl.includes("#"))
3703
+ return false;
3704
+ let url;
3705
+ try {
3706
+ url = new URL(rawUrl);
3707
+ }
3708
+ catch {
3709
+ return false;
3710
+ }
3711
+ if (url.href !== rawUrl || url.protocol !== "https:" || url.port || url.username || url.password)
3712
+ return false;
3713
+ if (url.origin !== p.endpoint)
3714
+ return false;
3715
+ if (opts?.checkoutOrigin !== void 0 && opts.checkoutOrigin !== p.checkout)
3716
+ return false;
3717
+ if (!pathMatchesTemplate(p.path, url.pathname))
3718
+ return false;
3719
+ const mark = rawUrl.indexOf("?");
3720
+ return queryPlaceholders(p.query, mark === -1 ? null : rawUrl.slice(mark + 1)) !== null;
3721
+ }
3722
+ function merchantCardEndpointMatches(profile, rawUrl, method, opts) {
3723
+ return endpointMatches(trustedMerchantCardEndpoint(profile), rawUrl, method, opts);
3724
+ }
3725
+ function matchMerchantCardEndpoint(rawUrl, method, opts) {
3726
+ return MERCHANT_CARD_ENDPOINT_CLIENT_TABLE.find((p) => endpointMatches(p, rawUrl, method, opts)) ?? null;
3727
+ }
3728
+ function matchMerchantCardEndpointResend(rawUrl, method) {
3729
+ if (typeof rawUrl !== "string" || rawUrl.length > MERCHANT_CARD_ENDPOINT_LIMITS.urlLength || typeof method !== "string")
3730
+ return null;
3731
+ const url = strictUrl(rawUrl);
3732
+ if (!url)
3733
+ return null;
3734
+ return MERCHANT_CARD_ENDPOINT_CLIENT_TABLE.find((p) => url.origin === p.endpoint && (p.resends ?? []).some((resend) => resend.method === method.toUpperCase() && urlTemplateMatch(resend, rawUrl) !== null)) ?? null;
3735
+ }
3736
+ function assertMerchantCardEndpoint(authorization) {
3737
+ if (!isPlainObject(authorization))
3738
+ throw new Error("Refusing to pay: malformed payment endpoint.");
3739
+ if (authorization.mode !== MERCHANT_CARD_ENDPOINT_MODE || authorization.psp !== MERCHANT_CARD_ENDPOINT_PSP) {
3740
+ throw new Error("Refusing to pay: this payment is not a reviewed merchant's own card request.");
3741
+ }
3742
+ const named = authorization.merchant_card_endpoint;
3743
+ if (!isPlainObject(named) || typeof named.profile !== "string")
3744
+ throw new Error("Refusing to pay: this payment names no merchant Agentcard reviewed.");
3745
+ const profile = trustedMerchantCardEndpoint(named.profile);
3746
+ let url;
3747
+ try {
3748
+ url = new URL(authorization.url);
3749
+ }
3750
+ catch {
3751
+ throw new Error("Refusing to pay: malformed payment endpoint.");
3752
+ }
3753
+ const method = authorization.method === void 0 ? "POST" : authorization.method;
3754
+ if (!endpointMatches(profile, authorization.url, method)) {
3755
+ throw new Error(`Refusing to pay: ${url.hostname} is not ${profile.merchant}'s reviewed card endpoint.`);
3756
+ }
3757
+ if (named.endpoint_host !== void 0 && named.endpoint_host !== url.hostname) {
3758
+ throw new Error(`Refusing to pay: this payment names a different host from ${url.hostname}, the one it pays.`);
3759
+ }
3760
+ if (named.page_origin !== profile.checkout) {
3761
+ throw new Error(`Refusing to pay: this payment was not started on ${profile.merchant}'s checkout page.`);
3762
+ }
3763
+ if (!merchantCardEndpointLive(profile))
3764
+ throw new Error(`Refusing to pay: Agentcard has not turned on payments to ${profile.merchant} yet.`);
3765
+ return Object.assign(url, { profile });
3766
+ }
3767
+ function refererKept(p, referer) {
3768
+ if (typeof referer !== "string" || referer.length > MERCHANT_CARD_ENDPOINT_LIMITS.headerValueBytes || !PRINTABLE_ASCII.test(referer) || referer.includes("#"))
3769
+ return false;
3770
+ let url;
3771
+ try {
3772
+ url = new URL(referer);
3773
+ }
3774
+ catch {
3775
+ return false;
3776
+ }
3777
+ return url.href === referer && url.origin === (p.frame ?? p.checkout) && !url.username && !url.password;
3778
+ }
3779
+ function lowerHeaders(headers) {
3780
+ const out = /* @__PURE__ */ Object.create(null);
3781
+ for (const [name, value] of Object.entries(headers)) {
3782
+ const lower = name.toLowerCase();
3783
+ if (Object.prototype.hasOwnProperty.call(out, lower))
3784
+ return null;
3785
+ out[lower] = value;
3786
+ }
3787
+ return out;
3788
+ }
3789
+ function cookieNameAllowed(p, name) {
3790
+ const names = p.session.cookie_names;
3791
+ if (names === null)
3792
+ return true;
3793
+ return names.some((entry) => entry.endsWith("*") ? name.startsWith(entry.slice(0, -1)) && name.length > entry.length - 1 : name === entry);
3794
+ }
3795
+ function sessionCookie(bundle, name) {
3796
+ const hits = (Array.isArray(bundle.cookies) ? bundle.cookies : []).filter((cookie) => cookie.name === name);
3797
+ return hits.length === 1 ? hits[0].value : null;
3798
+ }
3799
+ function checkoutKeyOf(p, bundle, reader) {
3800
+ const once = p.once_per;
3801
+ if (!once)
3802
+ return null;
3803
+ let value = null;
3804
+ if ("at" in once) {
3805
+ const at = reader.exact(once.at);
3806
+ value = at.found && reader.variants(once.at).length === 1 ? at.value : null;
3807
+ }
3808
+ else if ("path" in once) {
3809
+ value = pathPlaceholders(p.path, new URL(bundle.url).pathname)?.[once.path] ?? null;
3810
+ }
3811
+ else if ("query" in once) {
3812
+ const mark = bundle.url.indexOf("?");
3813
+ value = queryPlaceholders(p.query, mark === -1 ? null : bundle.url.slice(mark + 1))?.[once.query] ?? null;
3814
+ }
3815
+ else if ("cookie" in once) {
3816
+ value = sessionCookie(bundle, once.cookie);
3817
+ }
3818
+ return typeof value === "string" && value !== "" ? value : null;
3819
+ }
3820
+ function sessionBindingRefusal(p, bundle, contentType) {
3821
+ const { reader } = readBody(p, bundle.body, contentType);
3822
+ for (const rule of p.requires) {
3823
+ if ("equals_cookie" in rule) {
3824
+ const cookie = sessionCookie(bundle, rule.equals_cookie);
3825
+ if (cookie === null || reader.exact(rule.at).value !== cookie)
3826
+ return `${reader.label(rule.at)} is not the checkout the session's ${rule.equals_cookie} cookie names`;
3827
+ }
3828
+ if ("equals_path" in rule) {
3829
+ const segment = pathPlaceholders(p.path, new URL(bundle.url).pathname)?.[rule.equals_path];
3830
+ if (segment === void 0 || reader.exact(rule.at).value !== segment)
3831
+ return `${reader.label(rule.at)} is not the checkout the card request's URL names`;
3832
+ }
3833
+ }
3834
+ if (p.once_per && checkoutKeyOf(p, bundle, reader) === null)
3835
+ return `the session names no checkout for ${p.merchant} to take one payment from`;
3836
+ return null;
3837
+ }
3838
+ function merchantCardEndpointSessionRefusal(profile, bundle) {
3839
+ const p = trustedMerchantCardEndpoint(profile);
3840
+ const limits = MERCHANT_CARD_ENDPOINT_LIMITS;
3841
+ if (!isPlainObject(bundle))
3842
+ return "the session bundle is missing";
3843
+ if (Object.keys(bundle).some((member) => !BUNDLE_MEMBERS.has(member)))
3844
+ return "the session bundle carries a member it may not";
3845
+ if (!endpointMatches(p, bundle.url, p.method))
3846
+ return `the session bundle's URL is not ${p.merchant}'s reviewed card endpoint`;
3847
+ if (bundle.page_origin !== p.checkout)
3848
+ return `the card request was not paused on ${p.checkout}`;
3849
+ if (p.navigation === null ? bundle.navigation !== void 0 : bundle.navigation !== p.navigation) {
3850
+ return p.navigation === null ? `${p.merchant}'s card request is not a page navigation` : `the card request was not the ${p.navigation === "top" ? "page" : "frame"} navigation ${p.merchant}'s form sends`;
3851
+ }
3852
+ const raw = bundle.headers === void 0 ? {} : bundle.headers;
3853
+ const headers = isPlainObject(raw) ? lowerHeaders(raw) : null;
3854
+ if (!headers)
3855
+ return "the session headers are not one value per name";
3856
+ let total = 0;
3857
+ for (const [name, value] of Object.entries(headers)) {
3858
+ if (NEVER_FROM_CAPTURE.test(name) || !p.session.headers.includes(name))
3859
+ return `the session bundle carries a header outside ${p.merchant}'s reviewed list`;
3860
+ if (typeof value !== "string" || !PRINTABLE_ASCII.test(value) || value.length > limits.headerValueBytes)
3861
+ return `the session header ${name} is not printable text within the limit`;
3862
+ total += name.length + value.length;
3863
+ }
3864
+ if (total > limits.headerBytes)
3865
+ return "the session headers are larger than the limit";
3866
+ for (const name of p.session.required) {
3867
+ if (typeof headers[name] !== "string" || headers[name] === "")
3868
+ return `the session bundle is missing ${name}`;
3869
+ }
3870
+ const cookies = bundle.cookies === void 0 ? [] : bundle.cookies;
3871
+ if (!Array.isArray(cookies))
3872
+ return "the session cookies are not a list";
3873
+ if (!p.session.cookies && cookies.length > 0)
3874
+ return `${p.merchant}'s card request carries no cookies`;
3875
+ if (cookies.length > limits.cookies)
3876
+ return "the session bundle carries more cookies than the limit";
3877
+ let cookieBytes = 0;
3878
+ for (const cookie of cookies) {
3879
+ if (!isPlainObject(cookie) || Object.keys(cookie).sort().join(",") !== "name,value" || typeof cookie.name !== "string" || !COOKIE_NAME.test(cookie.name) || typeof cookie.value !== "string" || !COOKIE_VALUE.test(cookie.value))
3880
+ return "a session cookie is not an RFC 6265 name and value";
3881
+ if (cookie.name.length + cookie.value.length + 1 > limits.headerValueBytes)
3882
+ return "a session cookie is larger than the limit";
3883
+ if (!cookieNameAllowed(p, cookie.name))
3884
+ return `the session bundle carries a cookie outside ${p.merchant}'s reviewed list`;
3885
+ cookieBytes += cookie.name.length + cookie.value.length + 3;
3886
+ }
3887
+ if (cookieBytes > limits.headerBytes)
3888
+ return "the session cookies are larger than the limit";
3889
+ if (bundle.referer !== void 0 && !refererKept(p, bundle.referer))
3890
+ return `the session Referer is not a page on ${p.frame ?? p.checkout}`;
3891
+ return merchantCardEndpointBodyRefusal(p.id, bundle.body, headers["content-type"]) ?? sessionBindingRefusal(p, bundle, headers["content-type"]);
3892
+ }
3893
+ function merchantCardEndpointCheckoutKey(profile, bundle) {
3894
+ const p = trustedMerchantCardEndpoint(profile);
3895
+ const refusal = merchantCardEndpointSessionRefusal(p.id, bundle);
3896
+ if (refusal)
3897
+ throw new Error(`Refusing to pay: ${refusal}.`);
3898
+ const headers = lowerHeaders(bundle.headers ?? {});
3899
+ return checkoutKeyOf(p, bundle, readBody(p, bundle.body, headers["content-type"]).reader);
3900
+ }
3901
+ function slotValue(slot, spec, placeholder, facts) {
3902
+ if (slot === "pan")
3903
+ return spec.format === "grouped4" && placeholder.includes(" ") ? groupedFour(facts.number) : facts.number;
3904
+ if (slot === "cvv")
3905
+ return facts.cvc;
3906
+ if (slot === "last4")
3907
+ return facts.number.slice(-4);
3908
+ if (slot === "expiry")
3909
+ return expiryText(expiryForm(spec, placeholder), facts);
3910
+ if (slot === "leading")
3911
+ return facts.number.slice(0, spec.digits);
3912
+ const month = String(facts.month).padStart(2, "0");
3913
+ if (slot === "exp_month") {
3914
+ if (spec.format === "mm")
3915
+ return month;
3916
+ if (spec.format === "m")
3917
+ return String(facts.month);
3918
+ if (spec.format === "m_int" || typeof placeholder === "number")
3919
+ return facts.month;
3920
+ return placeholder.length === 2 ? month : String(facts.month);
3921
+ }
3922
+ if (spec.format === "yyyy")
3923
+ return String(facts.year);
3924
+ if (spec.format === "yyyy_int")
3925
+ return facts.year;
3926
+ if (spec.format === "yy_int")
3927
+ return facts.year % 100;
3928
+ if (typeof placeholder === "number")
3929
+ return placeholder >= 1e3 ? facts.year : facts.year % 100;
3930
+ return placeholder.length === 4 ? String(facts.year) : String(facts.year % 100).padStart(2, "0");
3931
+ }
3932
+ function plaintextFor(slot, spec, facts) {
3933
+ if (spec.plaintext === "digits")
3934
+ return slot === "cvv" ? facts.cvc : facts.number;
3935
+ if (spec.plaintext === "MMYYYY")
3936
+ return `${String(facts.month).padStart(2, "0")}${facts.year}`;
3937
+ let claims = facts.number;
3938
+ for (const name of [...spec.plaintext.claims].reverse())
3939
+ claims = { [name]: claims };
3940
+ return JSON.stringify(claims);
3941
+ }
3942
+ function writeJson(parsed, at, value) {
3943
+ const path = hasSelector(at) ? selectAt(parsed, at).path : at;
3944
+ if (!path)
3945
+ throw new SubstitutionError(`the paused body has no field at ${describeAt(at)}`);
3946
+ const parent = jsonAt(parsed, path.slice(0, -1));
3947
+ const key = path[path.length - 1];
3948
+ const owned = parent.found && (typeof key === "number" ? Array.isArray(parent.value) && key < parent.value.length : isPlainObject(parent.value) && !FORBIDDEN_KEYS.has(key) && Object.prototype.hasOwnProperty.call(parent.value, key));
3949
+ if (!owned)
3950
+ throw new SubstitutionError(`the paused body has no field at ${describeAt(at)}`);
3951
+ parent.value[key] = value;
3952
+ }
3953
+ function substitutionPlan(p, body, contentType, card) {
3954
+ const read = readBody(p, body, contentType);
3955
+ const refusal = read.refusal ?? rulesRefusal(p, read.reader, body);
3956
+ if (refusal)
3957
+ throw new SubstitutionError(refusal);
3958
+ const facts = cardFacts(card);
3959
+ const typed = p.card.pan && ["digits", "grouped4"].includes(p.card.pan.format) ? panDigits(p.card.pan, read.reader.exact(p.card.pan.at).value) : null;
3960
+ if (typed === facts.number)
3961
+ throw new SubstitutionError("the paused body already carries the real card number");
3962
+ const bins = p.card.bins && facts.brand && Object.prototype.hasOwnProperty.call(p.card.bins, facts.brand) ? p.card.bins[facts.brand] : null;
3963
+ if (bins && !bins.some((prefix) => facts.number.startsWith(prefix)))
3964
+ throw new SubstitutionError(`${p.merchant} does not take this card's number range`);
3965
+ const writes = [];
3966
+ for (const [slot, spec] of cardSlots(p)) {
3967
+ const at = read.reader.exact(spec.at);
3968
+ if (slot === "brand" && !spec.map)
3969
+ continue;
3970
+ if (slot === "cvv" && codeNotAsked(read.reader, spec, read.reader.variants(spec.at)))
3971
+ continue;
3972
+ if (slot === "brand") {
3973
+ const value = facts.brand && Object.prototype.hasOwnProperty.call(spec.map, facts.brand) ? spec.map[facts.brand] : void 0;
3974
+ if (value === void 0)
3975
+ throw new SubstitutionError(`${p.merchant} does not take this card's network`);
3976
+ writes.push({ at: spec.at, found: at, value });
3977
+ }
3978
+ else if (spec.format === "encrypted") {
3979
+ writes.push({ at: spec.at, found: at, value: null, plaintext: plaintextFor(slot, spec, facts) });
3980
+ }
3981
+ else {
3982
+ writes.push({ at: spec.at, found: at, value: slotValue(slot, spec, at.value, facts) });
3983
+ }
3984
+ }
3985
+ return { read, facts, typed, writes };
3986
+ }
3987
+ function writtenBody(p, plan, body) {
3988
+ if (jsonBody(p)) {
3989
+ for (const write of plan.writes)
3990
+ writeJson(plan.read.reader.parsed, write.at, write.value);
3991
+ return JSON.stringify(plan.read.reader.parsed);
3992
+ }
3993
+ let out = body;
3994
+ for (const write of [...plan.writes].sort((a, b) => b.found.field.start - a.found.field.start)) {
3995
+ const text = p.encoding === "form" ? encodeURIComponent(String(write.value)).replace(/%20/g, "+") : String(write.value);
3996
+ out = out.slice(0, write.found.field.start) + text + out.slice(write.found.field.end);
3997
+ }
3998
+ return out;
3999
+ }
4000
+ function merchantCardEndpointEncrypts(profile) {
4001
+ return trustedMerchantCardEndpoint(profile).merchant_key != null;
4002
+ }
4003
+ function substituteMerchantCardEndpoint(body, contentType, profile, card) {
4004
+ const p = trustedMerchantCardEndpoint(profile);
4005
+ if (p.merchant_key)
4006
+ throw new SubstitutionError(`${p.merchant} takes the card encrypted under its own key, which this call does not write`);
4007
+ const plan = substitutionPlan(p, body, contentType, card);
4008
+ const out = writtenBody(p, plan, body);
4009
+ if (countRuns(out, plan.typed) !== 0)
4010
+ throw new SubstitutionError("the typed card number would still reach the merchant");
4011
+ if (countRuns(out, plan.facts.number) !== 1)
4012
+ throw new SubstitutionError("the card number would reach the merchant outside its field");
4013
+ if (!merchantCardEndpointLive(p))
4014
+ throw new SubstitutionError(`Agentcard has not turned on payments to ${p.merchant} yet`);
4015
+ return out;
4016
+ }
4017
+ function merchantKeyFor(p, plan) {
4018
+ const merchantKey = p.merchant_key;
4019
+ const encrypted = cardSlots(p).filter(([, spec]) => spec.format === "encrypted").map(([, spec]) => plan.read.reader.exact(spec.at).value);
4020
+ let key = null;
4021
+ if (merchantKey.scheme === "rsa_oaep") {
4022
+ if (merchantKey.kid_at) {
4023
+ const kid = plan.read.reader.exact(merchantKey.kid_at).value;
4024
+ key = merchantKey.keys.find((entry) => entry.kid === kid) ?? null;
4025
+ }
4026
+ else if (merchantKey.keys.length === 1) {
4027
+ key = merchantKey.keys[0];
4028
+ }
4029
+ }
4030
+ else if (merchantKey.alg !== null) {
4031
+ const kids = new Set(encrypted.map((value) => readCompactJwe(value)?.header.kid));
4032
+ const [kid] = kids;
4033
+ key = kids.size === 1 ? merchantKey.keys.find((entry) => entry.kid === kid) ?? null : null;
4034
+ }
4035
+ if (!key)
4036
+ throw new SubstitutionError(`Agentcard has not pinned the key ${p.merchant} encrypts the card under`);
4037
+ let size;
4038
+ try {
4039
+ ({ size } = parseMerchantKey(key));
4040
+ }
4041
+ catch {
4042
+ throw new SubstitutionError(`the key Agentcard pinned for ${p.merchant} does not match its pin`);
4043
+ }
4044
+ const sizes = encrypted.map((value) => merchantKey.scheme === "rsa_oaep" ? decodeBase64(value)?.length : readCompactJwe(value)?.encryptedKey.length);
4045
+ if (sizes.some((length) => length !== size))
4046
+ throw new SubstitutionError(`the paused body was encrypted under a key Agentcard did not pin for ${p.merchant}`);
4047
+ return key;
4048
+ }
4049
+ async function encryptedValue(merchantKey, key, text, crypto) {
4050
+ const bytes = new TextEncoder().encode(text);
4051
+ try {
4052
+ if (merchantKey.scheme === "rsa_oaep") {
4053
+ return encodeBase64(await rsaOaepEncrypt(key, bytes, { hash: merchantKey.hash, mgf1: merchantKey.mgf1 }, crypto));
4054
+ }
4055
+ return await jweCompactEncrypt(key, bytes, merchantKey, key.kid, crypto);
4056
+ }
4057
+ catch (error) {
4058
+ if (error instanceof MerchantKeyError)
4059
+ throw new SubstitutionError(`the card could not be encrypted under ${error.reason === "key" ? "the pinned key" : "this device"}`);
4060
+ throw error;
4061
+ }
4062
+ finally {
4063
+ bytes.fill(0);
4064
+ }
4065
+ }
4066
+ async function encryptMerchantCardEndpointBody(body, contentType, profile, card, crypto) {
4067
+ const p = trustedMerchantCardEndpoint(profile);
4068
+ if (!p.merchant_key)
4069
+ throw new SubstitutionError(`${p.merchant} takes the card in the clear, which this call does not write`);
4070
+ const plan = substitutionPlan(p, body, contentType, card);
4071
+ const key = merchantKeyFor(p, plan);
4072
+ for (const write of plan.writes) {
4073
+ if (write.plaintext === void 0)
4074
+ continue;
4075
+ write.value = await encryptedValue(p.merchant_key, key, write.plaintext, crypto);
4076
+ write.plaintext = void 0;
4077
+ }
4078
+ const out = writtenBody(p, plan, body);
4079
+ if (countRuns(out, plan.facts.number) !== 0 || clearCardNumber([out]) !== null)
4080
+ throw new SubstitutionError("a card number would reach the merchant in the clear");
4081
+ if (!merchantCardEndpointLive(p))
4082
+ throw new SubstitutionError(`Agentcard has not turned on payments to ${p.merchant} yet`);
4083
+ return out;
4084
+ }
4085
+ function endpointRequest(p, bundle, captured, body) {
4086
+ const formType = p.navigation === null ? "application/x-www-form-urlencoded; charset=UTF-8" : NAVIGATION_FORM_TYPE;
4087
+ const headers = { "content-type": p.encoding === "form" ? formType : captured["content-type"] };
4088
+ for (const [name, value] of Object.entries(captured))
4089
+ if (name !== "content-type")
4090
+ headers[name] = value;
4091
+ const cookies = bundle.cookies ?? [];
4092
+ if (p.session.cookies && cookies.length > 0)
4093
+ headers.cookie = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
4094
+ headers.origin = p.frame ?? p.checkout;
4095
+ if (bundle.referer !== void 0)
4096
+ headers.referer = bundle.referer;
4097
+ return { url: bundle.url, method: p.method, headers, body };
4098
+ }
4099
+ function merchantEndpointRequest(profile, bundle, card) {
4100
+ const p = trustedMerchantCardEndpoint(profile);
4101
+ if (p.merchant_key)
4102
+ throw new Error(`Refusing to pay: ${p.merchant} takes the card encrypted under its own key, which this call does not write.`);
4103
+ const refusal = merchantCardEndpointSessionRefusal(p.id, bundle);
4104
+ if (refusal)
4105
+ throw new Error(`Refusing to pay: ${refusal}.`);
4106
+ const captured = lowerHeaders(bundle.headers ?? {});
4107
+ return endpointRequest(p, bundle, captured, substituteMerchantCardEndpoint(bundle.body, captured["content-type"], p.id, card));
4108
+ }
4109
+ async function encryptedMerchantEndpointRequest(profile, bundle, card, crypto) {
4110
+ const p = trustedMerchantCardEndpoint(profile);
4111
+ if (!p.merchant_key)
4112
+ throw new Error(`Refusing to pay: ${p.merchant} takes the card in the clear, which this call does not write.`);
4113
+ const refusal = merchantCardEndpointSessionRefusal(p.id, bundle);
4114
+ if (refusal)
4115
+ throw new Error(`Refusing to pay: ${refusal}.`);
4116
+ const captured = lowerHeaders(bundle.headers ?? {});
4117
+ return endpointRequest(p, bundle, captured, await encryptMerchantCardEndpointBody(bundle.body, captured["content-type"], p.id, card, crypto));
4118
+ }
4119
+ function headerPairs(headers) {
4120
+ if (headers === void 0)
4121
+ return [];
4122
+ if (Array.isArray(headers)) {
4123
+ return headers.every((pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string") ? headers : null;
4124
+ }
4125
+ if (!isPlainObject(headers))
4126
+ return null;
4127
+ const pairs = [];
4128
+ for (const [name, value] of Object.entries(headers)) {
4129
+ for (const item of Array.isArray(value) ? value : [value]) {
4130
+ if (typeof item !== "string")
4131
+ return null;
4132
+ pairs.push([name, item]);
4133
+ }
4134
+ }
4135
+ return pairs;
4136
+ }
4137
+ function answerParts(response) {
4138
+ const pairs = isPlainObject(response) ? headerPairs(response.headers) : null;
4139
+ if (!pairs || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || typeof response.bodyText !== "string" || utf8Length(response.bodyText) > MERCHANT_CARD_ENDPOINT_LIMITS.answerBytes) {
4140
+ throw new Error("Refusing to read: the merchant's answer is malformed.");
4141
+ }
4142
+ return pairs;
4143
+ }
4144
+ var BASE64_RUN = /[A-Za-z0-9+/_-]{8,}/g;
4145
+ var STREAM_RUN_LENGTH = 16;
4146
+ var CODE_BASE64_RUN = /[A-Za-z0-9+/_-]{4,}/g;
4147
+ var BASE64_ESCAPE = /\\\/|\\u002[bBfF]|%2[bBfF]|&#(?:43|47|x2[bBfF]);/g;
4148
+ var BASE64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4149
+ var unescapeBase64 = (escape) => /43|[bB]/.test(escape) ? "+" : "/";
4150
+ function base64Bytes(run, skip) {
4151
+ let bits = 0;
4152
+ let value = 0;
4153
+ let out = "";
4154
+ for (let i = skip; i < run.length; i += 1) {
4155
+ const char = run[i];
4156
+ value = value << 6 | (char === "-" ? 62 : char === "_" ? 63 : BASE64_ALPHABET2.indexOf(char));
4157
+ bits += 6;
4158
+ if (bits >= 8) {
4159
+ bits -= 8;
4160
+ out += String.fromCharCode(value >> bits);
4161
+ value &= (1 << bits) - 1;
4162
+ }
4163
+ }
4164
+ return out;
4165
+ }
4166
+ function compressedStream(bytes) {
4167
+ const first = bytes.charCodeAt(0);
4168
+ const second = bytes.charCodeAt(1);
4169
+ return first === 31 && second === 139 || (first & 15) === 8 && first >> 4 <= 7 && (first << 8 | second) % 31 === 0;
4170
+ }
4171
+ function carriesCard(text, facts, pan, code, depth, seen) {
4172
+ if (text.search(pan) !== -1)
4173
+ return true;
4174
+ for (const match of text.matchAll(CODE_KEY))
4175
+ if (match[1] === facts.cvc)
4176
+ return true;
4177
+ if (codeNearKey(text, code))
4178
+ return true;
4179
+ if (depth === 0)
4180
+ return false;
4181
+ for (const [run] of text.replace(BASE64_ESCAPE, unescapeBase64).matchAll(BASE64_RUN)) {
4182
+ for (let skip = 0; skip < 4; skip += 1) {
4183
+ const bytes = base64Bytes(run, skip);
4184
+ if (run.length >= STREAM_RUN_LENGTH && compressedStream(bytes))
4185
+ seen.compressed = true;
4186
+ if (carriesCard(bytes.replace(/\0/g, ""), facts, pan, code, depth - 1, seen))
4187
+ return true;
4188
+ }
4189
+ }
4190
+ return false;
4191
+ }
4192
+ function responseCardLeak(card, response) {
4193
+ const facts = cardFacts(card);
4194
+ const pan = digitRun(facts.number);
4195
+ const code = codeRun(facts.cvc);
4196
+ const seen = { compressed: false };
4197
+ for (const text of [response.bodyText, ...answerParts(response).flat()]) {
4198
+ if (carriesCard(text, facts, pan, code, 2, seen))
4199
+ return "card_in_response";
4200
+ }
4201
+ return seen.compressed ? "unreadable_in_response" : null;
4202
+ }
4203
+ var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
4204
+ function merchantCardEndpointAnswerRefusal(profile, bodyText) {
4205
+ const p = trustedMerchantCardEndpoint(profile);
4206
+ const rules = p.response.withhold ?? [];
4207
+ if (rules.length === 0 || typeof bodyText !== "string")
4208
+ return null;
4209
+ let parsed;
4210
+ try {
4211
+ parsed = JSON.parse(bodyText.replace(JSON_PROTECTION_PREFIX, ""));
4212
+ }
4213
+ catch {
4214
+ return null;
4215
+ }
4216
+ if (!isPlainObject(parsed))
4217
+ return null;
4218
+ return rules.some((rule) => valuesIgnoringCase(parsed, rule.at).some((value) => value === rule.equals)) ? "refused_step_in_response" : null;
4219
+ }
4220
+ function merchantCardEndpointAnswer(profile, response, card) {
4221
+ const p = trustedMerchantCardEndpoint(profile);
4222
+ const pairs = answerParts(response);
4223
+ const withheld = responseCardLeak(card, response) ?? merchantCardEndpointAnswerRefusal(p.id, response.bodyText);
4224
+ if (withheld)
4225
+ return { status: response.status, withheld, headers: {}, set_cookies: [], bodyText: null };
4226
+ const headers = {};
4227
+ const setCookies = [];
4228
+ for (const [rawName, value] of pairs) {
4229
+ const name = rawName.toLowerCase();
4230
+ if (!p.response.headers.includes(name))
4231
+ continue;
4232
+ if (name === "set-cookie")
4233
+ setCookies.push(value);
4234
+ else
4235
+ headers[name] = Object.prototype.hasOwnProperty.call(headers, name) ? `${headers[name]}, ${value}` : value;
4236
+ }
4237
+ return { status: response.status, headers, set_cookies: setCookies, bodyText: response.bodyText };
4238
+ }
4239
+ var GET_REDIRECTS = /* @__PURE__ */ new Set([301, 302, 303]);
4240
+ function merchantCardEndpointNavigationAnswer(profile, answer, requestUrl) {
4241
+ const p = trustedMerchantCardEndpoint(profile);
4242
+ const status = isPlainObject(answer) ? answer.status : void 0;
4243
+ if (p.navigation === null || !Number.isInteger(status) || status < 300 || status > 399)
4244
+ return { kind: "page" };
4245
+ if (!GET_REDIRECTS.has(status))
4246
+ return { kind: "refused", reason: "redirect_repeats_post" };
4247
+ const location = isPlainObject(answer.headers) ? answer.headers.location : void 0;
4248
+ let target = null;
4249
+ if (typeof location === "string" && location.length <= MERCHANT_CARD_ENDPOINT_LIMITS.urlLength && /^[\x21-\x7e]+$/.test(location)) {
4250
+ try {
4251
+ target = new URL(location, requestUrl);
4252
+ }
4253
+ catch {
4254
+ target = null;
4255
+ }
4256
+ }
4257
+ if (!target || target.protocol !== "https:" || target.username || target.password)
4258
+ return { kind: "refused", reason: "redirect_location_invalid" };
4259
+ return { kind: "redirect", status, location: target.href };
4260
+ }
4261
+ export { HILTON_CREATE_RESERVATION_DOCUMENTS, MERCHANT_CARD_ENDPOINT_CLIENT_TABLE, MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX, MERCHANT_CARD_ENDPOINT_FACT_READERS, MERCHANT_CARD_ENDPOINT_LIMITS, MERCHANT_CARD_ENDPOINT_MODE, MERCHANT_CARD_ENDPOINT_PROFILES, MERCHANT_CARD_ENDPOINT_PSP, MERCHANT_CARD_ENDPOINT_RECOGNIZERS, SubstitutionError, assertMerchantCardEndpoint, encryptMerchantCardEndpointBody, encryptedMerchantEndpointRequest, matchMerchantCardEndpoint, matchMerchantCardEndpointResend, merchantCardEndpointAmount, merchantCardEndpointAnswer, merchantCardEndpointAnswerRefusal, merchantCardEndpointBodyRefusal, merchantCardEndpointById, merchantCardEndpointCarriesCard, merchantCardEndpointCheckoutKey, merchantCardEndpointEncrypts, merchantCardEndpointFactsRefusal, merchantCardEndpointFactsRole, merchantCardEndpointLive, merchantCardEndpointMatches, merchantCardEndpointNavigationAnswer, merchantCardEndpointPasses, merchantCardEndpointRecognizerById, merchantCardEndpointRequestOrigin, merchantCardEndpointSessionRefusal, merchantCardEndpointSessionTtl, merchantEndpointRequest, projectMerchantCardEndpointFacts, readMerchantCardEndpointFacts, responseCardLeak, substituteMerchantCardEndpoint, trustedMerchantCardEndpoint };