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
package/dist/index.d.mts
CHANGED
|
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
|
|
|
283
283
|
* const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
284
284
|
*
|
|
285
285
|
* // Developer API (look-to-book search: 200 free after every booking)
|
|
286
|
-
* const bt2 = new LetsFG({ apiKey:
|
|
286
|
+
* const bt2 = new LetsFG({ apiKey: process.env.LETSFG_API_KEY });
|
|
287
287
|
* const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
|
|
288
288
|
* ```
|
|
289
289
|
*/
|
|
@@ -458,6 +458,12 @@ declare class ValidationError extends LetsFGError {
|
|
|
458
458
|
declare function offerSummary(offer: FlightOffer): string;
|
|
459
459
|
/** Get cheapest offer from search results */
|
|
460
460
|
declare function cheapestOffer(result: FlightSearchResult): FlightOffer | null;
|
|
461
|
+
/**
|
|
462
|
+
* Every hotel booking job status after which polling is pointless. `attention` is
|
|
463
|
+
* final for the caller too: a person settles it, and booking again would book twice.
|
|
464
|
+
*/
|
|
465
|
+
declare const HOTEL_BOOKING_FINAL_STATUSES: readonly ["succeeded", "failed", "attention"];
|
|
466
|
+
type HotelBookingStatus = 'in_progress' | (typeof HOTEL_BOOKING_FINAL_STATUSES)[number];
|
|
461
467
|
declare class LetsFG {
|
|
462
468
|
private bearerToken;
|
|
463
469
|
private apiKey;
|
|
@@ -587,12 +593,17 @@ declare class LetsFG {
|
|
|
587
593
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
588
594
|
* — so this gets its own generous timeout rather than the client default.
|
|
589
595
|
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
* `
|
|
596
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
597
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
598
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
599
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
600
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
601
|
+
* `cancellation_policy` and its own `session_id`.
|
|
593
602
|
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
603
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
604
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
605
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
606
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
596
607
|
*/
|
|
597
608
|
searchHotels(params: {
|
|
598
609
|
cityId: number;
|
|
@@ -606,76 +617,113 @@ declare class LetsFG {
|
|
|
606
617
|
nationality?: string;
|
|
607
618
|
limit?: number;
|
|
608
619
|
withImages?: boolean;
|
|
620
|
+
/** ISO code every `price` is quoted in. Default USD; PLN is the supplier's own. */
|
|
621
|
+
currency?: string;
|
|
609
622
|
}): Promise<Record<string, unknown>>;
|
|
610
623
|
/**
|
|
611
624
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
612
625
|
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
626
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
627
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
628
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
629
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
630
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
631
|
+
* reservation fee, no deposit and no pay link.
|
|
618
632
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
633
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
634
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
635
|
+
* bookHotelAndWait() if you would rather block.
|
|
621
636
|
*
|
|
622
|
-
* Send `expectedPrice`
|
|
623
|
-
* them
|
|
624
|
-
*
|
|
637
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
638
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
639
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
640
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
641
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
642
|
+
* returns 400 invalid_details naming the fields.
|
|
625
643
|
*
|
|
626
|
-
* Do NOT call this again for
|
|
627
|
-
* the
|
|
644
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
645
|
+
* A retry of the same booking returns the job already under way
|
|
646
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
628
647
|
*/
|
|
629
648
|
bookHotel(params: {
|
|
630
649
|
sessionId: string;
|
|
631
650
|
hotelCode: number;
|
|
632
651
|
combinationIdV2: string;
|
|
652
|
+
/** The offer's `price`, verbatim. */
|
|
633
653
|
expectedPrice: number;
|
|
634
|
-
|
|
654
|
+
/** The offer's `expected_cost` (the supplier's cost, PLN), verbatim. */
|
|
655
|
+
expectedCost: number;
|
|
656
|
+
/** The offer's `currency`. Copy it — omitted means PLN. */
|
|
657
|
+
currency?: string;
|
|
658
|
+
/** The offer's `fx_rate` (null for a PLN offer). */
|
|
659
|
+
fxRate?: number | null;
|
|
635
660
|
cityId: number;
|
|
636
661
|
cityName: string;
|
|
637
662
|
checkIn: string;
|
|
638
663
|
checkOut: string;
|
|
664
|
+
/**
|
|
665
|
+
* ONE entry per guest in the room, children included: adults first, then children in the
|
|
666
|
+
* `childAges` order used to search (the party travels with the offer's session). The hotel
|
|
667
|
+
* requires a name for every guest; fewer names than guests is refused before anything is
|
|
668
|
+
* submitted, and the hold is released.
|
|
669
|
+
*/
|
|
639
670
|
guests: Array<{
|
|
640
671
|
title: string;
|
|
641
672
|
first_name: string;
|
|
642
673
|
last_name: string;
|
|
643
674
|
}>;
|
|
644
|
-
/** The
|
|
675
|
+
/** The guest's e-mail: the confirmation, or a note that it did not go through, goes here. */
|
|
645
676
|
email: string;
|
|
646
677
|
phone: string;
|
|
678
|
+
/** Adults in the room, as searched. */
|
|
647
679
|
adults?: number;
|
|
648
680
|
combinationId?: number;
|
|
649
681
|
hotelName?: string;
|
|
650
682
|
phoneCountryCode?: string;
|
|
651
683
|
specialRequests?: string[];
|
|
684
|
+
/** Optional. A retry with the same key returns the booking already under way. */
|
|
685
|
+
idempotencyKey?: string;
|
|
652
686
|
}): Promise<Record<string, unknown>>;
|
|
653
687
|
/**
|
|
654
688
|
* Collect the result of a booking started with bookHotel().
|
|
655
689
|
*
|
|
656
|
-
* `status` is 'in_progress', 'succeeded'
|
|
657
|
-
*
|
|
658
|
-
*
|
|
690
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
691
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
692
|
+
*
|
|
693
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
694
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
695
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
696
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
697
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
698
|
+
* nothing was charged.
|
|
699
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
700
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
701
|
+
* person checks with the supplier. Do not book again.
|
|
702
|
+
*
|
|
703
|
+
* The guest is e-mailed in every case.
|
|
659
704
|
*/
|
|
660
705
|
hotelBooking(bookingJobId: string): Promise<Record<string, unknown>>;
|
|
661
706
|
/**
|
|
662
707
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
663
708
|
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
709
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
710
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
711
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
712
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
713
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
667
714
|
*/
|
|
668
715
|
bookHotelAndWait(params: Parameters<LetsFG['bookHotel']>[0] & {
|
|
669
716
|
pollIntervalMs?: number;
|
|
670
717
|
maxWaitMs?: number;
|
|
671
718
|
}): Promise<Record<string, unknown>>;
|
|
672
719
|
/**
|
|
673
|
-
* Release a reservation at the supplier.
|
|
720
|
+
* Release a reservation at the supplier and refund the guest.
|
|
674
721
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
722
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
723
|
+
* zero-charge cancellation — a refundable rate before its
|
|
724
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
725
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
726
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
679
727
|
*
|
|
680
728
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
681
729
|
* out, do not assume it failed — re-check before retrying.
|
|
@@ -730,4 +778,4 @@ declare const BoostedTravel: typeof LetsFG;
|
|
|
730
778
|
declare const BoostedTravelError: typeof LetsFGError;
|
|
731
779
|
type BoostedTravelConfig = LetsFGConfig;
|
|
732
780
|
|
|
733
|
-
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
|
781
|
+
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, HOTEL_BOOKING_FINAL_STATUSES, type HotelBookingStatus, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
package/dist/index.d.ts
CHANGED
|
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
|
|
|
283
283
|
* const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
284
284
|
*
|
|
285
285
|
* // Developer API (look-to-book search: 200 free after every booking)
|
|
286
|
-
* const bt2 = new LetsFG({ apiKey:
|
|
286
|
+
* const bt2 = new LetsFG({ apiKey: process.env.LETSFG_API_KEY });
|
|
287
287
|
* const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
|
|
288
288
|
* ```
|
|
289
289
|
*/
|
|
@@ -458,6 +458,12 @@ declare class ValidationError extends LetsFGError {
|
|
|
458
458
|
declare function offerSummary(offer: FlightOffer): string;
|
|
459
459
|
/** Get cheapest offer from search results */
|
|
460
460
|
declare function cheapestOffer(result: FlightSearchResult): FlightOffer | null;
|
|
461
|
+
/**
|
|
462
|
+
* Every hotel booking job status after which polling is pointless. `attention` is
|
|
463
|
+
* final for the caller too: a person settles it, and booking again would book twice.
|
|
464
|
+
*/
|
|
465
|
+
declare const HOTEL_BOOKING_FINAL_STATUSES: readonly ["succeeded", "failed", "attention"];
|
|
466
|
+
type HotelBookingStatus = 'in_progress' | (typeof HOTEL_BOOKING_FINAL_STATUSES)[number];
|
|
461
467
|
declare class LetsFG {
|
|
462
468
|
private bearerToken;
|
|
463
469
|
private apiKey;
|
|
@@ -587,12 +593,17 @@ declare class LetsFG {
|
|
|
587
593
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
588
594
|
* — so this gets its own generous timeout rather than the client default.
|
|
589
595
|
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
* `
|
|
596
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
597
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
598
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
599
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
600
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
601
|
+
* `cancellation_policy` and its own `session_id`.
|
|
593
602
|
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
603
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
604
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
605
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
606
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
596
607
|
*/
|
|
597
608
|
searchHotels(params: {
|
|
598
609
|
cityId: number;
|
|
@@ -606,76 +617,113 @@ declare class LetsFG {
|
|
|
606
617
|
nationality?: string;
|
|
607
618
|
limit?: number;
|
|
608
619
|
withImages?: boolean;
|
|
620
|
+
/** ISO code every `price` is quoted in. Default USD; PLN is the supplier's own. */
|
|
621
|
+
currency?: string;
|
|
609
622
|
}): Promise<Record<string, unknown>>;
|
|
610
623
|
/**
|
|
611
624
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
612
625
|
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
626
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
627
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
628
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
629
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
630
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
631
|
+
* reservation fee, no deposit and no pay link.
|
|
618
632
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
633
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
634
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
635
|
+
* bookHotelAndWait() if you would rather block.
|
|
621
636
|
*
|
|
622
|
-
* Send `expectedPrice`
|
|
623
|
-
* them
|
|
624
|
-
*
|
|
637
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
638
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
639
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
640
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
641
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
642
|
+
* returns 400 invalid_details naming the fields.
|
|
625
643
|
*
|
|
626
|
-
* Do NOT call this again for
|
|
627
|
-
* the
|
|
644
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
645
|
+
* A retry of the same booking returns the job already under way
|
|
646
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
628
647
|
*/
|
|
629
648
|
bookHotel(params: {
|
|
630
649
|
sessionId: string;
|
|
631
650
|
hotelCode: number;
|
|
632
651
|
combinationIdV2: string;
|
|
652
|
+
/** The offer's `price`, verbatim. */
|
|
633
653
|
expectedPrice: number;
|
|
634
|
-
|
|
654
|
+
/** The offer's `expected_cost` (the supplier's cost, PLN), verbatim. */
|
|
655
|
+
expectedCost: number;
|
|
656
|
+
/** The offer's `currency`. Copy it — omitted means PLN. */
|
|
657
|
+
currency?: string;
|
|
658
|
+
/** The offer's `fx_rate` (null for a PLN offer). */
|
|
659
|
+
fxRate?: number | null;
|
|
635
660
|
cityId: number;
|
|
636
661
|
cityName: string;
|
|
637
662
|
checkIn: string;
|
|
638
663
|
checkOut: string;
|
|
664
|
+
/**
|
|
665
|
+
* ONE entry per guest in the room, children included: adults first, then children in the
|
|
666
|
+
* `childAges` order used to search (the party travels with the offer's session). The hotel
|
|
667
|
+
* requires a name for every guest; fewer names than guests is refused before anything is
|
|
668
|
+
* submitted, and the hold is released.
|
|
669
|
+
*/
|
|
639
670
|
guests: Array<{
|
|
640
671
|
title: string;
|
|
641
672
|
first_name: string;
|
|
642
673
|
last_name: string;
|
|
643
674
|
}>;
|
|
644
|
-
/** The
|
|
675
|
+
/** The guest's e-mail: the confirmation, or a note that it did not go through, goes here. */
|
|
645
676
|
email: string;
|
|
646
677
|
phone: string;
|
|
678
|
+
/** Adults in the room, as searched. */
|
|
647
679
|
adults?: number;
|
|
648
680
|
combinationId?: number;
|
|
649
681
|
hotelName?: string;
|
|
650
682
|
phoneCountryCode?: string;
|
|
651
683
|
specialRequests?: string[];
|
|
684
|
+
/** Optional. A retry with the same key returns the booking already under way. */
|
|
685
|
+
idempotencyKey?: string;
|
|
652
686
|
}): Promise<Record<string, unknown>>;
|
|
653
687
|
/**
|
|
654
688
|
* Collect the result of a booking started with bookHotel().
|
|
655
689
|
*
|
|
656
|
-
* `status` is 'in_progress', 'succeeded'
|
|
657
|
-
*
|
|
658
|
-
*
|
|
690
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
691
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
692
|
+
*
|
|
693
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
694
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
695
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
696
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
697
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
698
|
+
* nothing was charged.
|
|
699
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
700
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
701
|
+
* person checks with the supplier. Do not book again.
|
|
702
|
+
*
|
|
703
|
+
* The guest is e-mailed in every case.
|
|
659
704
|
*/
|
|
660
705
|
hotelBooking(bookingJobId: string): Promise<Record<string, unknown>>;
|
|
661
706
|
/**
|
|
662
707
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
663
708
|
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
709
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
710
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
711
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
712
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
713
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
667
714
|
*/
|
|
668
715
|
bookHotelAndWait(params: Parameters<LetsFG['bookHotel']>[0] & {
|
|
669
716
|
pollIntervalMs?: number;
|
|
670
717
|
maxWaitMs?: number;
|
|
671
718
|
}): Promise<Record<string, unknown>>;
|
|
672
719
|
/**
|
|
673
|
-
* Release a reservation at the supplier.
|
|
720
|
+
* Release a reservation at the supplier and refund the guest.
|
|
674
721
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
722
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
723
|
+
* zero-charge cancellation — a refundable rate before its
|
|
724
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
725
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
726
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
679
727
|
*
|
|
680
728
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
681
729
|
* out, do not assume it failed — re-check before retrying.
|
|
@@ -730,4 +778,4 @@ declare const BoostedTravel: typeof LetsFG;
|
|
|
730
778
|
declare const BoostedTravelError: typeof LetsFGError;
|
|
731
779
|
type BoostedTravelConfig = LetsFGConfig;
|
|
732
780
|
|
|
733
|
-
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
|
781
|
+
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, HOTEL_BOOKING_FINAL_STATUSES, type HotelBookingStatus, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
BoostedTravelError: () => BoostedTravelError,
|
|
26
26
|
ErrorCategory: () => ErrorCategory,
|
|
27
27
|
ErrorCode: () => ErrorCode,
|
|
28
|
+
HOTEL_BOOKING_FINAL_STATUSES: () => HOTEL_BOOKING_FINAL_STATUSES,
|
|
28
29
|
LetsFG: () => LetsFG,
|
|
29
30
|
LetsFGError: () => LetsFGError,
|
|
30
31
|
OfferExpiredError: () => OfferExpiredError,
|
|
@@ -1612,6 +1613,7 @@ var LATE_MERGE_POLL_MS = 3e3;
|
|
|
1612
1613
|
var LATE_MERGE_GRACE_MS = 9e4;
|
|
1613
1614
|
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
1614
1615
|
var NON_TERMINAL = ["pending", "searching"];
|
|
1616
|
+
var HOTEL_BOOKING_FINAL_STATUSES = ["succeeded", "failed", "attention"];
|
|
1615
1617
|
var LetsFG = class {
|
|
1616
1618
|
bearerToken;
|
|
1617
1619
|
apiKey;
|
|
@@ -1863,16 +1865,18 @@ var LetsFG = class {
|
|
|
1863
1865
|
}
|
|
1864
1866
|
// ── Hotels ──────────────────────────────────────────────────────────
|
|
1865
1867
|
//
|
|
1866
|
-
// A
|
|
1867
|
-
// deliberate: a hotel search opens a real session at the
|
|
1868
|
-
// blocks a real rate, so a caller is never allowed to
|
|
1869
|
-
// commitment only to discover it cannot pay. The same
|
|
1870
|
-
// flight booking authorises hotels
|
|
1868
|
+
// A connected payment method is required for EVERY hotel call, search
|
|
1869
|
+
// included. That is deliberate: a hotel search opens a real session at the
|
|
1870
|
+
// supplier and booking blocks a real rate, so a caller is never allowed to
|
|
1871
|
+
// reach the point of commitment only to discover it cannot pay. The same
|
|
1872
|
+
// method that authorises flight booking authorises hotels.
|
|
1871
1873
|
//
|
|
1872
|
-
//
|
|
1873
|
-
//
|
|
1874
|
-
//
|
|
1875
|
-
//
|
|
1874
|
+
// How a hotel is paid (since 2026-09-11): the full `price` is HELD on the
|
|
1875
|
+
// connected Revolut method, LetsFG books and pays the supplier itself, and the
|
|
1876
|
+
// hold is captured only once the supplier has confirmed. A booking that fails
|
|
1877
|
+
// releases the hold. There is no reservation fee, no deposit and no pay link —
|
|
1878
|
+
// those belonged to the process retired on 2026-09-11. Every rate type is
|
|
1879
|
+
// sold, refundable and non-refundable.
|
|
1876
1880
|
/**
|
|
1877
1881
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
1878
1882
|
*
|
|
@@ -1893,12 +1897,17 @@ var LetsFG = class {
|
|
|
1893
1897
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
1894
1898
|
* — so this gets its own generous timeout rather than the client default.
|
|
1895
1899
|
*
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1898
|
-
* `
|
|
1900
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
1901
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
1902
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
1903
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
1904
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
1905
|
+
* `cancellation_policy` and its own `session_id`.
|
|
1899
1906
|
*
|
|
1900
|
-
*
|
|
1901
|
-
*
|
|
1907
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
1908
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
1909
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
1910
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
1902
1911
|
*/
|
|
1903
1912
|
async searchHotels(params) {
|
|
1904
1913
|
this.requireApiKey();
|
|
@@ -1911,7 +1920,8 @@ var LetsFG = class {
|
|
|
1911
1920
|
children: params.children ?? 0,
|
|
1912
1921
|
nationality: params.nationality ?? "PL",
|
|
1913
1922
|
limit: params.limit ?? 40,
|
|
1914
|
-
with_images: params.withImages ?? true
|
|
1923
|
+
with_images: params.withImages ?? true,
|
|
1924
|
+
currency: params.currency ?? "USD"
|
|
1915
1925
|
};
|
|
1916
1926
|
if (params.childAges?.length) body.child_ages = params.childAges;
|
|
1917
1927
|
return this.post("/developers/api/v1/hotels/search", body, 24e4);
|
|
@@ -1919,30 +1929,42 @@ var LetsFG = class {
|
|
|
1919
1929
|
/**
|
|
1920
1930
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
1921
1931
|
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
1932
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
1933
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
1934
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
1935
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
1936
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
1937
|
+
* reservation fee, no deposit and no pay link.
|
|
1927
1938
|
*
|
|
1928
|
-
*
|
|
1929
|
-
*
|
|
1939
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
1940
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
1941
|
+
* bookHotelAndWait() if you would rather block.
|
|
1930
1942
|
*
|
|
1931
|
-
* Send `expectedPrice`
|
|
1932
|
-
* them
|
|
1933
|
-
*
|
|
1943
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
1944
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
1945
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
1946
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
1947
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
1948
|
+
* returns 400 invalid_details naming the fields.
|
|
1934
1949
|
*
|
|
1935
|
-
* Do NOT call this again for
|
|
1936
|
-
* the
|
|
1950
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
1951
|
+
* A retry of the same booking returns the job already under way
|
|
1952
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
1937
1953
|
*/
|
|
1938
1954
|
async bookHotel(params) {
|
|
1939
1955
|
this.requireApiKey();
|
|
1956
|
+
if (typeof params.expectedCost !== "number") {
|
|
1957
|
+
throw new LetsFGError(
|
|
1958
|
+
"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",
|
|
1959
|
+
400
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1940
1962
|
const body = {
|
|
1941
1963
|
session_id: params.sessionId,
|
|
1942
1964
|
hotel_code: params.hotelCode,
|
|
1943
1965
|
combination_id_v2: params.combinationIdV2,
|
|
1944
1966
|
expected_price: params.expectedPrice,
|
|
1945
|
-
|
|
1967
|
+
expected_cost: params.expectedCost,
|
|
1946
1968
|
city_id: params.cityId,
|
|
1947
1969
|
city_name: params.cityName,
|
|
1948
1970
|
check_in: params.checkIn,
|
|
@@ -1954,16 +1976,30 @@ var LetsFG = class {
|
|
|
1954
1976
|
phone_country_code: params.phoneCountryCode ?? "48",
|
|
1955
1977
|
special_requests: params.specialRequests ?? []
|
|
1956
1978
|
};
|
|
1979
|
+
if (params.currency) body.currency = params.currency;
|
|
1980
|
+
if (params.fxRate != null) body.fx_rate = params.fxRate;
|
|
1957
1981
|
if (params.combinationId != null) body.combination_id = params.combinationId;
|
|
1958
1982
|
if (params.hotelName) body.hotel_name = params.hotelName;
|
|
1983
|
+
if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
|
|
1959
1984
|
return this.post("/developers/api/v1/hotels/book", body, 9e4);
|
|
1960
1985
|
}
|
|
1961
1986
|
/**
|
|
1962
1987
|
* Collect the result of a booking started with bookHotel().
|
|
1963
1988
|
*
|
|
1964
|
-
* `status` is 'in_progress', 'succeeded'
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
1989
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
1990
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
1991
|
+
*
|
|
1992
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
1993
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
1994
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
1995
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
1996
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
1997
|
+
* nothing was charged.
|
|
1998
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
1999
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
2000
|
+
* person checks with the supplier. Do not book again.
|
|
2001
|
+
*
|
|
2002
|
+
* The guest is e-mailed in every case.
|
|
1967
2003
|
*/
|
|
1968
2004
|
async hotelBooking(bookingJobId) {
|
|
1969
2005
|
this.requireApiKey();
|
|
@@ -1975,13 +2011,15 @@ var LetsFG = class {
|
|
|
1975
2011
|
/**
|
|
1976
2012
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
1977
2013
|
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
2014
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
2015
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
2016
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
2017
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
2018
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
1981
2019
|
*/
|
|
1982
2020
|
async bookHotelAndWait(params) {
|
|
1983
2021
|
const pollIntervalMs = params.pollIntervalMs ?? 2e4;
|
|
1984
|
-
const maxWaitMs = params.maxWaitMs ??
|
|
2022
|
+
const maxWaitMs = params.maxWaitMs ?? 18e5;
|
|
1985
2023
|
const job = await this.bookHotel(params);
|
|
1986
2024
|
const jobId = job.booking_job_id;
|
|
1987
2025
|
if (!jobId) return job;
|
|
@@ -1991,19 +2029,19 @@ var LetsFG = class {
|
|
|
1991
2029
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1992
2030
|
waited += pollIntervalMs;
|
|
1993
2031
|
result = await this.hotelBooking(jobId);
|
|
1994
|
-
|
|
1995
|
-
if (st === "succeeded" || st === "failed") return result;
|
|
2032
|
+
if (HOTEL_BOOKING_FINAL_STATUSES.includes(result.status)) return result;
|
|
1996
2033
|
}
|
|
1997
2034
|
if (result.booking_job_id == null) result.booking_job_id = jobId;
|
|
1998
2035
|
return result;
|
|
1999
2036
|
}
|
|
2000
2037
|
/**
|
|
2001
|
-
* Release a reservation at the supplier.
|
|
2038
|
+
* Release a reservation at the supplier and refund the guest.
|
|
2002
2039
|
*
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2040
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
2041
|
+
* zero-charge cancellation — a refundable rate before its
|
|
2042
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
2043
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
2044
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
2007
2045
|
*
|
|
2008
2046
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
2009
2047
|
* out, do not assume it failed — re-check before retrying.
|
|
@@ -2142,6 +2180,7 @@ var BoostedTravelError = LetsFGError;
|
|
|
2142
2180
|
BoostedTravelError,
|
|
2143
2181
|
ErrorCategory,
|
|
2144
2182
|
ErrorCode,
|
|
2183
|
+
HOTEL_BOOKING_FINAL_STATUSES,
|
|
2145
2184
|
LetsFG,
|
|
2146
2185
|
LetsFGError,
|
|
2147
2186
|
OfferExpiredError,
|