@curless/hotel-storefront 0.23.2 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +35 -11
  2. package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +2 -0
  3. package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -0
  4. package/dist/__tests__/__fixtures__/huazhu-fake.js +3 -0
  5. package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -0
  6. package/dist/__tests__/huazhu-backend.test.d.ts +2 -0
  7. package/dist/__tests__/huazhu-backend.test.d.ts.map +1 -0
  8. package/dist/__tests__/huazhu-backend.test.js +434 -0
  9. package/dist/__tests__/huazhu-backend.test.js.map +1 -0
  10. package/dist/__tests__/huazhu-cancel-policy.test.d.ts +2 -0
  11. package/dist/__tests__/huazhu-cancel-policy.test.d.ts.map +1 -0
  12. package/dist/__tests__/huazhu-cancel-policy.test.js +82 -0
  13. package/dist/__tests__/huazhu-cancel-policy.test.js.map +1 -0
  14. package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
  15. package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
  16. package/dist/__tests__/huazhu-sandbox.test.js +172 -0
  17. package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
  18. package/dist/__tests__/huazhu-sellable.test.d.ts +2 -0
  19. package/dist/__tests__/huazhu-sellable.test.d.ts.map +1 -0
  20. package/dist/__tests__/huazhu-sellable.test.js +192 -0
  21. package/dist/__tests__/huazhu-sellable.test.js.map +1 -0
  22. package/dist/__tests__/server.test.js +129 -4
  23. package/dist/__tests__/server.test.js.map +1 -1
  24. package/dist/__tests__/tool-descriptions.test.js +85 -7
  25. package/dist/__tests__/tool-descriptions.test.js.map +1 -1
  26. package/dist/backend.d.ts +27 -0
  27. package/dist/backend.d.ts.map +1 -1
  28. package/dist/backend.js +7 -0
  29. package/dist/backend.js.map +1 -1
  30. package/dist/card.generated.d.ts +1 -1
  31. package/dist/card.generated.js +2 -2
  32. package/dist/card.generated.js.map +1 -1
  33. package/dist/config.d.ts +64 -4
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js.map +1 -1
  36. package/dist/facilities.d.ts.map +1 -1
  37. package/dist/facilities.js +7 -5
  38. package/dist/facilities.js.map +1 -1
  39. package/dist/huazhu/backend.d.ts +40 -0
  40. package/dist/huazhu/backend.d.ts.map +1 -0
  41. package/dist/huazhu/backend.js +1318 -0
  42. package/dist/huazhu/backend.js.map +1 -0
  43. package/dist/huazhu/bookings.d.ts +81 -0
  44. package/dist/huazhu/bookings.d.ts.map +1 -0
  45. package/dist/huazhu/bookings.js +189 -0
  46. package/dist/huazhu/bookings.js.map +1 -0
  47. package/dist/huazhu/cancel-policy.d.ts +69 -0
  48. package/dist/huazhu/cancel-policy.d.ts.map +1 -0
  49. package/dist/huazhu/cancel-policy.js +201 -0
  50. package/dist/huazhu/cancel-policy.js.map +1 -0
  51. package/dist/huazhu/catalog.d.ts +75 -0
  52. package/dist/huazhu/catalog.d.ts.map +1 -0
  53. package/dist/huazhu/catalog.js +251 -0
  54. package/dist/huazhu/catalog.js.map +1 -0
  55. package/dist/huazhu/check.d.ts +5 -0
  56. package/dist/huazhu/check.d.ts.map +1 -0
  57. package/dist/huazhu/check.js +170 -0
  58. package/dist/huazhu/check.js.map +1 -0
  59. package/dist/huazhu/client.d.ts +100 -0
  60. package/dist/huazhu/client.d.ts.map +1 -0
  61. package/dist/huazhu/client.js +232 -0
  62. package/dist/huazhu/client.js.map +1 -0
  63. package/dist/huazhu/fake.d.ts +64 -0
  64. package/dist/huazhu/fake.d.ts.map +1 -0
  65. package/dist/huazhu/fake.js +365 -0
  66. package/dist/huazhu/fake.js.map +1 -0
  67. package/dist/huazhu/sandbox.d.ts +26 -0
  68. package/dist/huazhu/sandbox.d.ts.map +1 -0
  69. package/dist/huazhu/sandbox.js +289 -0
  70. package/dist/huazhu/sandbox.js.map +1 -0
  71. package/dist/huazhu/sellable.d.ts +81 -0
  72. package/dist/huazhu/sellable.d.ts.map +1 -0
  73. package/dist/huazhu/sellable.js +219 -0
  74. package/dist/huazhu/sellable.js.map +1 -0
  75. package/dist/huazhu/wire.d.ts +389 -0
  76. package/dist/huazhu/wire.d.ts.map +1 -0
  77. package/dist/huazhu/wire.js +72 -0
  78. package/dist/huazhu/wire.js.map +1 -0
  79. package/dist/index.js +18 -4
  80. package/dist/index.js.map +1 -1
  81. package/dist/legal.d.ts +4 -3
  82. package/dist/legal.d.ts.map +1 -1
  83. package/dist/legal.js +71 -30
  84. package/dist/legal.js.map +1 -1
  85. package/dist/lib.d.ts +8 -3
  86. package/dist/lib.d.ts.map +1 -1
  87. package/dist/lib.js +8 -1
  88. package/dist/lib.js.map +1 -1
  89. package/dist/server.d.ts +20 -8
  90. package/dist/server.d.ts.map +1 -1
  91. package/dist/server.js +132 -28
  92. package/dist/server.js.map +1 -1
  93. package/dist/surface.d.ts +2 -1
  94. package/dist/surface.d.ts.map +1 -1
  95. package/dist/surface.js +42 -10
  96. package/dist/surface.js.map +1 -1
  97. package/dist/tools.d.ts +33 -20
  98. package/dist/tools.d.ts.map +1 -1
  99. package/dist/tools.js +122 -11
  100. package/dist/tools.js.map +1 -1
  101. package/dist/types.d.ts +21 -2
  102. package/dist/types.d.ts.map +1 -1
  103. package/dist/vocab.d.ts +5 -0
  104. package/dist/vocab.d.ts.map +1 -1
  105. package/dist/vocab.js +2 -1
  106. package/dist/vocab.js.map +1 -1
  107. package/package.json +1 -1
