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.
@@ -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 card on file is required for EVERY hotel call, search included. That is
1819
- // deliberate: a hotel search opens a real session at the supplier and booking
1820
- // blocks a real rate, so a caller is never allowed to reach the point of
1821
- // commitment only to discover it cannot pay. The same card that authorises
1822
- // flight booking authorises hotels — there is no separate hotel enrolment.
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
- // Only free-cancellation, pay-later rates are sold. Those are the rates where
1825
- // the guest's balance can safely be settled with the supplier after booking,
1826
- // which is what makes 5%-now/rest-later work. The result set is smaller than
1827
- // a metasearch's, and every row in it can actually be booked.
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
- * Each offer carries `price` (what the guest pays), `reservation_fee_now`
1849
- * (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
1850
- * `free_cancellation_until`. There is no wholesale figure to quote by mistake.
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
- * Keep `session_id` and the chosen offer's `combination_id_v2`: together they
1853
- * identify the exact rate, and booking needs both.
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
- * A booking takes minutes: the rate is re-blocked at the supplier, every
1875
- * price and date rail is checked, the 5% reservation fee is charged to your
1876
- * card, and only then is the room committed. No proxy holds a connection that
1877
- * long, so this returns at once and you poll hotelBooking() for the outcome.
1878
- * Use bookHotelAndWait() if you would rather block.
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
- * Because the fee is taken BEFORE the commit, a declined card costs nothing
1881
- * to unwind: no reservation exists and nothing is charged.
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` and `expectedBalance` back exactly as search returned
1884
- * them — the booking is refused if the supplier has moved beyond tolerance,
1885
- * so a guest is never charged a price they did not agree to.
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 the same rate while a job is running: that books
1888
- * the room twice and charges two reservation fees.
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
- expected_balance: params.expectedBalance,
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' or 'failed'. On success you get
1917
- * `confirmation`, `reservation_fee_charged`, `pay_link`, `balance_due`,
1918
- * `balance_due_by` and `terms` (including the full cancellation ladder).
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
- * Giving up after `maxWaitMs` does NOT cancel anything — the booking may
1931
- * still complete. The returned object carries `booking_job_id` so you can
1932
- * keep polling, and the confirmation is emailed to the guest regardless.
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 ?? 6e5;
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
- const st = result.status;
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
- * Free until `balance_due_by`; after that the hotel's own cancellation ladder
1956
- * applies and can reach 100%. The ladder ships in the booking's `terms`, so
1957
- * you can see the cost before calling this. The 5% reservation fee is NOT
1958
- * refunded.
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 card on file is required for EVERY hotel call, search included. That is
575
- // deliberate: a hotel search opens a real session at the supplier and booking
576
- // blocks a real rate, so a caller is never allowed to reach the point of
577
- // commitment only to discover it cannot pay. The same card that authorises
578
- // flight booking authorises hotels — there is no separate hotel enrolment.
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
- // Only free-cancellation, pay-later rates are sold. Those are the rates where
581
- // the guest's balance can safely be settled with the supplier after booking,
582
- // which is what makes 5%-now/rest-later work. The result set is smaller than
583
- // a metasearch's, and every row in it can actually be booked.
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
- * Each offer carries `price` (what the guest pays), `reservation_fee_now`
605
- * (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
606
- * `free_cancellation_until`. There is no wholesale figure to quote by mistake.
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
- * Keep `session_id` and the chosen offer's `combination_id_v2`: together they
609
- * identify the exact rate, and booking needs both.
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
- * A booking takes minutes: the rate is re-blocked at the supplier, every
631
- * price and date rail is checked, the 5% reservation fee is charged to your
632
- * card, and only then is the room committed. No proxy holds a connection that
633
- * long, so this returns at once and you poll hotelBooking() for the outcome.
634
- * Use bookHotelAndWait() if you would rather block.
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
- * Because the fee is taken BEFORE the commit, a declined card costs nothing
637
- * to unwind: no reservation exists and nothing is charged.
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` and `expectedBalance` back exactly as search returned
640
- * them — the booking is refused if the supplier has moved beyond tolerance,
641
- * so a guest is never charged a price they did not agree to.
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 the same rate while a job is running: that books
644
- * the room twice and charges two reservation fees.
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
- expected_balance: params.expectedBalance,
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' or 'failed'. On success you get
673
- * `confirmation`, `reservation_fee_charged`, `pay_link`, `balance_due`,
674
- * `balance_due_by` and `terms` (including the full cancellation ladder).
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
- * Giving up after `maxWaitMs` does NOT cancel anything — the booking may
687
- * still complete. The returned object carries `booking_job_id` so you can
688
- * keep polling, and the confirmation is emailed to the guest regardless.
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 ?? 6e5;
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
- const st = result.status;
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
- * Free until `balance_due_by`; after that the hotel's own cancellation ladder
712
- * applies and can reach 100%. The ladder ships in the booking's `terms`, so
713
- * you can see the cost before calling this. The 5% reservation fee is NOT
714
- * refunded.
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.
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LetsFG,
4
4
  LetsFGError,
5
5
  offerSummary
6
- } from "./chunk-F5BBI6XX.mjs";
6
+ } from "./chunk-GVKYPTY3.mjs";
7
7
 
8
8
  // src/auth.ts
9
9
  import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from "fs";