letsfg 2026.5.73 → 2026.5.74
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/README.md +321 -283
- package/dist/{chunk-F5BBI6XX.mjs → chunk-GVKYPTY3.mjs} +80 -42
- package/dist/cli.js +79 -42
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +80 -32
- package/dist/index.d.ts +80 -32
- package/dist/index.js +81 -42
- package/dist/index.mjs +3 -1
- package/package.json +56 -56
|
@@ -1564,6 +1564,7 @@ var LATE_MERGE_POLL_MS = 3e3;
|
|
|
1564
1564
|
var LATE_MERGE_GRACE_MS = 9e4;
|
|
1565
1565
|
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
1566
1566
|
var NON_TERMINAL = ["pending", "searching"];
|
|
1567
|
+
var HOTEL_BOOKING_FINAL_STATUSES = ["succeeded", "failed", "attention"];
|
|
1567
1568
|
var LetsFG = class {
|
|
1568
1569
|
bearerToken;
|
|
1569
1570
|
apiKey;
|
|
@@ -1815,16 +1816,18 @@ var LetsFG = class {
|
|
|
1815
1816
|
}
|
|
1816
1817
|
// ── Hotels ──────────────────────────────────────────────────────────
|
|
1817
1818
|
//
|
|
1818
|
-
// A
|
|
1819
|
-
// deliberate: a hotel search opens a real session at the
|
|
1820
|
-
// blocks a real rate, so a caller is never allowed to
|
|
1821
|
-
// commitment only to discover it cannot pay. The same
|
|
1822
|
-
// flight booking authorises hotels
|
|
1819
|
+
// A connected payment method is required for EVERY hotel call, search
|
|
1820
|
+
// included. That is deliberate: a hotel search opens a real session at the
|
|
1821
|
+
// supplier and booking blocks a real rate, so a caller is never allowed to
|
|
1822
|
+
// reach the point of commitment only to discover it cannot pay. The same
|
|
1823
|
+
// method that authorises flight booking authorises hotels.
|
|
1823
1824
|
//
|
|
1824
|
-
//
|
|
1825
|
-
//
|
|
1826
|
-
//
|
|
1827
|
-
//
|
|
1825
|
+
// How a hotel is paid (since 2026-09-11): the full `price` is HELD on the
|
|
1826
|
+
// connected Revolut method, LetsFG books and pays the supplier itself, and the
|
|
1827
|
+
// hold is captured only once the supplier has confirmed. A booking that fails
|
|
1828
|
+
// releases the hold. There is no reservation fee, no deposit and no pay link —
|
|
1829
|
+
// those belonged to the process retired on 2026-09-11. Every rate type is
|
|
1830
|
+
// sold, refundable and non-refundable.
|
|
1828
1831
|
/**
|
|
1829
1832
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
1830
1833
|
*
|
|
@@ -1845,12 +1848,17 @@ var LetsFG = class {
|
|
|
1845
1848
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
1846
1849
|
* — so this gets its own generous timeout rather than the client default.
|
|
1847
1850
|
*
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1850
|
-
* `
|
|
1851
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
1852
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
1853
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
1854
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
1855
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
1856
|
+
* `cancellation_policy` and its own `session_id`.
|
|
1851
1857
|
*
|
|
1852
|
-
*
|
|
1853
|
-
*
|
|
1858
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
1859
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
1860
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
1861
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
1854
1862
|
*/
|
|
1855
1863
|
async searchHotels(params) {
|
|
1856
1864
|
this.requireApiKey();
|
|
@@ -1863,7 +1871,8 @@ var LetsFG = class {
|
|
|
1863
1871
|
children: params.children ?? 0,
|
|
1864
1872
|
nationality: params.nationality ?? "PL",
|
|
1865
1873
|
limit: params.limit ?? 40,
|
|
1866
|
-
with_images: params.withImages ?? true
|
|
1874
|
+
with_images: params.withImages ?? true,
|
|
1875
|
+
currency: params.currency ?? "USD"
|
|
1867
1876
|
};
|
|
1868
1877
|
if (params.childAges?.length) body.child_ages = params.childAges;
|
|
1869
1878
|
return this.post("/developers/api/v1/hotels/search", body, 24e4);
|
|
@@ -1871,30 +1880,42 @@ var LetsFG = class {
|
|
|
1871
1880
|
/**
|
|
1872
1881
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
1873
1882
|
*
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1883
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
1884
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
1885
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
1886
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
1887
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
1888
|
+
* reservation fee, no deposit and no pay link.
|
|
1879
1889
|
*
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1890
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
1891
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
1892
|
+
* bookHotelAndWait() if you would rather block.
|
|
1882
1893
|
*
|
|
1883
|
-
* Send `expectedPrice`
|
|
1884
|
-
* them
|
|
1885
|
-
*
|
|
1894
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
1895
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
1896
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
1897
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
1898
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
1899
|
+
* returns 400 invalid_details naming the fields.
|
|
1886
1900
|
*
|
|
1887
|
-
* Do NOT call this again for
|
|
1888
|
-
* the
|
|
1901
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
1902
|
+
* A retry of the same booking returns the job already under way
|
|
1903
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
1889
1904
|
*/
|
|
1890
1905
|
async bookHotel(params) {
|
|
1891
1906
|
this.requireApiKey();
|
|
1907
|
+
if (typeof params.expectedCost !== "number") {
|
|
1908
|
+
throw new LetsFGError(
|
|
1909
|
+
"bookHotel() needs expectedCost: copy the offer's expected_cost, currency and fx_rate. " + ("expectedBalance" in params ? "expectedBalance belonged to the reservation-fee process retired on 2026-09-11 and is not sent. " : "") + "See https://letsfg.co/developers/api/docs",
|
|
1910
|
+
400
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1892
1913
|
const body = {
|
|
1893
1914
|
session_id: params.sessionId,
|
|
1894
1915
|
hotel_code: params.hotelCode,
|
|
1895
1916
|
combination_id_v2: params.combinationIdV2,
|
|
1896
1917
|
expected_price: params.expectedPrice,
|
|
1897
|
-
|
|
1918
|
+
expected_cost: params.expectedCost,
|
|
1898
1919
|
city_id: params.cityId,
|
|
1899
1920
|
city_name: params.cityName,
|
|
1900
1921
|
check_in: params.checkIn,
|
|
@@ -1906,16 +1927,30 @@ var LetsFG = class {
|
|
|
1906
1927
|
phone_country_code: params.phoneCountryCode ?? "48",
|
|
1907
1928
|
special_requests: params.specialRequests ?? []
|
|
1908
1929
|
};
|
|
1930
|
+
if (params.currency) body.currency = params.currency;
|
|
1931
|
+
if (params.fxRate != null) body.fx_rate = params.fxRate;
|
|
1909
1932
|
if (params.combinationId != null) body.combination_id = params.combinationId;
|
|
1910
1933
|
if (params.hotelName) body.hotel_name = params.hotelName;
|
|
1934
|
+
if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
|
|
1911
1935
|
return this.post("/developers/api/v1/hotels/book", body, 9e4);
|
|
1912
1936
|
}
|
|
1913
1937
|
/**
|
|
1914
1938
|
* Collect the result of a booking started with bookHotel().
|
|
1915
1939
|
*
|
|
1916
|
-
* `status` is 'in_progress', 'succeeded'
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
1940
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
1941
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
1942
|
+
*
|
|
1943
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
1944
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
1945
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
1946
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
1947
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
1948
|
+
* nothing was charged.
|
|
1949
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
1950
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
1951
|
+
* person checks with the supplier. Do not book again.
|
|
1952
|
+
*
|
|
1953
|
+
* The guest is e-mailed in every case.
|
|
1919
1954
|
*/
|
|
1920
1955
|
async hotelBooking(bookingJobId) {
|
|
1921
1956
|
this.requireApiKey();
|
|
@@ -1927,13 +1962,15 @@ var LetsFG = class {
|
|
|
1927
1962
|
/**
|
|
1928
1963
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
1929
1964
|
*
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1965
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
1966
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
1967
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
1968
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
1969
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
1933
1970
|
*/
|
|
1934
1971
|
async bookHotelAndWait(params) {
|
|
1935
1972
|
const pollIntervalMs = params.pollIntervalMs ?? 2e4;
|
|
1936
|
-
const maxWaitMs = params.maxWaitMs ??
|
|
1973
|
+
const maxWaitMs = params.maxWaitMs ?? 18e5;
|
|
1937
1974
|
const job = await this.bookHotel(params);
|
|
1938
1975
|
const jobId = job.booking_job_id;
|
|
1939
1976
|
if (!jobId) return job;
|
|
@@ -1943,19 +1980,19 @@ var LetsFG = class {
|
|
|
1943
1980
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1944
1981
|
waited += pollIntervalMs;
|
|
1945
1982
|
result = await this.hotelBooking(jobId);
|
|
1946
|
-
|
|
1947
|
-
if (st === "succeeded" || st === "failed") return result;
|
|
1983
|
+
if (HOTEL_BOOKING_FINAL_STATUSES.includes(result.status)) return result;
|
|
1948
1984
|
}
|
|
1949
1985
|
if (result.booking_job_id == null) result.booking_job_id = jobId;
|
|
1950
1986
|
return result;
|
|
1951
1987
|
}
|
|
1952
1988
|
/**
|
|
1953
|
-
* Release a reservation at the supplier.
|
|
1989
|
+
* Release a reservation at the supplier and refund the guest.
|
|
1954
1990
|
*
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1991
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
1992
|
+
* zero-charge cancellation — a refundable rate before its
|
|
1993
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
1994
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
1995
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
1959
1996
|
*
|
|
1960
1997
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
1961
1998
|
* out, do not assume it failed — re-check before retrying.
|
|
@@ -2108,6 +2145,7 @@ export {
|
|
|
2108
2145
|
ValidationError,
|
|
2109
2146
|
offerSummary,
|
|
2110
2147
|
cheapestOffer,
|
|
2148
|
+
HOTEL_BOOKING_FINAL_STATUSES,
|
|
2111
2149
|
LetsFG,
|
|
2112
2150
|
index_default,
|
|
2113
2151
|
BoostedTravel,
|
package/dist/cli.js
CHANGED
|
@@ -320,6 +320,7 @@ var LATE_MERGE_POLL_MS = 3e3;
|
|
|
320
320
|
var LATE_MERGE_GRACE_MS = 9e4;
|
|
321
321
|
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
322
322
|
var NON_TERMINAL = ["pending", "searching"];
|
|
323
|
+
var HOTEL_BOOKING_FINAL_STATUSES = ["succeeded", "failed", "attention"];
|
|
323
324
|
var LetsFG = class {
|
|
324
325
|
bearerToken;
|
|
325
326
|
apiKey;
|
|
@@ -571,16 +572,18 @@ var LetsFG = class {
|
|
|
571
572
|
}
|
|
572
573
|
// ── Hotels ──────────────────────────────────────────────────────────
|
|
573
574
|
//
|
|
574
|
-
// A
|
|
575
|
-
// deliberate: a hotel search opens a real session at the
|
|
576
|
-
// blocks a real rate, so a caller is never allowed to
|
|
577
|
-
// commitment only to discover it cannot pay. The same
|
|
578
|
-
// flight booking authorises hotels
|
|
575
|
+
// A connected payment method is required for EVERY hotel call, search
|
|
576
|
+
// included. That is deliberate: a hotel search opens a real session at the
|
|
577
|
+
// supplier and booking blocks a real rate, so a caller is never allowed to
|
|
578
|
+
// reach the point of commitment only to discover it cannot pay. The same
|
|
579
|
+
// method that authorises flight booking authorises hotels.
|
|
579
580
|
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
//
|
|
581
|
+
// How a hotel is paid (since 2026-09-11): the full `price` is HELD on the
|
|
582
|
+
// connected Revolut method, LetsFG books and pays the supplier itself, and the
|
|
583
|
+
// hold is captured only once the supplier has confirmed. A booking that fails
|
|
584
|
+
// releases the hold. There is no reservation fee, no deposit and no pay link —
|
|
585
|
+
// those belonged to the process retired on 2026-09-11. Every rate type is
|
|
586
|
+
// sold, refundable and non-refundable.
|
|
584
587
|
/**
|
|
585
588
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
586
589
|
*
|
|
@@ -601,12 +604,17 @@ var LetsFG = class {
|
|
|
601
604
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
602
605
|
* — so this gets its own generous timeout rather than the client default.
|
|
603
606
|
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* `
|
|
607
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
608
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
609
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
610
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
611
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
612
|
+
* `cancellation_policy` and its own `session_id`.
|
|
607
613
|
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
614
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
615
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
616
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
617
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
610
618
|
*/
|
|
611
619
|
async searchHotels(params) {
|
|
612
620
|
this.requireApiKey();
|
|
@@ -619,7 +627,8 @@ var LetsFG = class {
|
|
|
619
627
|
children: params.children ?? 0,
|
|
620
628
|
nationality: params.nationality ?? "PL",
|
|
621
629
|
limit: params.limit ?? 40,
|
|
622
|
-
with_images: params.withImages ?? true
|
|
630
|
+
with_images: params.withImages ?? true,
|
|
631
|
+
currency: params.currency ?? "USD"
|
|
623
632
|
};
|
|
624
633
|
if (params.childAges?.length) body.child_ages = params.childAges;
|
|
625
634
|
return this.post("/developers/api/v1/hotels/search", body, 24e4);
|
|
@@ -627,30 +636,42 @@ var LetsFG = class {
|
|
|
627
636
|
/**
|
|
628
637
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
629
638
|
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
639
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
640
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
641
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
642
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
643
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
644
|
+
* reservation fee, no deposit and no pay link.
|
|
635
645
|
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
646
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
647
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
648
|
+
* bookHotelAndWait() if you would rather block.
|
|
638
649
|
*
|
|
639
|
-
* Send `expectedPrice`
|
|
640
|
-
* them
|
|
641
|
-
*
|
|
650
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
651
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
652
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
653
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
654
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
655
|
+
* returns 400 invalid_details naming the fields.
|
|
642
656
|
*
|
|
643
|
-
* Do NOT call this again for
|
|
644
|
-
* the
|
|
657
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
658
|
+
* A retry of the same booking returns the job already under way
|
|
659
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
645
660
|
*/
|
|
646
661
|
async bookHotel(params) {
|
|
647
662
|
this.requireApiKey();
|
|
663
|
+
if (typeof params.expectedCost !== "number") {
|
|
664
|
+
throw new LetsFGError(
|
|
665
|
+
"bookHotel() needs expectedCost: copy the offer's expected_cost, currency and fx_rate. " + ("expectedBalance" in params ? "expectedBalance belonged to the reservation-fee process retired on 2026-09-11 and is not sent. " : "") + "See https://letsfg.co/developers/api/docs",
|
|
666
|
+
400
|
|
667
|
+
);
|
|
668
|
+
}
|
|
648
669
|
const body = {
|
|
649
670
|
session_id: params.sessionId,
|
|
650
671
|
hotel_code: params.hotelCode,
|
|
651
672
|
combination_id_v2: params.combinationIdV2,
|
|
652
673
|
expected_price: params.expectedPrice,
|
|
653
|
-
|
|
674
|
+
expected_cost: params.expectedCost,
|
|
654
675
|
city_id: params.cityId,
|
|
655
676
|
city_name: params.cityName,
|
|
656
677
|
check_in: params.checkIn,
|
|
@@ -662,16 +683,30 @@ var LetsFG = class {
|
|
|
662
683
|
phone_country_code: params.phoneCountryCode ?? "48",
|
|
663
684
|
special_requests: params.specialRequests ?? []
|
|
664
685
|
};
|
|
686
|
+
if (params.currency) body.currency = params.currency;
|
|
687
|
+
if (params.fxRate != null) body.fx_rate = params.fxRate;
|
|
665
688
|
if (params.combinationId != null) body.combination_id = params.combinationId;
|
|
666
689
|
if (params.hotelName) body.hotel_name = params.hotelName;
|
|
690
|
+
if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
|
|
667
691
|
return this.post("/developers/api/v1/hotels/book", body, 9e4);
|
|
668
692
|
}
|
|
669
693
|
/**
|
|
670
694
|
* Collect the result of a booking started with bookHotel().
|
|
671
695
|
*
|
|
672
|
-
* `status` is 'in_progress', 'succeeded'
|
|
673
|
-
*
|
|
674
|
-
*
|
|
696
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
697
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
698
|
+
*
|
|
699
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
700
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
701
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
702
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
703
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
704
|
+
* nothing was charged.
|
|
705
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
706
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
707
|
+
* person checks with the supplier. Do not book again.
|
|
708
|
+
*
|
|
709
|
+
* The guest is e-mailed in every case.
|
|
675
710
|
*/
|
|
676
711
|
async hotelBooking(bookingJobId) {
|
|
677
712
|
this.requireApiKey();
|
|
@@ -683,13 +718,15 @@ var LetsFG = class {
|
|
|
683
718
|
/**
|
|
684
719
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
685
720
|
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
721
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
722
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
723
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
724
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
725
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
689
726
|
*/
|
|
690
727
|
async bookHotelAndWait(params) {
|
|
691
728
|
const pollIntervalMs = params.pollIntervalMs ?? 2e4;
|
|
692
|
-
const maxWaitMs = params.maxWaitMs ??
|
|
729
|
+
const maxWaitMs = params.maxWaitMs ?? 18e5;
|
|
693
730
|
const job = await this.bookHotel(params);
|
|
694
731
|
const jobId = job.booking_job_id;
|
|
695
732
|
if (!jobId) return job;
|
|
@@ -699,19 +736,19 @@ var LetsFG = class {
|
|
|
699
736
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
700
737
|
waited += pollIntervalMs;
|
|
701
738
|
result = await this.hotelBooking(jobId);
|
|
702
|
-
|
|
703
|
-
if (st === "succeeded" || st === "failed") return result;
|
|
739
|
+
if (HOTEL_BOOKING_FINAL_STATUSES.includes(result.status)) return result;
|
|
704
740
|
}
|
|
705
741
|
if (result.booking_job_id == null) result.booking_job_id = jobId;
|
|
706
742
|
return result;
|
|
707
743
|
}
|
|
708
744
|
/**
|
|
709
|
-
* Release a reservation at the supplier.
|
|
745
|
+
* Release a reservation at the supplier and refund the guest.
|
|
710
746
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
747
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
748
|
+
* zero-charge cancellation — a refundable rate before its
|
|
749
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
750
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
751
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
715
752
|
*
|
|
716
753
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
717
754
|
* out, do not assume it failed — re-check before retrying.
|