@voyant-travel/storefront 0.121.1 → 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
@@ -3,6 +3,8 @@ import type { HonoModule } from "@voyant-travel/hono/module";
3
3
  import { createStorefrontPublicRoutes } from "./routes-public.js";
4
4
  export type { GuestBookingGuardOptions, GuestBookingGuardRequest, GuestBookingLookupInput, } from "./guest-booking-guard.js";
5
5
  export { createGuestBookingGuard } from "./guest-booking-guard.js";
6
+ export type { PaymentLinkBankTransferDetails, PaymentLinkRoutesOptions, PaymentLinkSessionInput, PaymentLinkTripComponent, PaymentLinkTripData, } from "./payment-link/routes.js";
7
+ export { createPaymentLinkRoutes } from "./payment-link/routes.js";
6
8
  export type { StorefrontAdminRoutes } from "./routes-admin.js";
7
9
  export { createStorefrontAdminRoutes } from "./routes-admin.js";
8
10
  export type { StorefrontPublicRoutes } from "./routes-public.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { BOOKING_BOOTSTRAP_INTENT_EVENT, createBookingBootstrapIntentHandler, }
2
2
  import { createStorefrontAdminRoutes } from "./routes-admin.js";
3
3
  import { createStorefrontPublicRoutes } from "./routes-public.js";
4
4
  export { createGuestBookingGuard } from "./guest-booking-guard.js";
5
+ export { createPaymentLinkRoutes } from "./payment-link/routes.js";
5
6
  export { createStorefrontAdminRoutes } from "./routes-admin.js";
6
7
  export { createStorefrontPublicRoutes } from "./routes-public.js";
7
8
  export { createStorefrontService, mergeStorefrontSettingsPatch, resolveStorefrontSettings, } from "./service.js";
