@voyant-travel/flights 0.121.0 → 0.122.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./contract/types.js";
4
4
  export { createFlightAdminRoutes, createFlightsHonoModule, type FlightOrderPaymentSummary, type FlightPaymentIntegration, type FlightsHonoModuleOptions, type FlightsRouteOptions, } from "./hono.js";
5
5
  export { type ConnectionResult, type ConnectionSearchStatus, type FanOutFlightSearchOptions, type FanOutFlightSearchResult, fanOutFlightSearch, type MergedFlightOffer, } from "./orchestration/fan-out.js";
6
6
  export { itineraryFingerprint } from "./orchestration/fingerprint.js";
7
+ export { buildFlightSummary, createFlightOrderPaymentIntegration, type FlightCardBilling, type FlightOrderPaymentIntegrationDeps, formatDay, type OrderPaymentSessionsLike, parseAmountToCents, synthesizeBilling, } from "./payment-integration.js";
7
8
  export { type Aircraft, type Airline, type Airport, dedupeCodes, type ReferenceDataCapabilities, type ReferenceDataProvider, } from "./reference/contract.js";
8
9
  export { createLocalPostgresReferenceProvider, type LocalPostgresReferenceProviderOptions, referenceAircraft, referenceAirlines, referenceAirports, } from "./reference/local-postgres.js";
9
10
  export { createStaticBundleReferenceProvider, type StaticBundleProviderOptions, type StaticBundleReferenceData, } from "./reference/static-bundle.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAClB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,iCAAiC,EACjC,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AAGnC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,GACzB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AAErE,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,GAC3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oCAAoC,EACpC,KAAK,qCAAqC,EAC1C,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,mCAAmC,EACnC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,GAC/B,MAAM,8BAA8B,CAAA;AAErC,OAAO,EACL,KAAK,+BAA+B,EACpC,wBAAwB,GACzB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAClB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,iCAAiC,EACjC,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AAGnC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,GACzB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AAGrE,OAAO,EACL,kBAAkB,EAClB,mCAAmC,EACnC,KAAK,iBAAiB,EACtB,KAAK,iCAAiC,EACtC,SAAS,EACT,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,GAC3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oCAAoC,EACpC,KAAK,qCAAqC,EAC1C,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,mCAAmC,EACnC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,GAC/B,MAAM,8BAA8B,CAAA;AAErC,OAAO,EACL,KAAK,+BAA+B,EACpC,wBAAwB,GACzB,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -9,6 +9,9 @@ export { createFlightAdminRoutes, createFlightsHonoModule, } from "./hono.js";
9
9
  export { fanOutFlightSearch, } from "./orchestration/fan-out.js";
10
10
  // Orchestration — fingerprinting + multi-connection fan-out.
11
11
  export { itineraryFingerprint } from "./orchestration/fingerprint.js";
12
+ // Flight-specific payment integration — maps a FlightOrder onto a generic
13
+ // order-payment-session service + an optional card provider (both structural).
14
+ export { buildFlightSummary, createFlightOrderPaymentIntegration, formatDay, parseAmountToCents, synthesizeBilling, } from "./payment-integration.js";
12
15
  // ReferenceDataProvider — swappable provider for global reference data.
13
16
  export { dedupeCodes, } from "./reference/contract.js";
14
17
  export { createLocalPostgresReferenceProvider, referenceAircraft, referenceAirlines, referenceAirports, } from "./reference/local-postgres.js";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Flight-specific payment integration: maps a `FlightOrder` onto finance's
