@curless/hotel-storefront 0.37.1 → 0.38.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 +43 -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 +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/huazhu/backend.d.ts +7 -0
- package/dist/huazhu/backend.d.ts.map +1 -1
- package/dist/huazhu/backend.js +306 -7
- package/dist/huazhu/backend.js.map +1 -1
- package/dist/huazhu/bookings.d.ts +9 -0
- package/dist/huazhu/bookings.d.ts.map +1 -1
- package/dist/huazhu/bookings.js.map +1 -1
- package/dist/huazhu/fake.d.ts.map +1 -1
- package/dist/huazhu/fake.js +5 -0
- package/dist/huazhu/fake.js.map +1 -1
- package/dist/huazhu/make.d.ts.map +1 -1
- package/dist/huazhu/make.js +28 -1
- package/dist/huazhu/make.js.map +1 -1
- package/dist/huazhu/wire.d.ts +17 -0
- package/dist/huazhu/wire.d.ts.map +1 -1
- package/dist/huazhu/wire.js.map +1 -1
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +23 -4
- package/dist/legal.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/payments/fake-unionpay.d.ts +22 -0
- package/dist/payments/fake-unionpay.d.ts.map +1 -0
- package/dist/payments/fake-unionpay.js +89 -0
- package/dist/payments/fake-unionpay.js.map +1 -0
- package/dist/payments/unionpay.d.ts +125 -0
- package/dist/payments/unionpay.d.ts.map +1 -0
- package/dist/payments/unionpay.js +192 -0
- package/dist/payments/unionpay.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -3
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts +24 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +124 -2
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +14 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,
|
|
1
|
+
{"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,sm01BAAsm01B,EACtm01B,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,6GAA6G;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -263,6 +263,21 @@ export type ApiSource = {
|
|
|
263
263
|
merchantUrl?: string;
|
|
264
264
|
/** Only `card` is taken yet. */
|
|
265
265
|
method?: 'card';
|
|
266
|
+
/**
|
|
267
|
+
* A second way to pay a prepaid rate: UnionPay's page, whose one-time card
|
|
268
|
+
* the hotel's system charges (Huazhu's CCPN) - robin, 2026-10-15: "OPN 走
|
|
269
|
+
* stripe,信用卡走银联,两个都接上". Through the agentbank gateway, on a
|
|
270
|
+
* test-mode merchant: its key in `<prefix>_GATEWAY_KEY`, an agent key of
|
|
271
|
+
* the same organisation in `<prefix>_AGENT_KEY`. Huazhu's test
|
|
272
|
+
* environment or the simulation only. See docs/huazhu-unionpay-apop.md.
|
|
273
|
+
*/
|
|
274
|
+
unionpay?: {
|
|
275
|
+
gatewayUrl: string;
|
|
276
|
+
merchantId: string;
|
|
277
|
+
/** On UnionPay's page - ASCII only. Default: the brand. */
|
|
278
|
+
merchantName?: string;
|
|
279
|
+
merchantUrl?: string;
|
|
280
|
+
};
|
|
266
281
|
};
|
|
267
282
|
/**
|
|
268
283
|
* A TEST deployment: `baseUrl` and `authUrl` answer from the simulated
|
|
@@ -356,6 +371,7 @@ export declare const toolNames: (config: Pick<HotelConfig, "naming">) => {
|
|
|
356
371
|
readonly signIn: `sign_in_${string}member`;
|
|
357
372
|
readonly signOut: `sign_out_${string}member`;
|
|
358
373
|
readonly forgetPreferences: `forget_${string}preferences`;
|
|
374
|
+
readonly payWithUnionPay: `pay_${string}booking_with_unionpay`;
|
|
359
375
|
};
|
|
360
376
|
export type ToolNames = ReturnType<typeof toolNames>;
|
|
361
377
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -42,6 +42,10 @@ export const toolNames = (config) => {
|
|
|
42
42
|
signIn: `sign_in_${prefix}member`,
|
|
43
43
|
signOut: `sign_out_${prefix}member`,
|
|
44
44
|
forgetPreferences: `forget_${prefix}preferences`,
|
|
45
|
+
// The card's own, never the model's (`ui.visibility: ['app']`): a stay
|
|
46
|
+
// waiting for its payment, paid with UnionPay instead - offered only where
|
|
47
|
+
// the backend can (`capabilities.unionpay`).
|
|
48
|
+
payWithUnionPay: `pay_${prefix}booking_with_unionpay`,
|
|
45
49
|
};
|
|
46
50
|
};
|
|
47
51
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAoWA,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CACjC,CAAC;KACE,WAAW,EAAE;KACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;KAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAmC,EAAE,EAAE;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,OAAO,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAC7D,OAAO;QACL,UAAU,EAAE,QAAQ,MAAM,GAAG,MAAM,EAAE;QACrC,QAAQ,EAAE,OAAO,MAAM,GAAG,IAAI,EAAE;QAChC,yEAAyE;QACzE,0EAA0E;QAC1E,+CAA+C;QAC/C,MAAM,EAAE,WAAW,MAAM,GAAG,MAAM,EAAE;QACpC,UAAU,EAAE,QAAQ,MAAM,QAAQ;QAClC,SAAS,EAAE,UAAU,MAAM,MAAM;QACjC,QAAQ,EAAE,QAAQ,MAAM,MAAM;QAC9B,SAAS,EAAE,SAAS,MAAM,MAAM;QAChC,cAAc,EAAE,WAAW,MAAM,SAAS;QAC1C,YAAY,EAAE,WAAW,MAAM,UAAU;QACzC,mBAAmB,EAAE,WAAW,MAAM,cAAc;QACpD,aAAa,EAAE,UAAU,MAAM,SAAS;QACxC,yEAAyE;QACzE,kEAAkE;QAClE,aAAa,EAAE,WAAW,MAAM,gBAAgB;QAChD,aAAa,EAAE,UAAU,MAAM,SAAS;QACxC,QAAQ,EAAE,OAAO,MAAM,GAAG,IAAI,WAAW;QACzC,wEAAwE;QACxE,2EAA2E;QAC3E,6DAA6D;QAC7D,OAAO,EAAE,WAAW,MAAM,GAAG,MAAM,EAAE;QACrC,SAAS,EAAE,QAAQ,MAAM,WAAW;QACpC,MAAM,EAAE,WAAW,MAAM,QAAQ;QACjC,OAAO,EAAE,YAAY,MAAM,QAAQ;QACnC,iBAAiB,EAAE,UAAU,MAAM,aAAa;QAChD,uEAAuE;QACvE,2EAA2E;QAC3E,6CAA6C;QAC7C,eAAe,EAAE,OAAO,MAAM,uBAAuB;KAC7C,CAAC;AACb,CAAC,CAAC"}
|
package/dist/huazhu/backend.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BackendCapabilities, type HotelBackend } from '../backend.js';
|
|
2
2
|
import { type BookingLimits } from '../limits.js';
|
|
3
3
|
import type { Checkout } from '../payments/checkout.js';
|
|
4
|
+
import { type UnionPayHandoff } from '../payments/unionpay.js';
|
|
4
5
|
import type { Quote, ShownQuote } from '../types.js';
|
|
5
6
|
import { type BookingStore, type Reconciler, type StoredBooking } from './bookings.js';
|
|
6
7
|
import { type Catalog } from './catalog.js';
|
|
@@ -37,6 +38,12 @@ export type HuazhuHotelsOptions = {
|
|
|
37
38
|
*/
|
|
38
39
|
prepaid?: {
|
|
39
40
|
checkout: Checkout;
|
|
41
|
+
/**
|
|
42
|
+
* The other way to pay a prepaid rate: UnionPay's page, whose one-time
|
|
43
|
+
* card Huazhu charges (CCPN) - robin, 2026-10-15: "OPN 走stripe,信用卡走
|
|
44
|
+
* 银联,两个都接上". See docs/huazhu-unionpay-apop.md.
|
|
45
|
+
*/
|
|
46
|
+
unionpay?: UnionPayHandoff;
|
|
40
47
|
payWithinMs?: number;
|
|
41
48
|
};
|
|
42
49
|
now?: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/huazhu/backend.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/huazhu/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/huazhu/backend.js
CHANGED
|
@@ -6,6 +6,7 @@ import { FACILITIES, hasFacility } from '../facilities.js';
|
|
|
6
6
|
import { endOf, startProblem } from '../hourly.js';
|
|
7
7
|
import { BOOKING_LIMITS, DAY_MS, HOUR_MS, WindowCounter } from '../limits.js';
|
|
8
8
|
import { formatMoney, minorFactor, money, multiplyMoney, toMinor } from '../money.js';
|
|
9
|
+
import { expiryMMyy, lastFour, } from '../payments/unionpay.js';
|
|
9
10
|
import { MAX_ROOMS_PER_LINE, quoteLineKey } from '../trip.js';
|
|
10
11
|
import { IN_REQUEST_AT_MS, StoreWriteError, nextCheckAt, statusOf, } from './bookings.js';
|
|
11
12
|
import { describePolicy, feeFor, freeUntilOf, nightAuditOf, parseCancelPolicy, penaltyAt, refundableFrom, } from './cancel-policy.js';
|
|
@@ -473,6 +474,7 @@ export const createHuazhuHotels = (opts) => {
|
|
|
473
474
|
if (opts.prepaid && !opts.sandbox && !opts.testEnvironment)
|
|
474
475
|
throw new Error("prepaid rates are sold against the simulated Huazhu or Huazhu's test environment only, until robin decides who takes the money (docs/huazhu-products-plan.md)");
|
|
475
476
|
const checkout = opts.prepaid?.checkout;
|
|
477
|
+
const unionpay = opts.prepaid?.unionpay;
|
|
476
478
|
/** Unpaid this long, the page is closed and the booking lapses - inside the card page's own ~31 minutes. */
|
|
477
479
|
const payWithin = opts.prepaid?.payWithinMs ?? 30 * 60_000;
|
|
478
480
|
const prepaidType = checkout ? 'OPN' : undefined;
|
|
@@ -1255,6 +1257,37 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1255
1257
|
/** The money's side of a booking paid in advance, as the guest is shown it. */
|
|
1256
1258
|
const paymentOf = (b) => {
|
|
1257
1259
|
const p = b.prepaid;
|
|
1260
|
+
if (p.method === 'unionpay') {
|
|
1261
|
+
// The hotel charges the one-time card and gives back to it - nothing
|
|
1262
|
+
// here is taken or refunded.
|
|
1263
|
+
const status = b.status === 'cancelled' && p.issuedAt
|
|
1264
|
+
? 'hotel-refunds'
|
|
1265
|
+
: p.lapsedAt || p.declined
|
|
1266
|
+
? 'lapsed'
|
|
1267
|
+
: b.status === 'confirmed'
|
|
1268
|
+
? 'paid'
|
|
1269
|
+
: p.issuedAt
|
|
1270
|
+
? 'issued'
|
|
1271
|
+
: 'awaiting';
|
|
1272
|
+
return {
|
|
1273
|
+
method: 'unionpay',
|
|
1274
|
+
amount: b.quote.total,
|
|
1275
|
+
status,
|
|
1276
|
+
...(status === 'awaiting' && p.url ? { url: p.url } : {}),
|
|
1277
|
+
...(status === 'awaiting'
|
|
1278
|
+
? {
|
|
1279
|
+
payBy: new Date(p.payBy).toISOString(),
|
|
1280
|
+
payByLocal: deadlineAt(p.payBy, b.tz, en ? 'en' : 'zh'),
|
|
1281
|
+
payByLocalIso: localIsoAt(p.payBy, b.tz),
|
|
1282
|
+
}
|
|
1283
|
+
: {}),
|
|
1284
|
+
...(status === 'hotel-refunds'
|
|
1285
|
+
? {
|
|
1286
|
+
refundReason: 'Huazhu charged the UnionPay card, and gives back to that same card what its cancellation terms allow',
|
|
1287
|
+
}
|
|
1288
|
+
: {}),
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1258
1291
|
const refund = p.refund;
|
|
1259
1292
|
const status = refund
|
|
1260
1293
|
? refund.status === 'refunded'
|
|
@@ -1547,15 +1580,17 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1547
1580
|
* reconciler, whose write-off or "nobody could confirm it" refunds it too
|
|
1548
1581
|
* (`settlePrepaid`).
|
|
1549
1582
|
*/
|
|
1550
|
-
const bookPaid = async (id) => {
|
|
1583
|
+
const bookPaid = async (id, card) => {
|
|
1551
1584
|
const row = store.get(id);
|
|
1552
1585
|
const p = row?.prepaid;
|
|
1553
|
-
|
|
1586
|
+
// Paid on the card page (OPN), or a one-time card UnionPay issued (CCPN).
|
|
1587
|
+
if (!row || !p || !(p.paidAt || (card && p.issuedAt)) || row.status !== 'awaiting-payment')
|
|
1554
1588
|
return;
|
|
1555
1589
|
const contact = contactOf(row.guest);
|
|
1556
1590
|
if (typeof contact === 'string') {
|
|
1557
|
-
|
|
1558
|
-
|
|
1591
|
+
const why = "the guest's name could not be sent to Huazhu as written";
|
|
1592
|
+
store.update(id, { status: 'failed', ...(card ? { prepaid: { ...p, declined: why } } : {}) });
|
|
1593
|
+
await refundOf(id, why);
|
|
1559
1594
|
return;
|
|
1560
1595
|
}
|
|
1561
1596
|
const comment = commentOf(row.requests, row.guest.notes);
|
|
@@ -1564,7 +1599,19 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1564
1599
|
checkinType: row.hourly ? 'HOUR' : 'DAY',
|
|
1565
1600
|
contactPerson: contact,
|
|
1566
1601
|
distributorResId: row.distributorResId,
|
|
1567
|
-
paymentType: row.paymentType,
|
|
1602
|
+
paymentType: card ? 'CCPN' : row.paymentType,
|
|
1603
|
+
// ⚠️ THE ONE-TIME CARD, in this request and nowhere else: not on disk,
|
|
1604
|
+
// not in a log, not in an answer (docs/huazhu-unionpay-apop.md).
|
|
1605
|
+
...(card
|
|
1606
|
+
? {
|
|
1607
|
+
paymentCard: {
|
|
1608
|
+
cardType: 'CU',
|
|
1609
|
+
cardHolderName: row.guest.name.replace(/\s+/g, ' ').trim().slice(0, 64),
|
|
1610
|
+
cardNumber: card.number,
|
|
1611
|
+
expireDate: expiryMMyy(card),
|
|
1612
|
+
},
|
|
1613
|
+
}
|
|
1614
|
+
: {}),
|
|
1568
1615
|
reservations: [
|
|
1569
1616
|
{
|
|
1570
1617
|
roomTypeCode: row.roomTypeCode,
|
|
@@ -1617,6 +1664,19 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1617
1664
|
});
|
|
1618
1665
|
return;
|
|
1619
1666
|
}
|
|
1667
|
+
if (refusal && card) {
|
|
1668
|
+
// Paid with UnionPay: the hotel charges the card, and a booking it did
|
|
1669
|
+
// not make it never charged - there is nothing here to give back.
|
|
1670
|
+
store.update(id, {
|
|
1671
|
+
status: 'failed',
|
|
1672
|
+
pendingSince: undefined,
|
|
1673
|
+
prepaid: {
|
|
1674
|
+
...p,
|
|
1675
|
+
declined: `Huazhu did not take the booking: ${refusal.huazhuMessage} (${refusal.code})`.replace(/\d{12,19}/g, '****'),
|
|
1676
|
+
},
|
|
1677
|
+
});
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1620
1680
|
if (refusal) {
|
|
1621
1681
|
store.update(id, { status: 'failed', pendingSince: undefined });
|
|
1622
1682
|
// A price that moved since the prebook is one of these (5209): the
|
|
@@ -1654,6 +1714,67 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1654
1714
|
}
|
|
1655
1715
|
}
|
|
1656
1716
|
};
|
|
1717
|
+
/**
|
|
1718
|
+
* A confirmation that lands a little after `payBy` was made in time - the
|
|
1719
|
+
* callback is seconds behind the guest. Later than this, it is never used.
|
|
1720
|
+
*/
|
|
1721
|
+
const ISSUED_GRACE_MS = 5 * 60_000;
|
|
1722
|
+
/**
|
|
1723
|
+
* One step for a booking paid with UnionPay: confirmed on UnionPay's page →
|
|
1724
|
+
* the one-time card, once, straight into a CCPN booking with Huazhu;
|
|
1725
|
+
* unconfirmed past its time → lapsed. Nothing here is ever refunded: the
|
|
1726
|
+
* hotel charges the card, and gives back to it.
|
|
1727
|
+
*/
|
|
1728
|
+
const settleUnionPay = async (row) => {
|
|
1729
|
+
const p = row.prepaid;
|
|
1730
|
+
if (row.status !== 'awaiting-payment' || !unionpay)
|
|
1731
|
+
return;
|
|
1732
|
+
const lapse = () => store.update(row.id, {
|
|
1733
|
+
status: 'expired',
|
|
1734
|
+
prepaid: { ...p, lapsedAt: now(), url: undefined },
|
|
1735
|
+
});
|
|
1736
|
+
if (!p.sessionId) {
|
|
1737
|
+
if (now() >= p.payBy)
|
|
1738
|
+
lapse();
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
let state;
|
|
1742
|
+
try {
|
|
1743
|
+
state = await unionpay.state(p.sessionId);
|
|
1744
|
+
}
|
|
1745
|
+
catch {
|
|
1746
|
+
return; // asked again on the next pass
|
|
1747
|
+
}
|
|
1748
|
+
if (state === 'issued' && now() < p.payBy + ISSUED_GRACE_MS) {
|
|
1749
|
+
// ⚠️ ONCE: the gateway hands the card over a single time, then forgets it.
|
|
1750
|
+
let card;
|
|
1751
|
+
try {
|
|
1752
|
+
card = await unionpay.card(p.sessionId);
|
|
1753
|
+
}
|
|
1754
|
+
catch {
|
|
1755
|
+
return; // not handed over - asked again on the next pass
|
|
1756
|
+
}
|
|
1757
|
+
if (!card) {
|
|
1758
|
+
const why = 'UnionPay confirmed the payment, but its one-time card could not be read here - nothing was charged';
|
|
1759
|
+
store.update(row.id, {
|
|
1760
|
+
status: 'failed',
|
|
1761
|
+
prepaid: { ...p, url: undefined, declined: why },
|
|
1762
|
+
});
|
|
1763
|
+
console.error(`[huazhu prepaid ${row.id}] ${why}`);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
store.update(row.id, {
|
|
1767
|
+
prepaid: { ...p, issuedAt: now(), cardLastFour: lastFour(card), url: undefined },
|
|
1768
|
+
});
|
|
1769
|
+
console.log(`[huazhu prepaid ${row.id}] UnionPay issued a one-time card ···· ${lastFour(card)}: booking it with Huazhu as CCPN`);
|
|
1770
|
+
await bookPaid(row.id, card);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
// Unconfirmed in time: lapsed. A confirmation after this is never handed
|
|
1774
|
+
// on, so nothing is ever charged for it.
|
|
1775
|
+
if (state === 'closed' || now() >= p.payBy)
|
|
1776
|
+
lapse();
|
|
1777
|
+
};
|
|
1657
1778
|
/** Whether a booking paid in advance still has money work to do. */
|
|
1658
1779
|
let ticking = false;
|
|
1659
1780
|
const moneyPending = (b) => {
|
|
@@ -1662,6 +1783,9 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1662
1783
|
return false;
|
|
1663
1784
|
if (b.status === 'awaiting-payment')
|
|
1664
1785
|
return true;
|
|
1786
|
+
// Paid with UnionPay, the hotel charges and refunds the card: nothing here after the booking.
|
|
1787
|
+
if (p.method === 'unionpay')
|
|
1788
|
+
return false;
|
|
1665
1789
|
if (!p.paidAt)
|
|
1666
1790
|
return false;
|
|
1667
1791
|
// Booked: Huazhu's order number still to be written onto the payment, once.
|
|
@@ -1682,6 +1806,8 @@ export const createHuazhuHotels = (opts) => {
|
|
|
1682
1806
|
const p = row.prepaid;
|
|
1683
1807
|
if (!p || !checkout)
|
|
1684
1808
|
return;
|
|
1809
|
+
if (p.method === 'unionpay')
|
|
1810
|
+
return settleUnionPay(row);
|
|
1685
1811
|
if (row.status === 'awaiting-payment') {
|
|
1686
1812
|
if (!p.sessionId) {
|
|
1687
1813
|
if (now() >= p.payBy)
|
|
@@ -2157,6 +2283,158 @@ export const createHuazhuHotels = (opts) => {
|
|
|
2157
2283
|
}
|
|
2158
2284
|
return { booked: made.map((b) => publicBooking(store.get(b.id) ?? b)), failed };
|
|
2159
2285
|
};
|
|
2286
|
+
/**
|
|
2287
|
+
* Waits its turn behind the pass and the landing page - the one at a time
|
|
2288
|
+
* every step on a booking paid in advance takes - then runs.
|
|
2289
|
+
*/
|
|
2290
|
+
const inTurn = async (fn) => {
|
|
2291
|
+
for (let i = 0; ticking && i < 400; i += 1)
|
|
2292
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
2293
|
+
if (ticking)
|
|
2294
|
+
throw new Error('A payment here is being settled right now. Ask again in a moment.');
|
|
2295
|
+
ticking = true;
|
|
2296
|
+
try {
|
|
2297
|
+
return await fn();
|
|
2298
|
+
}
|
|
2299
|
+
finally {
|
|
2300
|
+
ticking = false;
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
/**
|
|
2304
|
+
* Pay a booking waiting for payment with UnionPay instead of the card page
|
|
2305
|
+
* (robin, 2026-10-15). The card page is closed first - paid on it in the
|
|
2306
|
+
* meantime, it goes on as that payment. The stay is prebooked again as
|
|
2307
|
+
* CCPN, the way it will be booked, and UnionPay's page opened for the total
|
|
2308
|
+
* the guest was shown. No card bound yet: UnionPay's page to bind one.
|
|
2309
|
+
*/
|
|
2310
|
+
const unionPay = async (code, email, who) => {
|
|
2311
|
+
if (!unionpay || !checkout)
|
|
2312
|
+
return undefined;
|
|
2313
|
+
// ⚠️ THE CODE AND THE EMAIL IT WAS MADE WITH, always - never the code
|
|
2314
|
+
// alone (香格里拉, 2026-10-15): the pair a front desk asks for.
|
|
2315
|
+
const asked = email.trim().toLowerCase();
|
|
2316
|
+
const found = findOwn(code, { ...who, email: asked });
|
|
2317
|
+
if (!found || found.guest.email.trim().toLowerCase() !== asked)
|
|
2318
|
+
return undefined;
|
|
2319
|
+
const refused = (why) => ({ kind: 'refused', why });
|
|
2320
|
+
const notWaiting = (b) => !b.prepaid
|
|
2321
|
+
? `${b.confirmationCode} is paid at the hotel - there is nothing to pay now.`
|
|
2322
|
+
: b.status !== 'awaiting-payment'
|
|
2323
|
+
? `${b.confirmationCode} is ${b.status} - it is not waiting for a payment.`
|
|
2324
|
+
: undefined;
|
|
2325
|
+
const first = store.get(found.id) ?? found;
|
|
2326
|
+
const no = notWaiting(first);
|
|
2327
|
+
if (no)
|
|
2328
|
+
return refused(no);
|
|
2329
|
+
if (first.prepaid?.method === 'unionpay' && first.prepaid.url)
|
|
2330
|
+
return { kind: 'confirm', url: first.prepaid.url, booking: publicBooking(first) };
|
|
2331
|
+
let cards;
|
|
2332
|
+
try {
|
|
2333
|
+
cards = await unionpay.cardsOf(first.guest.email);
|
|
2334
|
+
}
|
|
2335
|
+
catch (err) {
|
|
2336
|
+
return refused(`UnionPay could not be reached (${err.message}). Nothing changed - the card page still works, or ask again.`);
|
|
2337
|
+
}
|
|
2338
|
+
const card = cards[0];
|
|
2339
|
+
// No card yet: UnionPay's page to bind one - and nothing else changes.
|
|
2340
|
+
if (!card) {
|
|
2341
|
+
try {
|
|
2342
|
+
return { kind: 'enroll', url: (await unionpay.enroll(first.guest.email)).url };
|
|
2343
|
+
}
|
|
2344
|
+
catch (err) {
|
|
2345
|
+
return refused(`UnionPay's page for adding a card could not be opened (${err.message}). Nothing changed - ask again.`);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return inTurn(async () => {
|
|
2349
|
+
const row = store.get(first.id) ?? first;
|
|
2350
|
+
const again = notWaiting(row);
|
|
2351
|
+
if (again)
|
|
2352
|
+
return refused(again);
|
|
2353
|
+
const p = row.prepaid;
|
|
2354
|
+
if (p.method === 'unionpay' && p.url)
|
|
2355
|
+
return { kind: 'confirm', url: p.url, booking: publicBooking(row) };
|
|
2356
|
+
// The card page first: closed, it cannot be paid after this.
|
|
2357
|
+
if (p.method !== 'unionpay' && p.sessionId) {
|
|
2358
|
+
let state;
|
|
2359
|
+
try {
|
|
2360
|
+
state = await checkout.close(p.sessionId);
|
|
2361
|
+
}
|
|
2362
|
+
catch (err) {
|
|
2363
|
+
return refused(`The card page could not be closed (${err.message}). Nothing changed - ask again.`);
|
|
2364
|
+
}
|
|
2365
|
+
if (state === 'paid') {
|
|
2366
|
+
await settlePrepaid(row);
|
|
2367
|
+
return { kind: 'paid-on-card', booking: publicBooking(store.get(row.id) ?? row) };
|
|
2368
|
+
}
|
|
2369
|
+
store.update(row.id, { prepaid: { ...p, sessionId: undefined, url: undefined } });
|
|
2370
|
+
}
|
|
2371
|
+
const title = row.quote.lines[0]?.title ?? row.hotelName;
|
|
2372
|
+
// Prebooked again the way it will be booked: Huazhu's token holds only
|
|
2373
|
+
// for the same request (wire.ts).
|
|
2374
|
+
let token;
|
|
2375
|
+
try {
|
|
2376
|
+
token = (await client.prebook({
|
|
2377
|
+
hotelId: row.hotelId,
|
|
2378
|
+
checkinType: row.hourly ? 'HOUR' : 'DAY',
|
|
2379
|
+
paymentType: 'CCPN',
|
|
2380
|
+
reservations: [
|
|
2381
|
+
{
|
|
2382
|
+
roomTypeCode: row.roomTypeCode,
|
|
2383
|
+
rateCode: row.rateCode,
|
|
2384
|
+
...stayDatesOf(row),
|
|
2385
|
+
amountAfterTax: p.after,
|
|
2386
|
+
...(p.before ? { amountBeforeTax: p.before } : {}),
|
|
2387
|
+
currencyCode: p.currency,
|
|
2388
|
+
roomCount: row.rooms,
|
|
2389
|
+
adultCount: row.stay.guests,
|
|
2390
|
+
},
|
|
2391
|
+
],
|
|
2392
|
+
}))?.preBookToken;
|
|
2393
|
+
}
|
|
2394
|
+
catch (err) {
|
|
2395
|
+
if (err instanceof HuazhuRefusal) {
|
|
2396
|
+
store.update(row.id, {
|
|
2397
|
+
status: 'expired',
|
|
2398
|
+
prepaid: { ...p, sessionId: undefined, url: undefined, lapsedAt: now() },
|
|
2399
|
+
});
|
|
2400
|
+
return refused(`Nothing was booked or charged: ${refusalWords(err, `${row.hotelName} - ${title}`, p.currency)}`);
|
|
2401
|
+
}
|
|
2402
|
+
return refused(`Huazhu could not be reached to check the stay (${err.message}). Nothing was charged - ask again.`);
|
|
2403
|
+
}
|
|
2404
|
+
const names = hotelNamesOf(row.hotelId);
|
|
2405
|
+
const nights = row.quote.lines[0]?.nights ?? 1;
|
|
2406
|
+
let opened;
|
|
2407
|
+
try {
|
|
2408
|
+
opened = await unionpay.open({
|
|
2409
|
+
ref: row.distributorResId,
|
|
2410
|
+
code: row.confirmationCode,
|
|
2411
|
+
title: `${row.hotelName} · ${title}`.slice(0, 200),
|
|
2412
|
+
// UnionPay's page reads ASCII only: the hotel's English name.
|
|
2413
|
+
label: `${names?.en ?? 'Huazhu hotel'} - ${nights} night${nights === 1 ? '' : 's'} from ${row.stay.checkIn}`,
|
|
2414
|
+
amountMinor: row.quote.total.minor,
|
|
2415
|
+
currency: row.quote.total.currency,
|
|
2416
|
+
email: row.guest.email,
|
|
2417
|
+
name: row.guest.name,
|
|
2418
|
+
cardId: card.id,
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
catch (err) {
|
|
2422
|
+
return refused(`UnionPay's page could not be opened (${err.message}). Nothing was charged - ask again.`);
|
|
2423
|
+
}
|
|
2424
|
+
const updated = store.update(row.id, {
|
|
2425
|
+
paymentType: 'CCPN',
|
|
2426
|
+
prepaid: {
|
|
2427
|
+
...p,
|
|
2428
|
+
method: 'unionpay',
|
|
2429
|
+
sessionId: opened.sessionId,
|
|
2430
|
+
url: opened.url,
|
|
2431
|
+
...(token ? { preBookToken: token } : {}),
|
|
2432
|
+
payBy: now() + payWithin,
|
|
2433
|
+
},
|
|
2434
|
+
}) ?? row;
|
|
2435
|
+
return { kind: 'confirm', url: opened.url, booking: publicBooking(updated) };
|
|
2436
|
+
});
|
|
2437
|
+
};
|
|
2160
2438
|
/** When each payment page last moved its booking on, for the page a paid guest lands on. */
|
|
2161
2439
|
const returned = new Map();
|
|
2162
2440
|
/** What the landing page says of a booking - never the guest's name, email or phone. */
|
|
@@ -2232,6 +2510,7 @@ export const createHuazhuHotels = (opts) => {
|
|
|
2232
2510
|
...HUAZHU_CAPABILITIES,
|
|
2233
2511
|
...(opts.hourly ? { hourly: true } : {}),
|
|
2234
2512
|
...(checkout ? { prepaid: true } : {}),
|
|
2513
|
+
...(checkout && unionpay ? { unionpay: true } : {}),
|
|
2235
2514
|
},
|
|
2236
2515
|
/**
|
|
2237
2516
|
* One pass over the bookings paid in advance - every 10 seconds while the
|
|
@@ -2239,6 +2518,7 @@ export const createHuazhuHotels = (opts) => {
|
|
|
2239
2518
|
* money going back.
|
|
2240
2519
|
*/
|
|
2241
2520
|
...(checkout ? { paymentReturn } : {}),
|
|
2521
|
+
...(checkout && unionpay ? { unionPay } : {}),
|
|
2242
2522
|
async prepaidTick() {
|
|
2243
2523
|
// ⚠️ ONE PASS AT A TIME: a pass slower than the ten seconds between
|
|
2244
2524
|
// them would otherwise book a paid stay, or give money back, twice.
|
|
@@ -2845,12 +3125,15 @@ export const createHuazhuHotels = (opts) => {
|
|
|
2845
3125
|
.all()
|
|
2846
3126
|
.filter((b) => b.owners.includes(who.sessionId) &&
|
|
2847
3127
|
// A refused booking is nothing to show - unless the guest paid for
|
|
2848
|
-
// it: their money going back is.
|
|
3128
|
+
// it: their money going back is. Or confirmed it on UnionPay: that
|
|
3129
|
+
// the hotel turned it down, and charged nothing, is.
|
|
2849
3130
|
!(b.status === 'failed' &&
|
|
2850
3131
|
!b.resId &&
|
|
2851
3132
|
b.pendingSince === undefined &&
|
|
2852
3133
|
!b.attention &&
|
|
2853
|
-
!b.prepaid?.paidAt
|
|
3134
|
+
!b.prepaid?.paidAt &&
|
|
3135
|
+
!b.prepaid?.issuedAt &&
|
|
3136
|
+
!b.prepaid?.declined))
|
|
2854
3137
|
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
2855
3138
|
.slice(0, most);
|
|
2856
3139
|
const fresh = await pool(mine, 3, (b) => refresh(b));
|
|
@@ -2961,6 +3244,22 @@ export const createHuazhuHotels = (opts) => {
|
|
|
2961
3244
|
if (!found)
|
|
2962
3245
|
return null;
|
|
2963
3246
|
const b = await refresh(found);
|
|
3247
|
+
// Waiting on UnionPay: a confirmation already made is being booked by
|
|
3248
|
+
// the pass - which alone takes the card, once; one not made never will be.
|
|
3249
|
+
if (b.status === 'awaiting-payment' && b.prepaid?.method === 'unionpay') {
|
|
3250
|
+
const state = b.prepaid.sessionId && unionpay
|
|
3251
|
+
? await unionpay.state(b.prepaid.sessionId).catch(() => undefined)
|
|
3252
|
+
: 'closed';
|
|
3253
|
+
if (state !== 'issued' && state !== undefined) {
|
|
3254
|
+
return publicBooking(store.update(b.id, {
|
|
3255
|
+
status: 'expired',
|
|
3256
|
+
prepaid: { ...b.prepaid, lapsedAt: now(), url: undefined },
|
|
3257
|
+
}) ?? b);
|
|
3258
|
+
}
|
|
3259
|
+
throw new Error(state === 'issued'
|
|
3260
|
+
? 'The guest has just confirmed on UnionPay, so the stay is being booked with Huazhu now. Ask again in a moment to cancel it.'
|
|
3261
|
+
: 'UnionPay could not be reached to say whether the guest confirmed. Ask again in a moment - nothing is booked until it can.');
|
|
3262
|
+
}
|
|
2964
3263
|
// Not paid yet: the page is closed, so it cannot be paid after this.
|
|
2965
3264
|
if (b.status === 'awaiting-payment' && b.prepaid?.sessionId && checkout) {
|
|
2966
3265
|
const state = await checkout.close(b.prepaid.sessionId).catch(() => undefined);
|