@@ -0,0 +1,97 @@
1
+ import type { Context } from "hono";
2
+ import { Hono } from "hono";
3
+ /** Resolved bank-transfer beneficiary details from operator settings + env. */
4
+ export interface PaymentLinkBankTransferDetails {
5
+ beneficiary: string;
6
+ iban: string;
7
+ bankName?: string | null;
8
+ }
9
+ /** A resolved trip component, with optional product enrichment. */
10
+ export interface PaymentLinkTripComponent {
11
+ id: string;
12
+ kind: string;
13
+ entityModule: string | null;
14
+ entityId: string | null;
15
+ description: string | null;
16
+ status: string | null;
17
+ sequence: number | null;
18
+ componentTotalAmountCents: number | null;
19
+ componentCurrency: string | null;
20
+ metadata: Record<string, unknown> | null;
21
+ }
22
+ /** A resolved trip envelope + its visible components and product enrichment. */
23
+ export interface PaymentLinkTripData {
24
+ envelope: {
25
+ id: string;
26
+ status: string | null;
27
+ };
28
+ /**
29
+ * Visible (non-removed, non-cancelled) components, already ordered by
30
+ * sequence then createdAt.
31
+ */
32
+ components: PaymentLinkTripComponent[];
33
+ /** product id → display name (from the inventory product record). */
34
+ productNameById: Map<string, string>;
35
+ /** product id → cover image (from inventory product media). */
36
+ mediaByProductId: Map<string, {
37
+ url: string;
38
+ altText: string | null;
39
+ }>;
40
+ }
41
+ /** The payment-session record fields the handlers read across routes. */
42
+ export interface PaymentLinkSessionInput {
43
+ invoiceId: string | null;
44
+ amountCents: number;
45
+ currency: string;
46
+ }
47
+ /**
48
+ * Deployment-supplied access the payment-link handlers need. Everything here
49
+ * encapsulates a module storefront does not statically depend on, so the
50
+ * package stays free of inventory / trips / netopia / operator-settings
51
+ * imports.
52
+ */
53
+ export interface PaymentLinkRoutesOptions {
54
+ /**
55
+ * Resolve the bank-transfer beneficiary details (operator settings merged
56
+ * with deploy-wide env defaults), or `null` when not configured.
57
+ */
58
+ resolveBankTransferDetails(c: Context): Promise<PaymentLinkBankTransferDetails | null>;
59
+ /** Resolve the public checkout base URL from the deployment bindings. */
60
+ resolvePublicCheckoutBaseUrl(c: Context): string | null;
61
+ /**
62
+ * Best-effort: ensure a fresh payment session can be started on the card
63
+ * provider, returning the redirect URL (or null). Returns `{ configured:
64
+ * false }` when no card processor is wired so the handler can 503. May throw
65
+ * — the handler maps the error to a 502.
66
+ */
67
+ startCardPayment(c: Context, session: {
68
+ id: string;
69
+ payerName: string | null;
70
+ payerEmail: string | null;
71
+ notes: string | null;
72
+ redirectUrl: string | null;
73
+ }): Promise<{
74
+ configured: true;
75
+ redirectUrl: string | null;
76
+ } | {
77
+ configured: false;
78
+ }>;
79
+ /**
80
+ * Resolve a trip envelope (+ reconcile a paid checkout) and its visible
81
+ * components with product-media enrichment, or `null` when the envelope is
82
+ * gone. Encapsulates the trips + inventory schema reads.
83
+ */
84
+ resolveTripData(c: Context, tripEnvelopeId: string, session: {
85
+ id: string;
86
+ status: string | null;
87
+ amountCents: number;
88
+ currency: string;
89
+ provider: string | null;
90
+ }): Promise<PaymentLinkTripData | null>;
91
+ }
92
+ /**
93
+ * Build the public payment-link routes. Paths are ABSOLUTE so the deployment
94
+ * can lazy-mount the returned app directly via `lazyRoutes.paths`.
95
+ */
96
+ export declare function createPaymentLinkRoutes(options: PaymentLinkRoutesOptions): Hono;
97
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/payment-link/routes.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAS3B,+EAA+E;AAC/E,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,mEAAmE;AACnE,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACzC;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;IAC/C;;;OAGG;IACH,UAAU,EAAE,wBAAwB,EAAE,CAAA;IACtC,qEAAqE;IACrE,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,+DAA+D;IAC/D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;CACvE;AAED,yEAAyE;AACzE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,0BAA0B,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAAA;IACtF,yEAAyE;IACzE,4BAA4B,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAA;IACvD;;;;;OAKG;IACH,gBAAgB,CACd,CAAC,EAAE,OAAO,EACV,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAA;QACV,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;QACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,GACA,OAAO,CAAC;QAAE,UAAU,EAAE,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG;QAAE,UAAU,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IACpF;;;;OAIG;IACH,eAAe,CACb,CAAC,EAAE,OAAO,EACV,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAA;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,WAAW,EAAE,MAAM,CAAA;QACnB,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KACxB,GACA,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;CACvC;AA0DD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAiY/E"}
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Public payment-link + checkout-status routes — owned by
3
+ * `@voyant-travel/storefront`.
4
+ *
5
+ * agent-quality: file-size exception -- the public payment-link surface
6
+ * (config, retry, resolve, start-card, trip/booking summary, checkout-status)
7
+ * is one cohesive route family backed by the finance payment-session record;
8
+ * splitting it would scatter a single checkout contract.
9
+ *
10
+ * GET /v1/public/payment-link-config
11
+ * POST /v1/public/payment-link/:sessionId/retry
12
+ * GET /v1/public/payment-link/resolve
13
+ * POST /v1/public/payment-link/:sessionId/start-card
14
+ * GET /v1/public/payment-link/:sessionId/trip-summary
15
+ * GET /v1/public/payment-link/:sessionId/booking-summary
16
+ * GET /v1/public/bookings/:bookingId/checkout-status
17
+ *
18
+ * The routes are mounted at their ABSOLUTE public paths so the deployment can
19
+ * lazy-mount them via `lazyRoutes.paths`. All cross-module access that
20
+ * storefront does not already depend on (inventory product media, trip
21
+ * envelopes/components reconciliation, the card-payment provider, and the
22
+ * operator settings + checkout base URL) is INJECTED via `options` — the
23
+ * package never statically imports inventory / trips / the netopia plugin /
24
+ * the operator settings module.
25
+ *
26
+ * Storefront already depends acyclically on `@voyant-travel/bookings` and
27
+ * `@voyant-travel/finance`, so the booking / invoice / payment-session reads
28
+ * use those schemas directly.
29
+ */
30
+ import { bookingItems, bookings } from "@voyant-travel/bookings/schema";
31
+ import { financeService } from "@voyant-travel/finance";
32
+ import { invoices, paymentSessions } from "@voyant-travel/finance/schema";
33
+ import { and, asc, desc, eq, or } from "drizzle-orm";
34
+ import { Hono } from "hono";
35
+ const PUBLIC_PAYMENT_LINK_CONFIG_CACHE_CONTROL = "public, s-maxage=300, stale-while-revalidate=600";
36
+ // ─────────────────────────────────────────────────────────────────
37
+ // Local helpers
38
+ // ─────────────────────────────────────────────────────────────────
39
+ function cachePublicPaymentLinkConfig(c) {
40
+ c.header("Cache-Control", PUBLIC_PAYMENT_LINK_CONFIG_CACHE_CONTROL);
41
+ }
42
+ function requireRouteParam(c, name) {
43
+ const value = c.req.param(name);
44
+ return value ? value : c.json({ error: `${name} route param is required` }, 400);
45
+ }
46
+ function getDb(c) {
47
+ return c.get("db");
48
+ }
49
+ async function buildPublicBankTransferInstructions(c, options, bookingNumber, session) {
50
+ const db = getDb(c);
51
+ const details = await options.resolveBankTransferDetails(c);
52
+ if (!details)
53
+ return null;
54
+ const [invoice] = session.invoiceId
55
+ ? await db
56
+ .select({
57
+ invoiceNumber: invoices.invoiceNumber,
58
+ dueDate: invoices.dueDate,
59
+ balanceDueCents: invoices.balanceDueCents,
60
+ currency: invoices.currency,
61
+ })
62
+ .from(invoices)
63
+ .where(eq(invoices.id, session.invoiceId))
64
+ .limit(1)
65
+ : [];
66
+ return {
67
+ beneficiary: details.beneficiary,
68
+ iban: details.iban,
69
+ bankName: details.bankName ?? "-",
70
+ reference: `BOOK-${bookingNumber}`,
71
+ amountCents: invoice?.balanceDueCents ?? session.amountCents,
72
+ currency: invoice?.currency ?? session.currency,
73
+ dueAt: invoice?.dueDate ?? null,
74
+ proformaNumber: invoice?.invoiceNumber ?? null,
75
+ };
76
+ }
77
+ // ─────────────────────────────────────────────────────────────────
78
+ // Routes
79
+ // ─────────────────────────────────────────────────────────────────
80
+ /**
81
+ * Build the public payment-link routes. Paths are ABSOLUTE so the deployment
82
+ * can lazy-mount the returned app directly via `lazyRoutes.paths`.
83
+ */
84
+ export function createPaymentLinkRoutes(options) {
85
+ const hono = new Hono();
86
+ hono.get("/v1/public/payment-link-config", async (c) => {
87
+ const bankTransfer = await options.resolveBankTransferDetails(c);
88
+ cachePublicPaymentLinkConfig(c);
89
+ return c.json({
90
+ data: {
91
+ publicCheckoutBaseUrl: options.resolvePublicCheckoutBaseUrl(c),
92
+ bankTransfer,
93
+ },
94
+ });
95
+ });
96
+ hono.post("/v1/public/payment-link/:sessionId/retry", async (c) => {
97
+ const sessionId = requireRouteParam(c, "sessionId");
98
+ if (sessionId instanceof Response)
99
+ return sessionId;
100
+ const db = getDb(c);
101
+ const [original] = await db
102
+ .select()
103
+ .from(paymentSessions)
104
+ .where(eq(paymentSessions.id, sessionId))
105
+ .limit(1);
106
+ if (!original)
107
+ return c.json({ error: "Session not found" }, 404);
108
+ if (original.status === "paid" || original.status === "authorized") {
109
+ return c.json({ data: { sessionId: original.id, alreadyPaid: true } });
110
+ }
111
+ const dbCast = db;
112
+ const fresh = await financeService.createPaymentSession(dbCast, {
113
+ targetType: original.targetType,
114
+ targetId: original.targetId ?? undefined,
115
+ bookingId: original.bookingId ?? undefined,
116
+ invoiceId: original.invoiceId ?? undefined,
117
+ bookingPaymentScheduleId: original.bookingPaymentScheduleId ?? undefined,
118
+ bookingGuaranteeId: original.bookingGuaranteeId ?? undefined,
119
+ currency: original.currency,
120
+ amountCents: original.amountCents,
121
+ status: "pending",
122
+ provider: original.provider ?? undefined,
123
+ paymentMethod: original.paymentMethod ?? undefined,
124
+ payerEmail: original.payerEmail ?? undefined,
125
+ payerName: original.payerName ?? undefined,
126
+ notes: original.notes ?? undefined,
127
+ });
128
+ if (!fresh)
129
+ return c.json({ error: "Failed to create payment session" }, 500);
130
+ return c.json({ data: { sessionId: fresh.id } });
131
+ });
132
+ hono.get("/v1/public/payment-link/resolve", async (c) => {
133
+ const ref = c.req.query("ref");
134
+ if (!ref)
135
+ return c.json({ error: "ref query param is required" }, 400);
136
+ const db = getDb(c);
137
+ const [session] = await db
138
+ .select({ id: paymentSessions.id })
139
+ .from(paymentSessions)
140
+ .where(or(eq(paymentSessions.id, ref), eq(paymentSessions.clientReference, ref), eq(paymentSessions.externalReference, ref)))
141
+ .limit(1);
142
+ if (!session)
143
+ return c.json({ error: "Payment session not found" }, 404);
144
+ return c.json({ data: { sessionId: session.id } });
145
+ });
146
+ hono.post("/v1/public/payment-link/:sessionId/start-card", async (c) => {
147
+ const sessionId = requireRouteParam(c, "sessionId");
148
+ if (sessionId instanceof Response)
149
+ return sessionId;
150
+ const db = getDb(c);
151
+ const [session] = await db
152
+ .select()
153
+ .from(paymentSessions)
154
+ .where(eq(paymentSessions.id, sessionId))
155
+ .limit(1);
156
+ if (!session)
157
+ return c.json({ error: "Session not found" }, 404);
158
+ if (session.redirectUrl) {
159
+ return c.json({ data: { redirectUrl: session.redirectUrl } });
160
+ }
161
+ try {
162
+ const started = await options.startCardPayment(c, {
163
+ id: session.id,
164
+ payerName: session.payerName,
165
+ payerEmail: session.payerEmail,
166
+ notes: session.notes,
167
+ redirectUrl: session.redirectUrl,
168
+ });
169
+ if (!started.configured) {
170
+ return c.json({ error: "Card processor not configured" }, 503);
171
+ }
172
+ return c.json({ data: { redirectUrl: started.redirectUrl } });
173
+ }
174
+ catch (err) {
175
+ const message = err instanceof Error ? err.message : "Failed to start card payment";
176
+ return c.json({ error: message }, 502);
177
+ }
178
+ });
179
+ hono.get("/v1/public/payment-link/:sessionId/trip-summary", async (c) => {
180
+ const sessionId = requireRouteParam(c, "sessionId");
181
+ if (sessionId instanceof Response)
182
+ return sessionId;
183
+ const db = getDb(c);
184
+ const [session] = await db
185
+ .select()
186
+ .from(paymentSessions)
187
+ .where(eq(paymentSessions.id, sessionId))
188
+ .limit(1);
189
+ if (!session)
190
+ return c.json({ error: "Session not found" }, 404);
191
+ const metadata = (session.metadata ?? {});
192
+ const tripEnvelopeId = typeof metadata.tripEnvelopeId === "string" ? metadata.tripEnvelopeId : null;
193
+ if (!tripEnvelopeId)
194
+ return c.json({ data: null });
195
+ const tripData = await options.resolveTripData(c, tripEnvelopeId, {
196
+ id: session.id,
197
+ status: session.status,
198
+ amountCents: session.amountCents,
199
+ currency: session.currency,
200
+ provider: session.provider,
201
+ });
202
+ if (!tripData)
203
+ return c.json({ data: null });
204
+ const { envelope, components: visibleComponents, productNameById, mediaByProductId } = tripData;
205
+ const allocationsRaw = Array.isArray(metadata.componentAllocations)
206
+ ? metadata.componentAllocations
207
+ : [];
208
+ const allocationByComponentId = new Map();
209
+ for (const allocation of allocationsRaw) {
210
+ if (allocation.componentId)
211
+ allocationByComponentId.set(allocation.componentId, allocation);
212
+ }
213
+ const trip = {
214
+ envelopeId: envelope.id,
215
+ currency: session.currency,
216
+ totalAmountCents: session.amountCents,
217
+ components: visibleComponents.map((component) => {
218
+ const metadataRecord = (component.metadata ?? {});
219
+ const catalogItem = (metadataRecord.catalogItem ?? null);
220
+ const flightDraft = (metadataRecord.flightDraft ?? null);
221
+ const schedule = resolvePublicTripComponentSchedule(metadataRecord);
222
+ const fallbackName = catalogItem?.name ??
223
+ (component.entityId ? productNameById.get(component.entityId) : null) ??
224
+ (flightDraft?.origin && flightDraft?.destination
225
+ ? `${flightDraft.origin} -> ${flightDraft.destination}`
226
+ : null) ??
227
+ component.description ??
228
+ component.kind.replaceAll("_", " ");
229
+ const thumbnail = component.entityId
230
+ ? (mediaByProductId.get(component.entityId) ?? null)
231
+ : null;
232
+ const catalogThumbnailUrl = publicStringValue(readPublicRecord(metadataRecord.catalogItem)?.thumbnailUrl);
233
+ const allocation = allocationByComponentId.get(component.id);
234
+ return {
235
+ id: component.id,
236
+ kind: component.kind,
237
+ entityModule: component.entityModule,
238
+ title: fallbackName,
239
+ thumbnailUrl: thumbnail?.url ?? catalogThumbnailUrl ?? null,
240
+ thumbnailAlt: thumbnail?.altText ?? null,
241
+ scheduledStartsAt: schedule.start,
242
+ scheduledEndsAt: schedule.end,
243
+ sourceAmountCents: allocation?.sourceAmountCents ?? component.componentTotalAmountCents ?? null,
244
+ sourceCurrency: allocation?.sourceCurrency ?? component.componentCurrency ?? session.currency,
245
+ targetAmountCents: allocation?.targetAmountCents ?? component.componentTotalAmountCents ?? null,
246
+ targetCurrency: allocation?.targetCurrency ?? session.currency,
247
+ fx: allocation?.fx ?? null,
248
+ };
249
+ }),
250
+ };
251
+ return c.json({ data: trip });
252
+ });
253
+ hono.get("/v1/public/payment-link/:sessionId/booking-summary", async (c) => {
254
+ const sessionId = requireRouteParam(c, "sessionId");
255
+ if (sessionId instanceof Response)
256
+ return sessionId;
257
+ const db = getDb(c);
258
+ const [session] = await db
259
+ .select({
260
+ id: paymentSessions.id,
261
+ bookingId: paymentSessions.bookingId,
262
+ amountCents: paymentSessions.amountCents,
263
+ currency: paymentSessions.currency,
264
+ metadata: paymentSessions.metadata,
265
+ })
266
+ .from(paymentSessions)
267
+ .where(eq(paymentSessions.id, sessionId))
268
+ .limit(1);
269
+ if (!session)
270
+ return c.json({ error: "Session not found" }, 404);
271
+ const metadata = (session.metadata ?? {});
272
+ if (typeof metadata.tripEnvelopeId === "string" && metadata.tripEnvelopeId.length > 0) {
273
+ return c.json({ data: null });
274
+ }
275
+ if (!session.bookingId)
276
+ return c.json({ data: null });
277
+ const [booking] = await db
278
+ .select({
279
+ id: bookings.id,
280
+ bookingNumber: bookings.bookingNumber,
281
+ status: bookings.status,
282
+ sellCurrency: bookings.sellCurrency,
283
+ sellAmountCents: bookings.sellAmountCents,
284
+ pax: bookings.pax,
285
+ startDate: bookings.startDate,
286
+ endDate: bookings.endDate,
287
+ })
288
+ .from(bookings)
289
+ .where(eq(bookings.id, session.bookingId))
290
+ .limit(1);
291
+ if (!booking)
292
+ return c.json({ data: null });
293
+ const items = await db
294
+ .select({
295
+ id: bookingItems.id,
296
+ title: bookingItems.title,
297
+ itemType: bookingItems.itemType,
298
+ quantity: bookingItems.quantity,
299
+ totalSellAmountCents: bookingItems.totalSellAmountCents,
300
+ sellCurrency: bookingItems.sellCurrency,
301
+ startsAt: bookingItems.startsAt,
302
+ endsAt: bookingItems.endsAt,
303
+ serviceDate: bookingItems.serviceDate,
304
+ productNameSnapshot: bookingItems.productNameSnapshot,
305
+ optionNameSnapshot: bookingItems.optionNameSnapshot,
306
+ unitNameSnapshot: bookingItems.unitNameSnapshot,
307
+ departureLabelSnapshot: bookingItems.departureLabelSnapshot,
308
+ })
309
+ .from(bookingItems)
310
+ .where(eq(bookingItems.bookingId, booking.id))
311
+ .orderBy(asc(bookingItems.createdAt));
312
+ return c.json({
313
+ data: {
314
+ bookingId: booking.id,
315
+ bookingNumber: booking.bookingNumber,
316
+ status: booking.status,
317
+ pax: booking.pax,
318
+ startDate: booking.startDate,
319
+ endDate: booking.endDate,
320
+ chargeAmountCents: session.amountCents,
321
+ currency: session.currency ?? booking.sellCurrency,
322
+ bookingTotalAmountCents: booking.sellAmountCents,
323
+ bookingCurrency: booking.sellCurrency,
324
+ items: items.map((item) => ({
325
+ id: item.id,
326
+ productName: item.productNameSnapshot ?? item.title,
327
+ optionName: item.optionNameSnapshot,
328
+ unitName: item.unitNameSnapshot,
329
+ departureLabel: item.departureLabelSnapshot,
330
+ startsAt: item.startsAt instanceof Date ? item.startsAt.toISOString() : item.startsAt,
331
+ endsAt: item.endsAt instanceof Date ? item.endsAt.toISOString() : item.endsAt,
332
+ serviceDate: item.serviceDate,
333
+ quantity: item.quantity,
334
+ itemType: item.itemType,
335
+ amountCents: item.totalSellAmountCents,
336
+ currency: item.sellCurrency,
337
+ })),
338
+ },
339
+ });
340
+ });
341
+ hono.get("/v1/public/bookings/:bookingId/checkout-status", async (c) => {
342
+ const bookingId = requireRouteParam(c, "bookingId");
343
+ if (bookingId instanceof Response)
344
+ return bookingId;
345
+ const ref = c.req.query("session") ?? c.req.query("orderId") ?? c.req.query("ref") ?? null;
346
+ const db = getDb(c);
347
+ const [booking] = await db
348
+ .select({
349
+ id: bookings.id,
350
+ bookingNumber: bookings.bookingNumber,
351
+ status: bookings.status,
352
+ updatedAt: bookings.updatedAt,
353
+ })
354
+ .from(bookings)
355
+ .where(eq(bookings.id, bookingId))
356
+ .limit(1);
357
+ if (!booking)
358
+ return c.json({ error: "Booking not found" }, 404);
359
+ const sessionRefFilter = ref
360
+ ? or(eq(paymentSessions.id, ref), eq(paymentSessions.clientReference, ref), eq(paymentSessions.externalReference, ref), eq(paymentSessions.providerSessionId, ref), eq(paymentSessions.providerPaymentId, ref))
361
+ : undefined;
362
+ const sessionWhere = sessionRefFilter
363
+ ? and(eq(paymentSessions.bookingId, bookingId), sessionRefFilter)
364
+ : eq(paymentSessions.bookingId, bookingId);
365
+ let sessions = await db
366
+ .select({
367
+ id: paymentSessions.id,
368
+ status: paymentSessions.status,
369
+ amountCents: paymentSessions.amountCents,
370
+ currency: paymentSessions.currency,
371
+ invoiceId: paymentSessions.invoiceId,
372
+ paymentMethod: paymentSessions.paymentMethod,
373
+ completedAt: paymentSessions.completedAt,
374
+ failedAt: paymentSessions.failedAt,
375
+ updatedAt: paymentSessions.updatedAt,
376
+ })
377
+ .from(paymentSessions)
378
+ .where(sessionWhere)
379
+ .orderBy(desc(paymentSessions.createdAt))
380
+ .limit(5);
381
+ if (sessions.length === 0 && ref) {
382
+ sessions = await db
383
+ .select({
384
+ id: paymentSessions.id,
385
+ status: paymentSessions.status,
386
+ amountCents: paymentSessions.amountCents,
387
+ currency: paymentSessions.currency,
388
+ invoiceId: paymentSessions.invoiceId,
389
+ paymentMethod: paymentSessions.paymentMethod,
390
+ completedAt: paymentSessions.completedAt,
391
+ failedAt: paymentSessions.failedAt,
392
+ updatedAt: paymentSessions.updatedAt,
393
+ })
394
+ .from(paymentSessions)
395
+ .where(eq(paymentSessions.bookingId, bookingId))
396
+ .orderBy(desc(paymentSessions.createdAt))
397
+ .limit(5);
398
+ }
399
+ const paidSession = sessions.find((session) => session.status === "paid" || session.status === "authorized");
400
+ const latestSession = paidSession ?? sessions[0] ?? null;
401
+ const isBankTransferSession = latestSession?.paymentMethod === "bank_transfer" ||
402
+ (booking.status === "awaiting_payment" && Boolean(latestSession?.invoiceId));
403
+ const bankTransferInstructions = isBankTransferSession && latestSession
404
+ ? await buildPublicBankTransferInstructions(c, options, booking.bookingNumber, latestSession)
405
+ : null;
406
+ const failedStatuses = new Set(["failed", "cancelled", "expired"]);
407
+ const paymentStatus = booking.status === "confirmed" || paidSession
408
+ ? "paid"
409
+ : sessions.length > 0 && sessions.every((session) => failedStatuses.has(session.status))
410
+ ? "failed"
411
+ : "pending";
412
+ return c.json({
413
+ data: {
414
+ bookingId: booking.id,
415
+ bookingNumber: booking.bookingNumber,
416
+ bookingStatus: booking.status,
417
+ paymentStatus,
418
+ session: latestSession,
419
+ bankTransferInstructions,
420
+ updatedAt: (latestSession?.updatedAt ?? booking.updatedAt)?.toISOString?.() ?? null,
421
+ },
422
+ });
423
+ });
424
+ return hono;
425
+ }
426
+ // ─────────────────────────────────────────────────────────────────
427
+ // Pure schedule resolution helpers
428
+ // ─────────────────────────────────────────────────────────────────
429
+ function resolvePublicTripComponentSchedule(metadata) {
430
+ const scheduledStart = publicStringValue(metadata.scheduledStartsAt);
431
+ const scheduledEnd = publicStringValue(metadata.scheduledEndsAt);
432
+ if (scheduledStart)
433
+ return { start: scheduledStart, end: scheduledEnd };
434
+ const flightDraft = readPublicRecord(metadata.flightDraft);
435
+ if (flightDraft) {
436
+ const selectedOffer = readPublicRecord(flightDraft.selectedOffer);
437
+ const itineraries = Array.isArray(selectedOffer?.itineraries) ? selectedOffer.itineraries : [];
438
+ const firstItinerary = readPublicRecord(itineraries[0]);
439
+ const lastItinerary = readPublicRecord(itineraries[itineraries.length - 1]);
440
+ const firstSegments = Array.isArray(firstItinerary?.segments) ? firstItinerary.segments : [];
441
+ const lastSegments = Array.isArray(lastItinerary?.segments) ? lastItinerary.segments : [];
442
+ const firstSegment = readPublicRecord(firstSegments[0]);
443
+ const lastSegment = readPublicRecord(lastSegments[lastSegments.length - 1]);
444
+ const departure = readPublicRecord(firstSegment?.departure);
445
+ const arrival = readPublicRecord(lastSegment?.arrival);
446
+ return {
447
+ start: publicStringValue(departure?.at) ?? publicStringValue(flightDraft.departDate),
448
+ end: publicStringValue(arrival?.at) ?? publicStringValue(flightDraft.returnDate),
449
+ };
450
+ }
451
+ const bookingDraft = readPublicRecord(metadata.bookingDraftV1);
452
+ const configure = readPublicRecord(bookingDraft?.configure);
453
+ const dateRange = readPublicRecord(configure?.dateRange);
454
+ const departureDate = publicStringValue(configure?.departureDate);
455
+ const checkIn = publicStringValue(dateRange?.checkIn);
456
+ const checkOut = publicStringValue(dateRange?.checkOut);
457
+ if (departureDate || checkIn) {
458
+ return { start: departureDate ?? checkIn, end: checkOut };
459
+ }
460
+ const cruiseDraft = readPublicRecord(metadata.cruiseDraft);
461
+ const embarkationDate = publicStringValue(cruiseDraft?.embarkationDate);
462
+ if (embarkationDate)
463
+ return { start: embarkationDate, end: null };
464
+ return { start: null, end: null };
465
+ }
466
+ function readPublicRecord(value) {
467
+ return value && typeof value === "object" && !Array.isArray(value)
468
+ ? value
469
+ : null;
470
+ }
471
+ function publicStringValue(value) {
472
+ return typeof value === "string" && value.trim() ? value.trim() : null;
473
+ }
@@ -126,7 +126,7 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
126
126
  error: string;
