@voyant-travel/storefront 0.121.2 → 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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/payment-link/routes.d.ts +97 -0
- package/dist/payment-link/routes.d.ts.map +1 -0
- package/dist/payment-link/routes.js +473 -0
- package/dist/routes-public.d.ts +4 -4
- package/dist/service.d.ts +2 -2
- package/dist/validation/intake.d.ts +4 -4
- package/dist/verification/routes-public.d.ts +2 -2
- package/package.json +21 -16
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":"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"}
|
|
@@ -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
|
+
}
|
package/dist/routes-public.d.ts
CHANGED
|
@@ -126,7 +126,7 @@ export declare function createStorefrontPublicRoutes(options?: StorefrontService
|
|
|
126
126
|
error: string;
|
|
127
127
|
};
|
|
128
128
|
outputFormat: "json";
|
|
129
|
-
status:
|
|
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" | "
|
|
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:
|
|
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" | "
|
|
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";
|
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" | "
|
|
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" | "
|
|
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:
|
|
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:
|
|
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.
|
|
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.2",
|
|
94
|
-
"@voyant-travel/commerce": "^0.2.3",
|
|
95
98
|
"@voyant-travel/core": "^0.109.0",
|
|
99
|
+
"@voyant-travel/bookings": "^0.121.0",
|
|
96
100
|
"@voyant-travel/db": "^0.108.1",
|
|
97
|
-
"@voyant-travel/
|
|
98
|
-
"@voyant-travel/
|
|
99
|
-
"@voyant-travel/
|
|
100
|
-
"@voyant-travel/utils": "^0.105.2"
|
|
101
|
+
"@voyant-travel/commerce": "^0.3.0",
|
|
102
|
+
"@voyant-travel/finance": "^0.121.0",
|
|
103
|
+
"@voyant-travel/hono": "^0.111.0",
|
|
104
|
+
"@voyant-travel/utils": "^0.105.2",
|
|
105
|
+
"@voyant-travel/relationships-contracts": "^0.107.0"
|
|
101
106
|
},
|
|
102
107
|
"peerDependencies": {
|
|
103
|
-
"@voyant-travel/
|
|
104
|
-
"@voyant-travel/
|
|
105
|
-
"@voyant-travel/
|
|
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.
|
|
122
|
-
"@voyant-travel/
|
|
123
|
-
"@voyant-travel/legal": "^0.
|
|
124
|
-
"@voyant-travel/
|
|
125
|
-
"@voyant-travel/
|
|
126
|
-
"@voyant-travel/
|
|
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"
|