@curless/hotel-storefront 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend.d.ts +7 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js.map +1 -1
- package/dist/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/demo-hotels.d.ts.map +1 -1
- package/dist/demo-hotels.js +7 -1
- package/dist/demo-hotels.js.map +1 -1
- package/dist/huazhu/backend.d.ts +11 -1
- package/dist/huazhu/backend.d.ts.map +1 -1
- package/dist/huazhu/backend.js +516 -32
- package/dist/huazhu/backend.js.map +1 -1
- package/dist/huazhu/bookings.d.ts +37 -1
- package/dist/huazhu/bookings.d.ts.map +1 -1
- package/dist/huazhu/bookings.js.map +1 -1
- package/dist/huazhu/make.d.ts.map +1 -1
- package/dist/huazhu/make.js +25 -0
- package/dist/huazhu/make.js.map +1 -1
- package/dist/huazhu/sandbox-photos.d.ts +7 -0
- package/dist/huazhu/sandbox-photos.d.ts.map +1 -1
- package/dist/huazhu/sandbox-photos.js +7 -0
- package/dist/huazhu/sandbox-photos.js.map +1 -1
- package/dist/huazhu/sandbox.d.ts +2 -0
- package/dist/huazhu/sandbox.d.ts.map +1 -1
- package/dist/huazhu/sandbox.js +14 -6
- package/dist/huazhu/sandbox.js.map +1 -1
- package/dist/huazhu/sellable.d.ts +17 -0
- package/dist/huazhu/sellable.d.ts.map +1 -1
- package/dist/huazhu/sellable.js +37 -8
- package/dist/huazhu/sellable.js.map +1 -1
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +19 -8
- package/dist/legal.js.map +1 -1
- package/dist/payments/checkout.d.ts +71 -0
- package/dist/payments/checkout.d.ts.map +1 -0
- package/dist/payments/checkout.js +133 -0
- package/dist/payments/checkout.js.map +1 -0
- package/dist/payments/fake-gateway.d.ts +43 -0
- package/dist/payments/fake-gateway.d.ts.map +1 -0
- package/dist/payments/fake-gateway.js +135 -0
- package/dist/payments/fake-gateway.js.map +1 -0
- package/dist/server.d.ts +4 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +19 -4
- package/dist/server.js.map +1 -1
- package/dist/supplier.d.ts +2 -0
- package/dist/supplier.d.ts.map +1 -1
- package/dist/supplier.js.map +1 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +7 -2
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +15 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +127 -16
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +41 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -208,7 +208,9 @@ const forCapabilities = (tools, n, v, caps) => {
|
|
|
208
208
|
// itself: the instructions carried "test environment" while this
|
|
209
209
|
// line still promised "the hotel's own reservation" (2026-09-30).
|
|
210
210
|
description: v.sandbox
|
|
211
|
-
? `Makes a booking in a simulated copy of ${v.brand}'s booking system, for testing: each stay gets its own confirmation code there, and none of it is a ${v.brand} reservation or holds a real room.
|
|
211
|
+
? `Makes a booking in a simulated copy of ${v.brand}'s booking system, for testing: each stay gets its own confirmation code there, and none of it is a ${v.brand} reservation or holds a real room. ${caps.prepaid
|
|
212
|
+
? 'A rate marked paid in advance is booked on its own, and not at once: the answer gives a card page in test mode and the time it is paid by, and the stay is booked once it is paid - no card is collected here. Every other rate is paid by nobody.'
|
|
213
|
+
: 'No payment is taken 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.`
|
|
212
214
|
: `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.`,
|
|
213
215
|
};
|
|
214
216
|
}
|
|
@@ -864,13 +866,13 @@ const CHANGE_SCHEMA = {
|
|
|
864
866
|
* policies, and every trip a member badge and saving (2026-10-05 audit) -
|
|
865
867
|
* fields no answer there ever carries.
|
|
866
868
|
*/
|
|
867
|
-
export const outputSchemasFor = (schemas, caps) => {
|
|
869
|
+
export const outputSchemasFor = (schemas, caps, lang, also = {}) => {
|
|
868
870
|
const gone = [
|
|
869
871
|
...(caps.members ? [] : ['member', 'memberSaving']),
|
|
870
872
|
...(caps.extras ? [] : ['extras']),
|
|
871
873
|
...(caps.policies === false ? ['policies'] : []),
|
|
872
874
|
];
|
|
873
|
-
if (!gone.length && !caps.hourly)
|
|
875
|
+
if (!gone.length && !caps.hourly && !lang && !also.logoDark && !also.accent)
|
|
874
876
|
return schemas;
|
|
875
877
|
return Object.fromEntries(Object.entries(schemas).map(([name, schema]) => {
|
|
876
878
|
const s = schema;
|
|
@@ -882,6 +884,15 @@ export const outputSchemasFor = (schemas, caps) => {
|
|
|
882
884
|
// On every answer, as `common` puts it: rooms by the hour are sold here.
|
|
883
885
|
if (caps.hourly)
|
|
884
886
|
properties.hourly = BOOL;
|
|
887
|
+
// On every answer too: the language the card draws its own words in.
|
|
888
|
+
if (lang)
|
|
889
|
+
properties.lang = { enum: ['zh', 'en'] };
|
|
890
|
+
// And the dark page's logo, where the group gave one.
|
|
891
|
+
if (also.logoDark)
|
|
892
|
+
properties.logoDarkUrl = STR;
|
|
893
|
+
// And the group's own colour, where its deployment gives one.
|
|
894
|
+
if (also.accent)
|
|
895
|
+
properties.accent = { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' };
|
|
885
896
|
return [name, { ...s, properties }];
|
|
886
897
|
}));
|
|
887
898
|
};
|
|
@@ -1189,7 +1200,48 @@ const holderOf = (deps, supplier) => {
|
|
|
1189
1200
|
* room - and a booking whose confirmation has not come back yet is neither,
|
|
1190
1201
|
* and is said as exactly that.
|
|
1191
1202
|
*/
|
|
1203
|
+
/**
|
|
1204
|
+
* A booking as the card is given it: its money as strings, like every other
|
|
1205
|
+
* price - the card never does arithmetic, and an amount sent as
|
|
1206
|
+
* `{ minor, currency }` was drawn as "[object Object]" (香格里拉, 2026-10-12).
|
|
1207
|
+
*/
|
|
1208
|
+
const bookingForCard = (b) => ({
|
|
1209
|
+
...b,
|
|
1210
|
+
quote: quoteForCard(b.quote),
|
|
1211
|
+
...(b.payment ? { payment: { ...b.payment, amount: formatMoney(b.payment.amount) } } : {}),
|
|
1212
|
+
});
|
|
1213
|
+
/**
|
|
1214
|
+
* A booking paid here in advance, as the guest is told it - by where its
|
|
1215
|
+
* money stands. Undefined for any other booking.
|
|
1216
|
+
*/
|
|
1217
|
+
const paidLine = (deps, b) => {
|
|
1218
|
+
const p = b.payment;
|
|
1219
|
+
if (!p)
|
|
1220
|
+
return undefined;
|
|
1221
|
+
const h = holderOf(deps, b.supplier);
|
|
1222
|
+
const amount = formatMoney(p.amount);
|
|
1223
|
+
const test = h.sandbox
|
|
1224
|
+
? ' This is a test: the card page is in test mode, no real money moves, and the booking is in the simulated system.'
|
|
1225
|
+
: '';
|
|
1226
|
+
switch (p.status) {
|
|
1227
|
+
case 'awaiting':
|
|
1228
|
+
return `**Not booked yet, and nothing paid.** ${amount} is paid on the card page by ${p.payByLocal ?? p.payBy} hotel time: ${p.url}. Once it is paid the stay is booked with ${h.name}, and ${deps.tools.listBookings} shows it; unpaid by then, the page closes and nothing is booked or charged.${test}`;
|
|
1229
|
+
case 'paid':
|
|
1230
|
+
return b.status === 'confirmed'
|
|
1231
|
+
? `${amount} paid in advance by card, and booked with ${h.name}: nothing more is paid at the hotel.${test}`
|
|
1232
|
+
: `${amount} paid in advance by card. ${h.name} has not confirmed the booking yet - ${deps.tools.listBookings} shows when it has; if it does not take it, all ${amount} goes back.${test}`;
|
|
1233
|
+
case 'lapsed':
|
|
1234
|
+
return `Not paid in time: nothing was booked and nothing was charged.${test}`;
|
|
1235
|
+
case 'refunding':
|
|
1236
|
+
return `All ${amount} is going back to the card it was paid with${p.refundReason ? ` - ${p.refundReason}` : ''}.${test}`;
|
|
1237
|
+
case 'refunded':
|
|
1238
|
+
return `All ${amount} has gone back to the card it was paid with${p.refundReason ? ` - ${p.refundReason}` : ''}.${test}`;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1192
1241
|
const bookedLine = (deps, b) => {
|
|
1242
|
+
const paid = paidLine(deps, b);
|
|
1243
|
+
if (paid)
|
|
1244
|
+
return paid;
|
|
1193
1245
|
const total = formatMoney(b.quote.total);
|
|
1194
1246
|
const h = holderOf(deps, b.supplier);
|
|
1195
1247
|
if (h.caps.booking === 'pay-at-hotel') {
|
|
@@ -1224,6 +1276,8 @@ const common = (deps, key) => {
|
|
|
1224
1276
|
return {
|
|
1225
1277
|
brand: deps.vocab.brand,
|
|
1226
1278
|
...(deps.logoUrl ? { logoUrl: deps.logoUrl } : {}),
|
|
1279
|
+
...(deps.logoDarkUrl ? { logoDarkUrl: deps.logoDarkUrl } : {}),
|
|
1280
|
+
...(deps.accent ? { accent: deps.accent } : {}),
|
|
1227
1281
|
...(deps.showName ? { showName: true } : {}),
|
|
1228
1282
|
toolNames: deps.offered
|
|
1229
1283
|
? Object.fromEntries(Object.entries(deps.tools).filter(([, name]) => deps.offered?.has(name)))
|
|
@@ -1245,8 +1299,19 @@ const common = (deps, key) => {
|
|
|
1245
1299
|
vocab: { noun: deps.vocab.noun, nounPlural: deps.vocab.nounPlural },
|
|
1246
1300
|
...(deps.vocab.demo ? { demo: true, demoNote: DEMO_NOTE } : { demo: false }),
|
|
1247
1301
|
...(key ? memberBadge(deps, key) : {}),
|
|
1302
|
+
...(langOf(deps, key) ? { lang: langOf(deps, key) } : {}),
|
|
1248
1303
|
};
|
|
1249
1304
|
};
|
|
1305
|
+
/**
|
|
1306
|
+
* The language the CARD speaks to this guest - its own buttons, labels and
|
|
1307
|
+
* dates. What the server writes for the model stays English.
|
|
1308
|
+
*
|
|
1309
|
+
* ⚠️ PER TRIP, NOT ONLY PER BRAND (总线程, 2026-10-12): today it is the
|
|
1310
|
+
* deployment's (Huazhu's single store is Chinese), and a guest's own choice -
|
|
1311
|
+
* as the market's \`set_language\` keeps one per buyer - is meant to come in
|
|
1312
|
+
* HERE, by \`key\`, without the card or the schema changing.
|
|
1313
|
+
*/
|
|
1314
|
+
const langOf = (deps, _key) => deps.lang;
|
|
1250
1315
|
/**
|
|
1251
1316
|
* When a room stops being free to cancel, the way a guest reads it: the
|
|
1252
1317
|
* hotel's own clock when the backend knows it, else the instant in UTC - never
|
|
@@ -1278,6 +1343,12 @@ const quoteForCard = (quote, groupOf) => ({
|
|
|
1278
1343
|
total: formatMoney(l.total),
|
|
1279
1344
|
refundable: l.refundable,
|
|
1280
1345
|
freeCancellationUntil: l.freeCancellationUntil,
|
|
1346
|
+
// The deadline on the hotel's clock, where the backend knows it - the
|
|
1347
|
+
// review restates the terms beside the total (round-2 jury).
|
|
1348
|
+
...(l.freeCancellationLocal ? { freeCancellationLocal: l.freeCancellationLocal } : {}),
|
|
1349
|
+
// Paid in advance: the review says so, and its button leads to the
|
|
1350
|
+
// payment page rather than to a booking (香格里拉's card, 2026-10-12).
|
|
1351
|
+
...(l.payment ? { payment: l.payment } : {}),
|
|
1281
1352
|
day: l.day,
|
|
1282
1353
|
})),
|
|
1283
1354
|
subtotal: formatMoney(quote.subtotal),
|
|
@@ -1541,6 +1612,13 @@ const labelWords = (r) => {
|
|
|
1541
1612
|
];
|
|
1542
1613
|
return words.length ? ` [${words.join(', ')}]` : '';
|
|
1543
1614
|
};
|
|
1615
|
+
const PAYMENT_WORDS = {
|
|
1616
|
+
awaiting: 'to be paid on the card page',
|
|
1617
|
+
paid: 'paid in advance by card',
|
|
1618
|
+
lapsed: 'not paid in time - nothing booked or charged',
|
|
1619
|
+
refunding: 'refund in full under way',
|
|
1620
|
+
refunded: 'refunded in full',
|
|
1621
|
+
};
|
|
1544
1622
|
const bookingLine = (b) => {
|
|
1545
1623
|
const kinds = b.requests?.kinds ?? [];
|
|
1546
1624
|
return [
|
|
@@ -1550,6 +1628,9 @@ const bookingLine = (b) => {
|
|
|
1550
1628
|
? 'unconfirmed: whether it was made is not known yet, and a person is confirming it'
|
|
1551
1629
|
: b.status}`,
|
|
1552
1630
|
kinds.length ? ` · asked for ${kinds.map(bare).join(', ')}` : '',
|
|
1631
|
+
b.payment
|
|
1632
|
+
? ` · ${PAYMENT_WORDS[b.payment.status]}${b.payment.status === 'awaiting' && b.payment.url ? ` by ${b.payment.payByLocal ?? b.payment.payBy}: ${b.payment.url}` : ''}`
|
|
1633
|
+
: '',
|
|
1553
1634
|
].join('');
|
|
1554
1635
|
};
|
|
1555
1636
|
/**
|
|
@@ -1594,6 +1675,13 @@ const dateFlex = (dates, stay) => {
|
|
|
1594
1675
|
// the other two are worse, which is a claim about prices that are equal.
|
|
1595
1676
|
const beats = best?.from && chosen?.from && best.from.minor < chosen.from.minor ? best : undefined;
|
|
1596
1677
|
const beatsBy = beats?.from?.minor;
|
|
1678
|
+
// The cheapest of the window - the chosen date too - when two or more are
|
|
1679
|
+
// priced in one currency; ties all marked. `best` stays what it was: a date
|
|
1680
|
+
// that beats the guest's own.
|
|
1681
|
+
const priced = dates.filter((d) => d.from);
|
|
1682
|
+
const lowest = priced.length >= 2 && new Set(priced.map((d) => d.from?.currency)).size === 1
|
|
1683
|
+
? Math.min(...priced.map((d) => d.from?.minor ?? Number.POSITIVE_INFINITY))
|
|
1684
|
+
: undefined;
|
|
1597
1685
|
const structured = {
|
|
1598
1686
|
dates: dates.map((d) => ({
|
|
1599
1687
|
checkIn: d.checkIn,
|
|
@@ -1602,6 +1690,7 @@ const dateFlex = (dates, stay) => {
|
|
|
1602
1690
|
...(d.chosen ? { chosen: true } : {}),
|
|
1603
1691
|
// The chosen date cannot match: `beatsBy` is a price strictly below it.
|
|
1604
1692
|
...(beatsBy !== undefined && d.from?.minor === beatsBy ? { best: true } : {}),
|
|
1693
|
+
...(lowest !== undefined && d.from?.minor === lowest ? { lowest: true } : {}),
|
|
1605
1694
|
})),
|
|
1606
1695
|
};
|
|
1607
1696
|
if (!best?.from)
|
|
@@ -2434,8 +2523,19 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
2434
2523
|
.join(' '),
|
|
2435
2524
|
};
|
|
2436
2525
|
}
|
|
2526
|
+
// ⚠️ THE CHEAPEST, SAID BY US - the card never compares two prices it was
|
|
2527
|
+
// handed as text. Only on named nights, in one currency, with two or more
|
|
2528
|
+
// priced to compare; every hotel that ties is marked (总线程, 2026-10-12).
|
|
2529
|
+
// A room by the hour is priced for its own hours, and is not in the race.
|
|
2530
|
+
const stays = hotels.filter((h) => h.forStay && !h.forStay.hours);
|
|
2531
|
+
const lowest = stays.length >= 2 && new Set(stays.map((h) => h.forStay?.total.currency)).size === 1
|
|
2532
|
+
? Math.min(...stays.map((h) => h.forStay?.total.minor ?? Number.POSITIVE_INFINITY))
|
|
2533
|
+
: undefined;
|
|
2437
2534
|
const forCard = hotels.map((h) => ({
|
|
2438
2535
|
...h,
|
|
2536
|
+
...(lowest !== undefined && h.forStay && !h.forStay.hours && h.forStay.total.minor === lowest
|
|
2537
|
+
? { lowest: true }
|
|
2538
|
+
: {}),
|
|
2439
2539
|
summary: hotelText('description', h.name, h.summary, DESCRIPTION_MAX).text,
|
|
2440
2540
|
fromNightly: h.fromNightly ? formatMoney(h.fromNightly) : undefined,
|
|
2441
2541
|
...(h.forStay
|
|
@@ -3037,6 +3137,10 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3037
3137
|
error: 'no-such-room',
|
|
3038
3138
|
query: roomRef,
|
|
3039
3139
|
rooms: dated.rooms.map((r) => ({ id: r.id, name: r.name })),
|
|
3140
|
+
// The hotel and the nights, so the card can take the guest straight
|
|
3141
|
+
// back to its rooms rather than to the start (round-2 jury).
|
|
3142
|
+
hotel: { id: detail.hotel.id, name: detail.hotel.name },
|
|
3143
|
+
stay,
|
|
3040
3144
|
},
|
|
3041
3145
|
text: `${detail.hotel.name} has no room type by that name for those nights. It has: ${dated.rooms.map((r) => r.name).join(', ')}.`,
|
|
3042
3146
|
};
|
|
@@ -3192,16 +3296,20 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3192
3296
|
const several = booked.length > 1;
|
|
3193
3297
|
return {
|
|
3194
3298
|
structured: {
|
|
3299
|
+
// ⚠️ 'booking' EVEN WHEN IT IS AWAITING PAYMENT: the card draws a view
|
|
3300
|
+
// only once it knows it, and the card-contract test holds every view
|
|
3301
|
+
// here to that. 香格里拉's card turns this into its own 'payment' step
|
|
3302
|
+
// (booking.status 'awaiting-payment', booking.payment) - and moves the
|
|
3303
|
+
// view with it.
|
|
3195
3304
|
view: 'booking',
|
|
3196
3305
|
...common(deps, key),
|
|
3197
|
-
booking:
|
|
3306
|
+
booking: bookingForCard(first.booking),
|
|
3198
3307
|
// ⚠️ ONE PER STAY - see `requestBooking`. The card draws each with its
|
|
3199
3308
|
// own code; `booking` stays the first, for a host that reads one.
|
|
3200
3309
|
...(several
|
|
3201
3310
|
? {
|
|
3202
3311
|
bookings: booked.map(({ booking, asked }) => ({
|
|
3203
|
-
...booking,
|
|
3204
|
-
quote: quoteForCard(booking.quote),
|
|
3312
|
+
...bookingForCard(booking),
|
|
3205
3313
|
total: formatMoney(booking.quote.total),
|
|
3206
3314
|
...(asked.length ? { requested: asked } : {}),
|
|
3207
3315
|
// Each its own warning colour - it rode on the first alone.
|
|
@@ -3290,7 +3398,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3290
3398
|
structured: {
|
|
3291
3399
|
view: 'bookings',
|
|
3292
3400
|
...common(deps, key),
|
|
3293
|
-
bookings: bookings.map(
|
|
3401
|
+
bookings: bookings.map(bookingForCard),
|
|
3294
3402
|
count: bookings.length,
|
|
3295
3403
|
// What was looked for, so a card can say THAT did not match rather
|
|
3296
3404
|
// than "nothing in this conversation".
|
|
@@ -3424,7 +3532,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3424
3532
|
structured: {
|
|
3425
3533
|
view: 'changed',
|
|
3426
3534
|
...common(deps, key),
|
|
3427
|
-
booking:
|
|
3535
|
+
booking: bookingForCard(changed),
|
|
3428
3536
|
hotelName: preview.hotelName,
|
|
3429
3537
|
from: stayOf(preview.from),
|
|
3430
3538
|
...(preview.to ? { to: stayOf(preview.to) } : {}),
|
|
@@ -3525,19 +3633,22 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3525
3633
|
structured: {
|
|
3526
3634
|
view: 'cancelled',
|
|
3527
3635
|
...common(deps, key),
|
|
3528
|
-
booking:
|
|
3636
|
+
booking: bookingForCard(cancelled),
|
|
3529
3637
|
fee: formatMoney(charged),
|
|
3530
3638
|
rule: preview.rule,
|
|
3531
3639
|
cancellable: false,
|
|
3532
3640
|
},
|
|
3533
3641
|
text: [
|
|
3534
|
-
|
|
3535
|
-
? //
|
|
3536
|
-
`${cancelled.confirmationCode} is
|
|
3537
|
-
: h.
|
|
3538
|
-
? //
|
|
3539
|
-
`${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this is a
|
|
3540
|
-
:
|
|
3642
|
+
cancelled.payment
|
|
3643
|
+
? // Paid here in advance: what becomes of the money is the answer.
|
|
3644
|
+
`${cancelled.confirmationCode} is ${cancelled.status === 'expired' ? 'called off before it was paid' : 'cancelled'}. ${paidLine(deps, cancelled)}`
|
|
3645
|
+
: h.sandbox
|
|
3646
|
+
? // A test booking cancelled in the simulation: no hotel, no charge (2026-10-05 audit, P2).
|
|
3647
|
+
`${cancelled.confirmationCode} is cancelled in the simulated system. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this one is a test, and nothing is charged.`}`
|
|
3648
|
+
: h.demo
|
|
3649
|
+
? // Nobody charges a demonstration anything (2026-10-05 audit, P2).
|
|
3650
|
+
`${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this is a demonstration, and nothing is charged.`}`
|
|
3651
|
+
: `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(charged)}.`}`,
|
|
3541
3652
|
demoBookingNote(deps, cancelled.supplier),
|
|
3542
3653
|
deps.vocab.demo ? DEMO_NOTE : '',
|
|
3543
3654
|
]
|