127
127
  };
128
128
  outputFormat: "json";
129
- status: 429 | 400 | 403;
129
+ status: 400 | 403 | 429;
130
130
  } | {
131
131
  input: {};
132
132
  output: {
@@ -134,7 +134,7 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
134
134
  id: string;
135
135
  personId: string;
136
136
  kind: "notify" | "wishlist" | "inquiry" | "request_offer" | "referral";
137
- source: "admin" | "phone" | "booking" | "form" | "website" | "abandoned_cart";
137
+ source: "admin" | "form" | "phone" | "booking" | "website" | "abandoned_cart";
138
138
  status: "expired" | "new" | "contacted" | "qualified" | "converted" | "lost";
139
139
  duplicate: boolean;
140
140
  };
@@ -151,7 +151,7 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
151
151
  error: string;
152
152
  };
153
153
  outputFormat: "json";
154
- status: 429 | 400 | 403;
154
+ status: 400 | 403 | 429;
155
155
  } | {
156
156
  input: {};
157
157
  output: {
@@ -159,7 +159,7 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
159
159
  id: string;
160
160
  personId: string;
161
161
  kind: "notify" | "wishlist" | "inquiry" | "request_offer" | "referral";
162
- source: "admin" | "phone" | "booking" | "form" | "website" | "abandoned_cart";
162
+ source: "admin" | "form" | "phone" | "booking" | "website" | "abandoned_cart";
163
163
  status: "expired" | "new" | "contacted" | "qualified" | "converted" | "lost";
164
164
  duplicate: boolean;
165
165
  doubleOptIn: "requested" | "not_configured";
@@ -1278,6 +1278,17 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
1278
1278
  } & {
1279
1279
  "/offers/:slug": {
1280
1280
  $get: {
1281
+ input: {
1282
+ param: {
1283
+ slug: string;
1284
+ };
1285
+ };
1286
+ output: {
1287
+ error: string;
1288
+ };
1289
+ outputFormat: "json";
1290
+ status: 404;
1291
+ } | {
1281
1292
  input: {
1282
1293
  param: {
1283
1294
  slug: string;
@@ -1306,17 +1317,6 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
1306
1317
  };
1307
1318
  outputFormat: "json";
1308
1319
  status: import("hono/utils/http-status").ContentfulStatusCode;
1309
- } | {
1310
- input: {
1311
- param: {
1312
- slug: string;
1313
- };
1314
- };
1315
- output: {
1316
- error: string;
1317
- };
1318
- outputFormat: "json";
1319
- status: 404;
1320
1320
  };
1321
1321
  };
1322
1322
  } & {
@@ -1 +1 @@
1
- {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,9 @@ import { storefrontTransportEligibilityInputSchema } from "./validation-transpor
15
15
  * to success responses only.
16
16
  */
17
17
  const PUBLIC_CACHE_CONTROL = "public, s-maxage=60, stale-while-revalidate=300";
18
+ function setPublicCacheHeaders(c) {
19
+ c.header("Cache-Control", PUBLIC_CACHE_CONTROL);
20
+ }
18
21
  /**
19
22
  * KV read-model TTL for the departure list (RFC voyant#1687 Phase 2.2).
20
23
  * Departure availability shifts with every booking, so unlike the
@@ -144,14 +147,14 @@ export function createStorefrontPublicRoutes(options) {
144
147
  const departure = await storefrontService.getDeparture(c.get("db"), c.req.param("departureId"));
145
148
  if (!departure)
146
149
  return c.json({ error: "Storefront departure not found" }, 404);
147
- c.header("Cache-Control", PUBLIC_CACHE_CONTROL);
150
+ setPublicCacheHeaders(c);
148
151
  return c.json({ data: departure });
149
152
  })
150
153
  .get("/products/:productId/departures", async (c) => {
151
154
  const productId = c.req.param("productId");
152
155
  const query = await parseQuery(c, storefrontDepartureListQuerySchema);
153
156
  const result = await readThroughDepartures(c, departuresDocKey(productId, query), () => storefrontService.listProductDepartures(c.get("db"), productId, query));
154
- c.header("Cache-Control", PUBLIC_CACHE_CONTROL);
157
+ setPublicCacheHeaders(c);
155
158
  return c.json(result);
156
159
  })
157
160
  .post("/departures/:departureId/price", async (c) => {
@@ -301,14 +304,14 @@ export function createStorefrontPublicRoutes(options) {
301
304
  })
302
305
  .get("/products/:productId/extensions", async (c) => {
303
306
  const query = await parseQuery(c, storefrontProductExtensionsQuerySchema);
304
- return c.json({
305
- data: await storefrontService.getProductExtensions(c.get("db"), c.req.param("productId"), query.optionId),
306
- });
307
+ const extensions = await storefrontService.getProductExtensions(c.get("db"), c.req.param("productId"), query.optionId);
308
+ setPublicCacheHeaders(c);
309
+ return c.json({ data: extensions });
307
310
  })
308
311
  .get("/products/:productId/availability", async (c) => {
309
- return c.json({
310
- data: await storefrontService.getProductAvailabilitySummary(c.get("db"), c.req.param("productId"), await parseQuery(c, storefrontProductAvailabilitySummaryQuerySchema)),
311
- });
312
+ const availability = await storefrontService.getProductAvailabilitySummary(c.get("db"), c.req.param("productId"), await parseQuery(c, storefrontProductAvailabilitySummaryQuerySchema));
313
+ setPublicCacheHeaders(c);
314
+ return c.json({ data: availability });
312
315
  })
313
316
  .get("/products/:productId/departures/:departureId/itinerary", async (c) => {
314
317
  const itinerary = await storefrontService.getDepartureItinerary(c.get("db"), {
@@ -317,19 +320,19 @@ export function createStorefrontPublicRoutes(options) {
317
320
  });
318
321
  if (!itinerary)
319
322
  return c.json({ error: "Storefront itinerary not found" }, 404);
320
- c.header("Cache-Control", PUBLIC_CACHE_CONTROL);
323
+ setPublicCacheHeaders(c);
321
324
  return c.json({ data: itinerary });
322
325
  })
323
326
  .get("/products/:productId/offers", async (c) => {
324
327
  const query = await parseQuery(c, storefrontPromotionalOfferListQuerySchema);
325
- return c.json({
326
- data: await storefrontService.listApplicableOffers({
327
- productId: c.req.param("productId"),
328
- departureId: query.departureId,
329
- locale: query.locale,
330
- context: getRequestContext(c),
331
- }),
328
+ const offers = await storefrontService.listApplicableOffers({
329
+ productId: c.req.param("productId"),
330
+ departureId: query.departureId,
331
+ locale: query.locale,
332
+ context: getRequestContext(c),
332
333
  });
334
+ setPublicCacheHeaders(c);
335
+ return c.json({ data: offers });
333
336
  })
334
337
  .get("/offers/:slug", async (c) => {
335
338
  const query = await parseQuery(c, storefrontPromotionalOfferListQuerySchema);
@@ -338,7 +341,10 @@ export function createStorefrontPublicRoutes(options) {
338
341
  locale: query.locale,
339
342
  context: getRequestContext(c),
340
343
  });
341
- return offer ? c.json({ data: offer }) : c.json({ error: "Storefront offer not found" }, 404);
344
+ if (!offer)
345
+ return c.json({ error: "Storefront offer not found" }, 404);
346
+ setPublicCacheHeaders(c);
347
+ return c.json({ data: offer });
342
348
  })
343
349
  .post("/offers/:slug/apply", async (c) => {
344
350
  const result = await storefrontService.applyOffer({
package/dist/service.d.ts CHANGED
@@ -1041,7 +1041,7 @@ export declare function createStorefrontService(options?: StorefrontServiceOptio
1041
1041
  id: string;
1042
1042
  personId: string;
1043
1043
  kind: "notify" | "wishlist" | "inquiry" | "request_offer" | "referral";
1044
- source: "admin" | "phone" | "booking" | "form" | "website" | "abandoned_cart";
1044
+ source: "admin" | "form" | "phone" | "booking" | "website" | "abandoned_cart";
1045
1045
  status: "expired" | "new" | "contacted" | "qualified" | "converted" | "lost";
1046
1046
  duplicate: boolean;
1047
1047
  }>;
@@ -1052,7 +1052,7 @@ export declare function createStorefrontService(options?: StorefrontServiceOptio
1052
1052
  id: string;
1053
1053
  personId: string;
1054
1054
  kind: "notify" | "wishlist" | "inquiry" | "request_offer" | "referral";
1055
- source: "admin" | "phone" | "booking" | "form" | "website" | "abandoned_cart";
1055
+ source: "admin" | "form" | "phone" | "booking" | "website" | "abandoned_cart";
1056
1056
  status: "expired" | "new" | "contacted" | "qualified" | "converted" | "lost";
1057
1057
  duplicate: boolean;
1058
1058
  doubleOptIn: "requested" | "not_configured";
@@ -23,9 +23,9 @@ export declare const storefrontLeadIntakeInputSchema: z.ZodObject<{
23
23
  }>>;
24
24
  source: z.ZodDefault<z.ZodEnum<{
25
25
  admin: "admin";
26
+ form: "form";
26
27
  phone: "phone";
27
28
  booking: "booking";
28
- form: "form";
29
29
  website: "website";
30
30
  abandoned_cart: "abandoned_cart";
31
31
  }>>;
@@ -59,9 +59,9 @@ export declare const storefrontNewsletterSubscribeInputSchema: z.ZodObject<{
59
59
  lastName: z.ZodOptional<z.ZodString>;
60
60
  source: z.ZodDefault<z.ZodEnum<{
61
61
  admin: "admin";
62
+ form: "form";
62
63
  phone: "phone";
63
64
  booking: "booking";
64
- form: "form";
65
65
  website: "website";
66
66
  abandoned_cart: "abandoned_cart";
67
67
  }>>;
@@ -90,9 +90,9 @@ export declare const storefrontIntakeResponseSchema: z.ZodObject<{
90
90
  }>;
91
91
  source: z.ZodEnum<{
92
92
  admin: "admin";
93
+ form: "form";
93
94
  phone: "phone";
94
95
  booking: "booking";
95
- form: "form";
96
96
  website: "website";
97
97
  abandoned_cart: "abandoned_cart";
98
98
  }>;
@@ -118,9 +118,9 @@ export declare const storefrontNewsletterSubscribeResponseSchema: z.ZodObject<{
118
118
  }>;
119
119
  source: z.ZodEnum<{
120
120
  admin: "admin";
121
+ form: "form";
121
122
  phone: "phone";
122
123
  booking: "booking";
123
- form: "form";
124
124
  website: "website";
125
125
  abandoned_cart: "abandoned_cart";
126
126
  }>;
@@ -46,7 +46,7 @@ export declare function createStorefrontVerificationPublicRoutes(options?: Store
46
46
  error: string;
47
47
  };
48
48
  outputFormat: "json";
49
- status: 404 | 400 | 409 | 410 | 501;
49
+ status: 400 | 404 | 409 | 410 | 501;
50
50
  } | {
51
51
  input: {};
52
52
  output: {
@@ -95,7 +95,7 @@ export declare function createStorefrontVerificationPublicRoutes(options?: Store
95
95
  error: string;
96
96
  };
97
97
  outputFormat: "json";
98
- status: 404 | 400 | 409 | 410 | 501;
98
+ status: 400 | 404 | 409 | 410 | 501;
99
99
  } | {
100
100
  input: {};
101
101
  output: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/storefront",
3
- "version": "0.121.1",
3
+ "version": "0.122.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -34,6 +34,11 @@
34
34
  "import": "./dist/customer-portal/routes.js",
35
35
  "default": "./dist/customer-portal/routes.js"
36
36
  },
37
+ "./payment-link": {
38
+ "types": "./dist/payment-link/routes.d.ts",
39
+ "import": "./dist/payment-link/routes.js",
40
+ "default": "./dist/payment-link/routes.js"
41
+ },
37
42
  "./public-routes": {
38
43
  "types": "./dist/routes-public.d.ts",
39
44
  "import": "./dist/routes-public.js",
@@ -90,19 +95,19 @@
90
95
  "drizzle-orm": "^0.45.2",
91
96
  "hono": "^4.12.10",
92
97
  "zod": "^4.3.6",
93
- "@voyant-travel/bookings": "^0.120.1",
94
- "@voyant-travel/commerce": "^0.2.2",
95
- "@voyant-travel/db": "^0.108.1",
96
- "@voyant-travel/finance": "^0.120.1",
97
98
  "@voyant-travel/core": "^0.109.0",
98
- "@voyant-travel/hono": "^0.110.2",
99
+ "@voyant-travel/bookings": "^0.121.0",
100
+ "@voyant-travel/db": "^0.108.1",
101
+ "@voyant-travel/commerce": "^0.3.0",
102
+ "@voyant-travel/finance": "^0.121.0",
103
+ "@voyant-travel/hono": "^0.111.0",
99
104
  "@voyant-travel/utils": "^0.105.2",
100
105
  "@voyant-travel/relationships-contracts": "^0.107.0"
101
106
  },
102
107
  "peerDependencies": {
103
- "@voyant-travel/identity": "^0.120.1",
104
- "@voyant-travel/legal": "^0.120.1",
105
- "@voyant-travel/relationships": "^0.119.3"
108
+ "@voyant-travel/legal": "^0.121.0",
109
+ "@voyant-travel/relationships": "^0.119.4",
110
+ "@voyant-travel/identity": "^0.121.0"
106
111
  },
107
112
  "peerDependenciesMeta": {
108
113
  "@voyant-travel/identity": {
@@ -118,12 +123,12 @@
118
123
  "devDependencies": {
119
124
  "typescript": "^6.0.2",
120
125
  "vitest": "^4.1.2",
121
- "@voyant-travel/identity": "^0.120.1",
122
- "@voyant-travel/inventory": "^0.2.0",
123
- "@voyant-travel/legal": "^0.120.1",
124
- "@voyant-travel/operations": "^0.1.0",
125
- "@voyant-travel/relationships": "^0.119.3",
126
- "@voyant-travel/voyant-typescript-config": "^0.1.0"
126
+ "@voyant-travel/identity": "^0.121.0",
127
+ "@voyant-travel/operations": "^0.1.1",
128
+ "@voyant-travel/legal": "^0.121.0",
129
+ "@voyant-travel/voyant-typescript-config": "^0.1.0",
130
+ "@voyant-travel/relationships": "^0.119.4",
131
+ "@voyant-travel/inventory": "^0.3.0"
127
132
  },
128
133
  "files": [
129
134
  "dist"