package/dist/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_PAGE, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, } from './backend.js';
1
+ import { DEFAULT_PAGE, FULL_CAPABILITIES, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, } from './backend.js';
2
2
  import { isDay, nights as nightsBetween } from './dates.js';
3
3
  import { FACILITIES } from './facilities.js';
4
4
  import { DESCRIPTION_MAX, POLICY_MAX, hotelText } from './fence.js';
@@ -63,7 +63,69 @@ const cancels = (title, openWorld) => ({
63
63
  const DATE = (what) => ({ type: 'string', description: `${what}, as YYYY-MM-DD` });
64
64
  /** How many ${nounPlural} one look-up reads - a city's worth, and what a comparison can hold and then some. */
65
65
  export const LOOK_UP_MAX = 6;
66
- export const buildTools = (n, v) => {
66
+ /**
67
+ * The tools this deployment offers: every tool, less the ones its backend
68
+ * cannot do, and with the descriptions of the rest saying only what is true
69
+ * of it - see `BackendCapabilities`.
70
+ */
71
+ export const buildTools = (n, v, caps = FULL_CAPABILITIES) => forCapabilities(allTools(n, v), n, v, caps);
72
+ /** The tool names a backend with these capabilities leaves out. */
73
+ export const withheldTools = (n, caps) => new Set([
74
+ ...(caps.members ? [] : [n.signIn, n.signOut, n.forgetPreferences]),
75
+ ...(caps.extras ? [] : [n.listExtras]),
76
+ ...(caps.changes || caps.extras ? [] : [n.previewChange, n.changeBooking]),
77
+ ]);
78
+ const forCapabilities = (tools, n, v, caps) => {
79
+ const withheld = withheldTools(n, caps);
80
+ const without = (schema, ...keys) => {
81
+ const s = schema;
82
+ const properties = { ...(s.properties ?? {}) };
83
+ for (const k of keys)
84
+ delete properties[k];
85
+ return { ...s, properties };
86
+ };
87
+ return tools
88
+ .filter((t) => !withheld.has(t.name))
89
+ .map((t) => {
90
+ let out = t;
91
+ if (!caps.extras) {
92
+ if (t.name === n.addToTrip) {
93
+ out = {
94
+ ...out,
95
+ description: `Puts room nights into the trip and returns the whole trip, priced. A room line needs the ${v.noun}, the room type and the dates; \`rooms\` books more than one of that room type. A line is replaced, not stacked: sending the same room and dates again sets that line's room count, and 0 removes it.`,
96
+ inputSchema: without(out.inputSchema, 'extra', 'quantity'),
97
+ };
98
+ }
99
+ if (t.name === n.viewTrip) {
100
+ out = {
101
+ ...out,
102
+ description: 'The trip as it stands: every room line with its own total, the taxes and fees, and the all-in total, laid out day by day on the card.',
103
+ };
104
+ }
105
+ if (t.name === n.lookUp) {
106
+ out = {
107
+ ...out,
108
+ description: out.description.replace(', its add-ons with their ids and prices', ''),
109
+ };
110
+ }
111
+ if (t.name === n.itinerary) {
112
+ out = JSON.parse(JSON.stringify(out).replaceAll(' and add-ons', ''));
113
+ }
114
+ }
115
+ if (t.name === n.requestBooking) {
116
+ if (!caps.members)
117
+ out = { ...out, inputSchema: without(out.inputSchema, 'rememberRequests') };
118
+ if (caps.booking === 'pay-at-hotel') {
119
+ out = {
120
+ ...out,
121
+ description: `Books the trip's rooms with ${v.brand} for a named guest: each stay becomes the hotel's own reservation, with its own confirmation code, paid at the hotel. No payment is taken here and no card is collected. All of the trip is booked or none of it. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests the desk tries to meet.`,
122
+ };
123
+ }
124
+ }
125
+ return out;
126
+ });
127
+ };
128
+ const allTools = (n, v) => {
67
129
  const open = !v.demo;
68
130
  const stayProps = {
69
131
  checkIn: DATE('The arrival date'),
@@ -578,6 +640,7 @@ const TRIP_SCHEMA = {
578
640
  view: { enum: ['trip', 'needs-dates', 'error'] },
579
641
  brand: STR,
580
642
  toolNames: OBJ,
643
+ bookingMode: STR,
581
644
  vocab: OBJ,
582
645
  demo: BOOL,
583
646
  demoNote: STR,
@@ -601,6 +664,7 @@ const CANCEL_SCHEMA = {
601
664
  view: { enum: ['cancellation-preview', 'cancelled', 'booking-not-found'] },
602
665
  brand: STR,
603
666
  toolNames: OBJ,
667
+ bookingMode: STR,
604
668
  demo: BOOL,
605
669
  demoNote: STR,
606
670
  booking: OBJ,
@@ -619,6 +683,7 @@ const CHANGE_SCHEMA = {
619
683
  view: { enum: ['change-preview', 'changed', 'booking-not-found'] },
620
684
  brand: STR,
621
685
  toolNames: OBJ,
686
+ bookingMode: STR,
622
687
  demo: BOOL,
623
688
  demoNote: STR,
624
689
  booking: OBJ,
@@ -641,6 +706,7 @@ export const buildOutputSchemas = (n) => ({
641
706
  view: { enum: ['hotels', 'no-hotels', 'destinations'] },
642
707
  brand: STR,
643
708
  toolNames: OBJ,
709
+ bookingMode: STR,
644
710
  vocab: OBJ,
645
711
  demo: BOOL,
646
712
  demoNote: STR,
@@ -669,6 +735,7 @@ export const buildOutputSchemas = (n) => ({
669
735
  view: { enum: ['hotel', 'no-rooms', 'hotel-not-found'] },
670
736
  brand: STR,
671
737
  toolNames: OBJ,
738
+ bookingMode: STR,
672
739
  vocab: OBJ,
673
740
  demo: BOOL,
674
741
  demoNote: STR,
@@ -693,6 +760,7 @@ export const buildOutputSchemas = (n) => ({
693
760
  view: { enum: ['extras', 'hotel-not-found'] },
694
761
  brand: STR,
695
762
  toolNames: OBJ,
763
+ bookingMode: STR,
696
764
  demo: BOOL,
697
765
  demoNote: STR,
698
766
  hotel: OBJ,
@@ -709,6 +777,7 @@ export const buildOutputSchemas = (n) => ({
709
777
  view: { enum: ['booking', 'needs-guest', 'empty-trip', 'error'] },
710
778
  brand: STR,
711
779
  toolNames: OBJ,
780
+ bookingMode: STR,
712
781
  demo: BOOL,
713
782
  demoNote: STR,
714
783
  booking: OBJ,
@@ -729,6 +798,7 @@ export const buildOutputSchemas = (n) => ({
729
798
  view: { const: 'bookings' },
730
799
  brand: STR,
731
800
  toolNames: OBJ,
801
+ bookingMode: STR,
732
802
  demo: BOOL,
733
803
  demoNote: STR,
734
804
  bookings: ARR,
@@ -746,6 +816,7 @@ export const buildOutputSchemas = (n) => ({
746
816
  view: { enum: ['comparison', 'nothing-to-compare'] },
747
817
  brand: STR,
748
818
  toolNames: OBJ,
819
+ bookingMode: STR,
749
820
  demo: BOOL,
750
821
  demoNote: STR,
751
822
  title: STR,
@@ -764,6 +835,7 @@ export const buildOutputSchemas = (n) => ({
764
835
  view: { enum: ['itinerary', 'empty-itinerary'] },
765
836
  brand: STR,
766
837
  toolNames: OBJ,
838
+ bookingMode: STR,
767
839
  demo: BOOL,
768
840
  demoNote: STR,
769
841
  title: STR,
@@ -782,6 +854,7 @@ export const buildOutputSchemas = (n) => ({
782
854
  view: { enum: ['member', 'member-not-found'] },
783
855
  brand: STR,
784
856
  toolNames: OBJ,
857
+ bookingMode: STR,
785
858
  demo: BOOL,
786
859
  demoNote: STR,
787
860
  member: OBJ,
@@ -799,6 +872,7 @@ export const buildOutputSchemas = (n) => ({
799
872
  view: { enum: ['member', 'no-member'] },
800
873
  brand: STR,
801
874
  toolNames: OBJ,
875
+ bookingMode: STR,
802
876
  member: OBJ,
803
877
  },
804
878
  required: ['view'],
@@ -811,6 +885,7 @@ export const buildOutputSchemas = (n) => ({
811
885
  view: { enum: ['look-up', 'nothing-found'] },
812
886
  brand: STR,
813
887
  toolNames: OBJ,
888
+ bookingMode: STR,
814
889
  demo: BOOL,
815
890
  hotels: ARR,
816
891
  stay: OBJ,
@@ -825,6 +900,7 @@ export const buildOutputSchemas = (n) => ({
825
900
  view: { enum: ['policy', 'policies', 'policy-missing', 'hotel-not-found'] },
826
901
  brand: STR,
827
902
  toolNames: OBJ,
903
+ bookingMode: STR,
828
904
  demo: BOOL,
829
905
  hotel: OBJ,
830
906
  topic: STR,
@@ -872,10 +948,32 @@ const memberBadge = (deps, key) => {
872
948
  }
873
949
  : { signedIn: false };
874
950
  };
951
+ /**
952
+ * What was made, in one sentence - which depends on what asking MAKES here.
953
+ *
954
+ * ⚠️ HELD IS NOT BOOKED, AND BOOKED IS NOT PAID. The demonstration holds rooms
955
+ * until a moment and then lets them go; a group's own system makes its own
956
+ * reservation, kept until cancelled and paid at the hotel. Saying "held until"
957
+ * of a reservation invents a deadline, and saying "booked" of a hold invents a
958
+ * room - and a booking whose confirmation has not come back yet is neither,
959
+ * and is said as exactly that.
960
+ */
961
+ const bookedLine = (deps, b) => {
962
+ const total = formatMoney(b.quote.total);
963
+ if ((deps.backend.capabilities ?? FULL_CAPABILITIES).booking === 'pay-at-hotel') {
964
+ return b.status === 'requested'
965
+ ? `${total} all in, to be paid at the hotel. ${deps.vocab.brand} has not confirmed this booking yet - that takes up to eight minutes, and ${deps.tools.listBookings} shows when it has. Nothing has been charged.`
966
+ : `${total} all in, paid at the hotel. **Nothing has been charged here** - this is ${deps.vocab.brand}'s own reservation, kept until it is cancelled, and ${b.guest.name} pays at check-in.`;
967
+ }
968
+ return `${total} all in. **Nothing has been charged** - the rooms are held until ${b.holdUntil?.slice(0, 16).replace('T', ' ')} UTC, and ${b.guest.name} presents a card at check-in.`;
969
+ };
875
970
  const common = (deps, key) => ({
876
971
  brand: deps.vocab.brand,
877
972
  ...(deps.logoUrl ? { logoUrl: deps.logoUrl } : {}),
878
- toolNames: deps.tools,
973
+ toolNames: deps.offered
974
+ ? Object.fromEntries(Object.entries(deps.tools).filter(([, name]) => deps.offered?.has(name)))
975
+ : deps.tools,
976
+ bookingMode: (deps.backend.capabilities ?? FULL_CAPABILITIES).booking,
879
977
  vocab: { noun: deps.vocab.noun, nounPlural: deps.vocab.nounPlural },
880
978
  ...(deps.vocab.demo ? { demo: true, demoNote: DEMO_NOTE } : { demo: false }),
881
979
  ...(key ? memberBadge(deps, key) : {}),
@@ -1882,8 +1980,14 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
1882
1980
  // which narrowing would make the question answerable.
1883
1981
  const oneCurrency = answer.currencies.length === 1;
1884
1982
  const wantedPrice = sort === 'price-low' || sort === 'price-high';
1885
- const applied = wantedPrice && !oneCurrency ? 'group' : sort;
1983
+ const applied = (wantedPrice && !oneCurrency) || answer.ignored?.sort ? 'group' : sort;
1984
+ // Room terms the backend could not apply are not "filtered to".
1985
+ const filtersSaid = asked.filters && answer.ignored?.roomTerms
1986
+ ? { ...asked.filters, freeCancellation: undefined, breakfast: undefined }
1987
+ : asked.filters;
1886
1988
  const priceNotes = [
1989
+ // What the backend was asked and did not do, in its own words.
1990
+ ...(answer.unapplied ?? []),
1887
1991
  wantedPrice && !oneCurrency
1888
1992
  ? `These are quoted in ${answer.currencies.length} currencies (${answer.currencies.join(', ')}), and no rate is applied here, so they are not in price order — one city or one country gives one currency.`
1889
1993
  : '',
@@ -1893,7 +1997,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
1893
1997
  answer.overBudget && hotels.length
1894
1998
  ? `${answer.overBudget} more matched and came in over the ceiling.`
1895
1999
  : '',
1896
- asked.filters ? `Filtered to ${filterWords(asked.filters)}.` : '',
2000
+ filtersSaid && filterWords(filtersSaid) ? `Filtered to ${filterWords(filtersSaid)}.` : '',
1897
2001
  // What the prices on a dated list are, in one line - and how many cannot
1898
2002
  // be had at all, so a sold-out hotel is not described as a choice.
1899
2003
  ask.stay
@@ -2054,7 +2158,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2054
2158
  },
2055
2159
  text: [
2056
2160
  `**${h.name}** · ${enough.length} room ${enough.length === 1 ? 'type' : 'types'} for ${stay.checkIn} to ${stay.checkOut}, ${stay.guests} guests${roomsWanted > 1 ? `, ${roomsWanted} rooms` : ''}.`,
2057
- ...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? ', the free cancellation window has closed - cancelling costs a night' : ', non-refundable'}`),
2161
+ ...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? ', the free cancellation window has closed - cancelling now has a fee' : ', non-refundable'}`),
2058
2162
  flex.text,
2059
2163
  deps.vocab.demo ? DEMO_NOTE : '',
2060
2164
  ]
@@ -2270,7 +2374,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2270
2374
  error: 'no-such-extra',
2271
2375
  query: extraRef,
2272
2376
  },
2273
- text: `${detail.hotel.name} does not offer that. ${t.listExtras} lists what it has.`,
2377
+ text: (deps.backend.capabilities ?? FULL_CAPABILITIES).extras
2378
+ ? `${detail.hotel.name} does not offer that. ${t.listExtras} lists what it has.`
2379
+ : `${deps.vocab.brand} ${deps.vocab.nounPlural} sell rooms only here - there are no add-ons.`,
2274
2380
  };
2275
2381
  }
2276
2382
  const line = {
@@ -2469,11 +2575,11 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2469
2575
  },
2470
2576
  text: [
2471
2577
  several
2472
- ? `${booked.length} bookings, one for each stay - each hotel holds its own nights under its own code, and each is changed or cancelled on its own.`
2578
+ ? `${booked.length} bookings, one for each stay - each hotel keeps its own nights under its own code, and each is ${(deps.backend.capabilities ?? FULL_CAPABILITIES).changes ? 'changed or cancelled' : 'cancelled'} on its own.`
2473
2579
  : '',
2474
2580
  ...booked.flatMap(({ booking, asked }) => [
2475
2581
  `**${booking.confirmationCode}** · ${booking.hotelName} · ${booking.stay.checkIn} to ${booking.stay.checkOut}`,
2476
- `${formatMoney(booking.quote.total)} all in. **Nothing has been charged** - the rooms are held until ${booking.holdUntil?.slice(0, 16).replace('T', ' ')} UTC, and ${booking.guest.name} presents a card at check-in.`,
2582
+ bookedLine(deps, booking),
2477
2583
  ...asked,
2478
2584
  ]),
2479
2585
  remembered?.line ?? '',
@@ -2737,18 +2843,23 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2737
2843
  const cancelled = await deps.backend.cancelBooking(ref, who, str(args.reason) || undefined);
2738
2844
  if (!cancelled)
2739
2845
  return asPreview;
2846
+ // ⚠️ WHAT THE HOTEL SAYS IT CHARGED, once it has said. A group's own system
2847
+ // answers a cancellation with its penalty; the preview was our reading of
2848
+ // the rate's terms, and where the two differ the hotel's is the one that
2849
+ // will be charged.
2850
+ const charged = cancelled.cancellationFee ?? preview.fee;
2740
2851
  return {
2741
2852
  structured: {
2742
2853
  view: 'cancelled',
2743
2854
  ...common(deps, key),
2744
2855
  booking: { ...cancelled, quote: quoteForCard(cancelled.quote) },
2745
- fee: formatMoney(preview.fee),
2856
+ fee: formatMoney(charged),
2746
2857
  refund: formatMoney(preview.refund),
2747
2858
  rule: preview.rule,
2748
2859
  cancellable: false,
2749
2860
  },
2750
2861
  text: [
2751
- `${cancelled.confirmationCode} is cancelled. ${preview.fee.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(preview.fee)}.`}`,
2862
+ `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(charged)}.`}`,
2752
2863
  deps.vocab.demo ? DEMO_NOTE : '',
2753
2864
  ]
2754
2865
  .filter(Boolean)