3
+ * generic order-payment-session service and (optionally) a card provider.
4
+ *
5
+ * The flights module stays payment-provider agnostic AND finance-agnostic — the
6
+ * deps below are typed STRUCTURALLY so this file imports neither
7
+ * `@voyant-travel/finance` nor any payment provider. The deployment wires the
8
+ * concrete services in `createFlightOrderPaymentIntegration({ ... })`.
9
+ *
10
+ * What's flight-specific (and therefore lives here, not in finance):
11
+ * - mapping `order.totalPrice` → amountCents/currency,
12
+ * - picking the payer from `order.passengers[0]`,
13
+ * - the `notes` summary (`buildFlightSummary`),
14
+ * - synthesizing card billing (`synthesizeBilling`).
15
+ */
16
+ import type { AnyDrizzleDb } from "@voyant-travel/db";
17
+ import type { Context } from "hono";
18
+ import type { FlightOrder, FlightPassenger } from "./contract/types.js";
19
+ import type { FlightPaymentIntegration } from "./hono.js";
20
+ export declare function parseAmountToCents(amount: string): number;
21
+ /** Card billing details synthesized from a passenger + (optional) contact. */
22
+ export interface FlightCardBilling {
23
+ email: string;
24
+ phone: string;
25
+ firstName: string;
26
+ lastName: string;
27
+ city: string;
28
+ country: number;
29
+ state: string;
30
+ postalCode: string;
31
+ details: string;
32
+ }
33
+ export declare function synthesizeBilling(pax: FlightPassenger, contact: {
34
+ email?: string;
35
+ phone?: string;
36
+ } | undefined): FlightCardBilling;
37
+ export declare function formatDay(iso: string): string;
38
+ /**
39
+ * Build a single-line human summary of the flight order — surfaced as
40
+ * `payment_session.notes` so the public landing page can show what's being paid.
41
+ */
42
+ export declare function buildFlightSummary(order: FlightOrder): string;
43
+ /**
44
+ * Structural shape of finance's generic order-payment-session service. Matches
45
+ * `@voyant-travel/finance`'s `createOrderPaymentSessions(...)` return value —
46
+ * declared here (not imported) so flights never depends on finance.
47
+ */
48
+ export interface OrderPaymentSessionsLike {
49
+ ensureSession(db: AnyDrizzleDb, params: {
50
+ targetId: string;
51
+ currency: string;
52
+ amountCents: number;
53
+ payerEmail?: string | null;
54
+ payerName?: string | null;
55
+ notes?: string | null;
56
+ }, startProvider?: (db: AnyDrizzleDb, sessionId: string) => Promise<void>): Promise<{
57
+ sessionId: string;
58
+ status: string;
59
+ } | null>;
60
+ fetchSessions(db: AnyDrizzleDb, targetIds: string[]): Promise<Map<string, {
61
+ sessionId: string;
62
+ status: string;
63
+ }>>;
64
+ }
65
+ export interface FlightOrderPaymentIntegrationDeps {
66
+ /** Finance's generic order-payment-session service (structural). */
67
+ orderPaymentSessions: OrderPaymentSessionsLike;
68
+ /**
69
+ * Optional card provider start. Best-effort — called after a session is
70
+ * created so the provider can populate the card redirect link. The deployment
71
+ * supplies its card provider (e.g. Netopia); flights stays provider-agnostic.
72
+ *
73
+ * Receives the request `Context` (so the deployment can resolve its
74
+ * request-scoped provider runtime / container) plus the new session id and
75
+ * synthesized card billing.
76
+ */
77
+ startCardPayment?(c: Context, sessionId: string, billing: FlightCardBilling): Promise<void>;
78
+ }
79
+ /**
80
+ * Build a {@link FlightPaymentIntegration} from the generic order-payment
81
+ * service + an optional card provider. The deployment composes this in its
82
+ * flights runtime wiring.
83
+ */
84
+ export declare function createFlightOrderPaymentIntegration(deps: FlightOrderPaymentIntegrationDeps): FlightPaymentIntegration;
85
+ //# sourceMappingURL=payment-integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-integration.d.ts","sourceRoot":"","sources":["../src/payment-integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACvE,OAAO,KAAK,EAA6B,wBAAwB,EAAE,MAAM,WAAW,CAAA;AAQpF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACtD,iBAAiB,CAYnB;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAgB7D;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,aAAa,CACX,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KACtB,EACD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACrE,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACxD,aAAa,CACX,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CAC/D;AAED,MAAM,WAAW,iCAAiC;IAChD,oEAAoE;IACpE,oBAAoB,EAAE,wBAAwB,CAAA;IAC9C;;;;;;;;OAQG;IACH,gBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5F;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,iCAAiC,GACtC,wBAAwB,CAoC1B"}
@@ -0,0 +1,86 @@
1
+ // `c.var.db` is set by the createApp DB middleware; the global ContextVariableMap
2
+ // doesn't declare it, so cast at the call site to keep type-safety local.
3
+ function getDb(c) {
4
+ return c.var.db;
5
+ }
6
+ export function parseAmountToCents(amount) {
7
+ const n = Number.parseFloat(amount);
8
+ if (!Number.isFinite(n))
9
+ return 0;
10
+ return Math.round(n * 100);
11
+ }
12
+ export function synthesizeBilling(pax, contact) {
13
+ return {
14
+ email: contact?.email ?? pax.email ?? "tbd@example.com",
15
+ phone: contact?.phone ?? pax.phone ?? "0000000000",
16
+ firstName: pax.firstName,
17
+ lastName: pax.lastName,
18
+ city: "TBD",
19
+ country: 642, // ISO 3166-1 numeric — Romania default; customer overrides on hosted form.
20
+ state: "TBD",
21
+ postalCode: "00000",
22
+ details: "Pending — customer to confirm at payment.",
23
+ };
24
+ }
25
+ export function formatDay(iso) {
26
+ const d = new Date(iso);
27
+ if (Number.isNaN(d.getTime()))
28
+ return iso;
29
+ return d.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" });
30
+ }
31
+ /**
32
+ * Build a single-line human summary of the flight order — surfaced as
33
+ * `payment_session.notes` so the public landing page can show what's being paid.
34
+ */
35
+ export function buildFlightSummary(order) {
36
+ const parts = [];
37
+ for (const itin of order.offer.itineraries) {
38
+ if (itin.segments.length === 0)
39
+ continue;
40
+ const first = itin.segments[0];
41
+ const last = itin.segments[itin.segments.length - 1];
42
+ if (!first || !last)
43
+ continue;
44
+ parts.push(`${first.departure.iataCode} → ${last.arrival.iataCode} · ${formatDay(first.departure.at)}`);
45
+ }
46
+ const paxNames = order.passengers
47
+ .map((p) => `${p.firstName} ${p.lastName}`.trim())
48
+ .filter(Boolean);
49
+ if (paxNames.length > 0)
50
+ parts.push(paxNames.join(", "));
51
+ return parts.join(" · ");
52
+ }
53
+ /**
54
+ * Build a {@link FlightPaymentIntegration} from the generic order-payment
55
+ * service + an optional card provider. The deployment composes this in its
56
+ * flights runtime wiring.
57
+ */
58
+ export function createFlightOrderPaymentIntegration(deps) {
59
+ const { orderPaymentSessions, startCardPayment } = deps;
60
+ return {
61
+ async ensureOrderSession(c, order, contact) {
62
+ const db = getDb(c);
63
+ const passengerForBilling = order.passengers[0];
64
+ if (!passengerForBilling)
65
+ return null;
66
+ const amountCents = parseAmountToCents(order.totalPrice.amount);
67
+ const startProvider = startCardPayment
68
+ ? (_writer, sessionId) => startCardPayment(c, sessionId, synthesizeBilling(passengerForBilling, contact))
69
+ : undefined;
70
+ return orderPaymentSessions.ensureSession(db, {
71
+ targetId: order.orderId,
72
+ currency: order.totalPrice.currency,
73
+ amountCents,
74
+ payerEmail: contact?.email ?? passengerForBilling.email ?? null,
75
+ payerName: `${passengerForBilling.firstName} ${passengerForBilling.lastName}`.trim(),
76
+ notes: buildFlightSummary(order),
77
+ }, startProvider);
78
+ },
79
+ async fetchOrderSessions(c, orderIds) {
80
+ const result = new Map();
81
+ if (orderIds.length === 0)
82
+ return result;
83
+ return orderPaymentSessions.fetchSessions(getDb(c), orderIds);
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=payment-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-integration.test.d.ts","sourceRoot":"","sources":["../src/payment-integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { buildFlightSummary, createFlightOrderPaymentIntegration, parseAmountToCents, synthesizeBilling, } from "./payment-integration.js";
3
+ const DB = { __db: true };
4
+ let CTX;
5
+ function stubContext() {
6
+ CTX = { var: { db: DB } };
7
+ return CTX;
8
+ }
9
+ function stubOrder(over = {}) {
10
+ return {
11
+ orderId: "ord_1",
12
+ status: "confirmed",
13
+ offer: {
14
+ itineraries: [
15
+ {
16
+ segments: [
17
+ {
18
+ departure: { iataCode: "LHR", at: "2026-07-01T08:00:00Z" },
19
+ arrival: { iataCode: "CDG", at: "2026-07-01T10:00:00Z" },
20
+ },
21
+ ],
22
+ },
23
+ ],
24
+ },
25
+ passengers: [
26
+ { passengerId: "p1", type: "adult", firstName: "Ada", lastName: "Lovelace" },
27
+ ],
28
+ totalPrice: { amount: "250.50", currency: "EUR" },
29
+ createdAt: "2026-06-01T00:00:00Z",
30
+ ...over,
31
+ };
32
+ }
33
+ describe("helpers", () => {
34
+ it("parseAmountToCents rounds to cents and guards non-finite", () => {
35
+ expect(parseAmountToCents("250.50")).toBe(25050);
36
+ expect(parseAmountToCents("not-a-number")).toBe(0);
37
+ });
38
+ it("synthesizeBilling prefers contact, falls back to passenger then defaults", () => {
39
+ const pax = {
40
+ passengerId: "p1",
41
+ type: "adult",
42
+ firstName: "Ada",
43
+ lastName: "Lovelace",
44
+ email: "pax@x.com",
45
+ };
46
+ expect(synthesizeBilling(pax, { email: "c@x.com" })).toMatchObject({
47
+ email: "c@x.com",
48
+ phone: "0000000000",
49
+ firstName: "Ada",
50
+ lastName: "Lovelace",
51
+ country: 642,
52
+ });
53
+ expect(synthesizeBilling(pax, undefined).email).toBe("pax@x.com");
54
+ });
55
+ it("buildFlightSummary renders route + passenger names", () => {
56
+ const summary = buildFlightSummary(stubOrder());
57
+ expect(summary).toContain("LHR → CDG");
58
+ expect(summary).toContain("Ada Lovelace");
59
+ });
60
+ });
61
+ describe("createFlightOrderPaymentIntegration", () => {
62
+ function stubSessions(over = {}) {
63
+ return {
64
+ ensureSession: vi.fn(async () => ({ sessionId: "ps_1", status: "pending" })),
65
+ fetchSessions: vi.fn(async () => new Map()),
66
+ ...over,
67
+ };
68
+ }
69
+ it("ensureOrderSession maps the order onto generic session params", async () => {
70
+ const orderPaymentSessions = stubSessions();
71
+ const integration = createFlightOrderPaymentIntegration({ orderPaymentSessions });
72
+ const result = await integration.ensureOrderSession(stubContext(), stubOrder(), {
73
+ email: "c@x.com",
74
+ });
75
+ expect(result).toEqual({ sessionId: "ps_1", status: "pending" });
76
+ expect(orderPaymentSessions.ensureSession).toHaveBeenCalledWith(DB, expect.objectContaining({
77
+ targetId: "ord_1",
78
+ currency: "EUR",
79
+ amountCents: 25050,
80
+ payerEmail: "c@x.com",
81
+ payerName: "Ada Lovelace",
82
+ notes: expect.stringContaining("LHR → CDG"),
83
+ }), undefined);
84
+ });
85
+ it("returns null when the order has no passengers", async () => {
86
+ const orderPaymentSessions = stubSessions();
87
+ const integration = createFlightOrderPaymentIntegration({ orderPaymentSessions });
88
+ const result = await integration.ensureOrderSession(stubContext(), stubOrder({ passengers: [] }));
89
+ expect(result).toBeNull();
90
+ expect(orderPaymentSessions.ensureSession).not.toHaveBeenCalled();
91
+ });
92
+ it("wires startCardPayment through the generic startProvider with synthesized billing", async () => {
93
+ const startCardPayment = vi.fn(async () => undefined);
94
+ const ensureSession = vi.fn(async (_db, _params, startProvider) => {
95
+ await startProvider?.(DB, "ps_new");
96
+ return { sessionId: "ps_new", status: "pending" };
97
+ });
98
+ const integration = createFlightOrderPaymentIntegration({
99
+ orderPaymentSessions: stubSessions({ ensureSession: ensureSession }),
100
+ startCardPayment,
101
+ });
102
+ const ctx = stubContext();
103
+ await integration.ensureOrderSession(ctx, stubOrder(), undefined);
104
+ expect(startCardPayment).toHaveBeenCalledWith(ctx, "ps_new", expect.objectContaining({ firstName: "Ada", lastName: "Lovelace", country: 642 }));
105
+ });
106
+ it("fetchOrderSessions delegates to the generic service", async () => {
107
+ const fetchSessions = vi.fn(async () => new Map([["ord_1", { sessionId: "ps_1", status: "paid" }]]));
108
+ const integration = createFlightOrderPaymentIntegration({
109
+ orderPaymentSessions: stubSessions({ fetchSessions: fetchSessions }),
110
+ });
111
+ const result = await integration.fetchOrderSessions(stubContext(), ["ord_1"]);
112
+ expect(result.get("ord_1")).toEqual({ sessionId: "ps_1", status: "paid" });
113
+ expect(fetchSessions).toHaveBeenCalledWith(DB, ["ord_1"]);
114
+ });
115
+ it("fetchOrderSessions short-circuits empty input", async () => {
116
+ const fetchSessions = vi.fn(async () => new Map());
117
+ const integration = createFlightOrderPaymentIntegration({
118
+ orderPaymentSessions: stubSessions({ fetchSessions: fetchSessions }),
119
+ });
120
+ const result = await integration.fetchOrderSessions(stubContext(), []);
121
+ expect(result.size).toBe(0);
122
+ expect(fetchSessions).not.toHaveBeenCalled();
123
+ });
124
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/flights",
3
- "version": "0.121.0",
3
+ "version": "0.122.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -58,6 +58,11 @@
58
58
  "types": "./dist/hono.d.ts",
59
59
  "import": "./dist/hono.js",
60
60
  "default": "./dist/hono.js"
61
+ },
62
+ "./payment-integration": {
63
+ "types": "./dist/payment-integration.d.ts",
64
+ "import": "./dist/payment-integration.js",
65
+ "default": "./dist/payment-integration.js"
61
66
  }
62
67
  },
63
68
  "voyant": {
@@ -77,7 +82,7 @@
77
82
  "hono": "^4.12.10",
78
83
  "zod": "^4.3.6",
79
84
  "@voyant-travel/db": "^0.108.1",
80
- "@voyant-travel/catalog": "^0.119.0",
85
+ "@voyant-travel/catalog": "^0.120.0",
81
86
  "@voyant-travel/flights-contracts": "^0.104.4",
82
87
  "@voyant-travel/hono": "^0.111.0"
83
88
  },