From 9fb2c7673613d817dd32df638e0d7e7b4e777251 Mon Sep 17 00:00:00 2001 From: dproychev-payhawk Date: Thu, 30 Jul 2026 11:10:10 +0300 Subject: [PATCH] feat(loyalty-programmes): add air loyalty programmes resource --- src/index.ts | 10 ++- .../LoyaltyProgrammes.spec.ts | 63 +++++++++++++++++++ .../LoyaltyProgrammes/LoyaltyProgrammes.ts | 37 +++++++++++ .../LoyaltyProgrammesTypes.ts | 26 ++++++++ .../LoyaltyProgrammes/index.ts | 2 + .../LoyaltyProgrammes/mockLoyaltyProgramme.ts | 10 +++ src/supportingResources/index.ts | 1 + src/types/index.ts | 1 + 8 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.spec.ts create mode 100644 src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.ts create mode 100644 src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammesTypes.ts create mode 100644 src/supportingResources/LoyaltyProgrammes/index.ts create mode 100644 src/supportingResources/LoyaltyProgrammes/mockLoyaltyProgramme.ts diff --git a/src/index.ts b/src/index.ts index c9215b6d..98a94732 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,12 @@ import { AirlineInitiatedChanges, } from './booking' import { Client, Config, DuffelError as _DuffelError } from './Client' -import { Aircraft, Airlines, Airports } from './supportingResources' +import { + Aircraft, + Airlines, + Airports, + LoyaltyProgrammes, +} from './supportingResources' import { Suggestions } from './Places/Suggestions' import { Refunds } from './DuffelPayments/Refunds' import { Sessions } from './Links' @@ -32,6 +37,7 @@ export interface DuffelAPIClient { airlines: Airlines airports: Airports batchOfferRequests: BatchOfferRequests + loyaltyProgrammes: LoyaltyProgrammes offers: Offers offerRequests: OfferRequests orders: Orders @@ -51,6 +57,7 @@ export class Duffel { public airlines: Airlines public airports: Airports public links: Sessions + public loyaltyProgrammes: LoyaltyProgrammes public batchOfferRequests: BatchOfferRequests public offerRequests: OfferRequests public offers: Offers @@ -82,6 +89,7 @@ export class Duffel { this.airports = new Airports(this.client) this.airlineInitiatedChanges = new AirlineInitiatedChanges(this.client) this.links = new Sessions(this.client) + this.loyaltyProgrammes = new LoyaltyProgrammes(this.client) this.batchOfferRequests = new BatchOfferRequests(this.client) this.offerRequests = new OfferRequests(this.client) this.offers = new Offers(this.client) diff --git a/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.spec.ts b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.spec.ts new file mode 100644 index 00000000..20ca4e7d --- /dev/null +++ b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.spec.ts @@ -0,0 +1,63 @@ +import nock from 'nock' +import { Client } from '../../Client' +import { LoyaltyProgrammes } from './LoyaltyProgrammes' +import { mockLoyaltyProgramme } from './mockLoyaltyProgramme' + +describe('loyaltyProgrammes', () => { + afterEach(() => { + nock.cleanAll() + }) + + test('should get a single loyalty programme', async () => { + nock(/(.*)/) + .get(`/air/loyalty_programmes/${mockLoyaltyProgramme.id}`) + .reply(200, { data: mockLoyaltyProgramme }) + + const response = await new LoyaltyProgrammes( + new Client({ token: 'mockToken' }), + ).get(mockLoyaltyProgramme.id) + expect(response.data?.id).toBe(mockLoyaltyProgramme.id) + }) + + test('should get a page of loyalty programmes', async () => { + nock(/(.*)/) + .get(`/air/loyalty_programmes?limit=1`) + .reply(200, { + data: [mockLoyaltyProgramme], + meta: { limit: 1, before: null, after: null }, + }) + + const response = await new LoyaltyProgrammes( + new Client({ token: 'mockToken' }), + ).list({ limit: 1 }) + expect(response.data).toHaveLength(1) + expect(response.data[0].id).toBe(mockLoyaltyProgramme.id) + }) + + test('should follow the pagination cursor across pages', async () => { + nock(/(.*)/) + .get(`/air/loyalty_programmes?limit=1`) + .reply(200, { + data: [mockLoyaltyProgramme], + meta: { limit: 1, before: null, after: 'cursor' }, + }) + nock(/(.*)/) + .get(`/air/loyalty_programmes?after=cursor&limit=1`) + .reply(200, { + data: [{ ...mockLoyaltyProgramme, id: 'loy_00001876aqC8c5umZmrRdt' }], + meta: { limit: 1, before: null, after: null }, + }) + + const loyaltyProgrammes = new LoyaltyProgrammes( + new Client({ token: 'mockToken' }), + ) + const firstPage = await loyaltyProgrammes.list({ limit: 1 }) + expect(firstPage.meta?.after).toBe('cursor') + const secondPage = await loyaltyProgrammes.list({ + limit: 1, + after: firstPage.meta?.after, + }) + expect(secondPage.data[0].id).toBe('loy_00001876aqC8c5umZmrRdt') + expect(secondPage.meta?.after).toBeNull() + }) +}) diff --git a/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.ts b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.ts new file mode 100644 index 00000000..054a5f4b --- /dev/null +++ b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammes.ts @@ -0,0 +1,37 @@ +import { Client } from '../../Client' +import { Resource } from '../../Resource' +import { DuffelResponse, LoyaltyProgramme, PaginationMeta } from '../../types' + +/** Loyalty programmes are used to identify the frequent flyer programmes offered by airlines + * @class + * @link https://duffel.com/docs/api/loyalty-programmes + */ +export class LoyaltyProgrammes extends Resource { + /** + * Endpoint path + */ + path: string + + constructor(client: Client) { + super(client) + this.path = 'air/loyalty_programmes' + } + + /** + * Retrieves a loyalty programme by its ID + * @param {string} id - Duffel's unique identifier for the loyalty programme + * @link https://duffel.com/docs/api/loyalty-programmes/get-a-single-loyalty-programme + */ + public get = async (id: string): Promise> => + this.request({ method: 'GET', path: `${this.path}/${id}` }) + + /** + * Retrieves a page of loyalty programmes. The results may be returned in any order. + * @param {Object} [options] - Pagination options (optional: limit, after, before) + * @link https://duffel.com/docs/api/loyalty-programmes/list-loyalty-programmes + */ + public list = ( + options?: PaginationMeta, + ): Promise> => + this.request({ method: 'GET', path: this.path, params: options }) +} diff --git a/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammesTypes.ts b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammesTypes.ts new file mode 100644 index 00000000..0ad69306 --- /dev/null +++ b/src/supportingResources/LoyaltyProgrammes/LoyaltyProgrammesTypes.ts @@ -0,0 +1,26 @@ +/** + * Loyalty programmes are used to identify the frequent flyer programmes offered by airlines + * @link https://duffel.com/docs/api/loyalty-programmes/schema + */ +export interface LoyaltyProgramme { + /** + * Duffel's unique identifier for the loyalty programme + */ + id: string + /** + * Name of the loyalty programme + */ + name: string + /** + * The Duffel ID of the airline that corresponds to the loyalty programme + */ + owner_airline_id: string + /* + * The name of the alliance this loyalty programme is part of. This may be `null` if the loyalty programme isn't part of an alliance. + */ + alliance: string | null + /* + * Path to a svg of the loyalty programme logo. This may be `null` if no logo is available. + */ + logo_url: string | null +} diff --git a/src/supportingResources/LoyaltyProgrammes/index.ts b/src/supportingResources/LoyaltyProgrammes/index.ts new file mode 100644 index 00000000..d039fa11 --- /dev/null +++ b/src/supportingResources/LoyaltyProgrammes/index.ts @@ -0,0 +1,2 @@ +export * from './LoyaltyProgrammes' +export * as LoyaltyProgrammeType from './LoyaltyProgrammesTypes' diff --git a/src/supportingResources/LoyaltyProgrammes/mockLoyaltyProgramme.ts b/src/supportingResources/LoyaltyProgrammes/mockLoyaltyProgramme.ts new file mode 100644 index 00000000..b8eb1595 --- /dev/null +++ b/src/supportingResources/LoyaltyProgrammes/mockLoyaltyProgramme.ts @@ -0,0 +1,10 @@ +import { LoyaltyProgramme } from '../../types' + +export const mockLoyaltyProgramme: LoyaltyProgramme = { + id: 'loy_00001876aqC8c5umZmrRds', + name: 'Miles & More', + owner_airline_id: 'arl_00001876aqC8c5umZmrRds', + alliance: 'Star Alliance', + logo_url: + 'https://assets.duffel.com/img/loyalty-programmes/full-color-logo/LH.svg', +} diff --git a/src/supportingResources/index.ts b/src/supportingResources/index.ts index 846c7577..08a46805 100644 --- a/src/supportingResources/index.ts +++ b/src/supportingResources/index.ts @@ -1,3 +1,4 @@ export * from './Aircraft' export * from './Airlines' export * from './Airports' +export * from './LoyaltyProgrammes' diff --git a/src/types/index.ts b/src/types/index.ts index 2a4f4527..04d06f31 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -17,6 +17,7 @@ export * from '../notifications/Webhooks/WebhooksType' export * from '../supportingResources/Aircraft/AircraftTypes' export * from '../supportingResources/Airlines/AirlinesTypes' export * from '../supportingResources/Airports/AirportsTypes' +export * from '../supportingResources/LoyaltyProgrammes/LoyaltyProgrammesTypes' export * from './ClientType' export * from './Identity' export * from './shared'