@voyant-travel/commerce 0.2.2 → 0.3.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.
Files changed (45) hide show
  1. package/dist/checkout/acceptance-signature.d.ts +4 -0
  2. package/dist/checkout/acceptance-signature.d.ts.map +1 -0
  3. package/dist/checkout/acceptance-signature.js +95 -0
  4. package/dist/checkout/finalize.d.ts +42 -0
  5. package/dist/checkout/finalize.d.ts.map +1 -0
  6. package/dist/checkout/finalize.js +208 -0
  7. package/dist/checkout/index.d.ts +26 -0
  8. package/dist/checkout/index.d.ts.map +1 -0
  9. package/dist/checkout/index.js +24 -0
  10. package/dist/checkout/materialization-support.d.ts +105 -0
  11. package/dist/checkout/materialization-support.d.ts.map +1 -0
  12. package/dist/checkout/materialization-support.js +451 -0
  13. package/dist/checkout/materialization-support.test.d.ts +2 -0
  14. package/dist/checkout/materialization-support.test.d.ts.map +1 -0
  15. package/dist/checkout/materialization-support.test.js +196 -0
  16. package/dist/checkout/materialization-tax.d.ts +10 -0
  17. package/dist/checkout/materialization-tax.d.ts.map +1 -0
  18. package/dist/checkout/materialization-tax.js +113 -0
  19. package/dist/checkout/materialization-tax.test.d.ts +2 -0
  20. package/dist/checkout/materialization-tax.test.d.ts.map +1 -0
  21. package/dist/checkout/materialization-tax.test.js +69 -0
  22. package/dist/checkout/materialization.d.ts +99 -0
  23. package/dist/checkout/materialization.d.ts.map +1 -0
  24. package/dist/checkout/materialization.js +269 -0
  25. package/dist/checkout/options.d.ts +89 -0
  26. package/dist/checkout/options.d.ts.map +1 -0
  27. package/dist/checkout/options.js +21 -0
  28. package/dist/checkout/routes.d.ts +21 -0
  29. package/dist/checkout/routes.d.ts.map +1 -0
  30. package/dist/checkout/routes.js +59 -0
  31. package/dist/checkout/start-service.d.ts +75 -0
  32. package/dist/checkout/start-service.d.ts.map +1 -0
  33. package/dist/checkout/start-service.js +415 -0
  34. package/dist/checkout/start-service.test.d.ts +2 -0
  35. package/dist/checkout/start-service.test.d.ts.map +1 -0
  36. package/dist/checkout/start-service.test.js +57 -0
  37. package/dist/markets/routes.d.ts +1 -1
  38. package/dist/markets/service-core.d.ts +1 -1
  39. package/dist/pricing/routes-public.d.ts.map +1 -1
  40. package/dist/pricing/routes-public.js +12 -2
  41. package/dist/sellability/routes.d.ts +10 -10
  42. package/dist/sellability/service-records.d.ts +4 -4
  43. package/dist/sellability/service-snapshots.d.ts +2 -2
  44. package/dist/sellability/service.d.ts +10 -10
  45. package/package.json +28 -6
@@ -0,0 +1,4 @@
1
+ import type { EventBus } from "@voyant-travel/core";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ export declare function persistAcceptanceSignature(db: PostgresJsDatabase, contractId: string, eventBus?: EventBus): Promise<void>;
4
+ //# sourceMappingURL=acceptance-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acceptance-signature.d.ts","sourceRoot":"","sources":["../../src/checkout/acceptance-signature.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAoCjE,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,IAAI,CAAC,CAoFf"}
@@ -0,0 +1,95 @@
1
+ import { bookings } from "@voyant-travel/bookings/schema";
2
+ import { eq } from "drizzle-orm";
3
+ const ACCEPTANCE_MARKER_PREFIX = "__contract_acceptance__:";
4
+ function readContractAcceptance(contractMetadata, internalNotesFallback) {
5
+ if (contractMetadata && typeof contractMetadata === "object") {
6
+ const meta = contractMetadata;
7
+ if (meta.acceptance && typeof meta.acceptance === "object") {
8
+ return meta.acceptance;
9
+ }
10
+ }
11
+ if (!internalNotesFallback)
12
+ return null;
13
+ for (const line of internalNotesFallback.split("\n")) {
14
+ if (!line.startsWith(ACCEPTANCE_MARKER_PREFIX))
15
+ continue;
16
+ try {
17
+ return JSON.parse(line.slice(ACCEPTANCE_MARKER_PREFIX.length));
18
+ }
19
+ catch {
20
+ // Bad marker - try next line.
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ export async function persistAcceptanceSignature(db, contractId, eventBus) {
26
+ const { contractsService } = await import("@voyant-travel/legal/contracts");
27
+ const { contracts: contractsTable } = await import("@voyant-travel/legal/contracts");
28
+ const [contract] = await db
29
+ .select()
30
+ .from(contractsTable)
31
+ .where(eq(contractsTable.id, contractId))
32
+ .limit(1);
33
+ if (!contract?.bookingId)
34
+ return;
35
+ const [booking] = await db
36
+ .select()
37
+ .from(bookings)
38
+ .where(eq(bookings.id, contract.bookingId))
39
+ .limit(1);
40
+ if (!booking)
41
+ return;
42
+ const acceptance = readContractAcceptance(contract.metadata, booking.internalNotes);
43
+ if (!acceptance)
44
+ return;
45
+ const existing = await contractsService.listSignatures(db, contractId);
46
+ if (existing.length > 0)
47
+ return;
48
+ if (contract.status === "issued") {
49
+ const sent = await contractsService.sendContract(db, contractId, { eventBus });
50
+ if (sent.status !== "sent") {
51
+ console.warn(`[catalog-checkout] could not send contract before acceptance signature for ${contractId}: ${sent.status}`);
52
+ return;
53
+ }
54
+ }
55
+ const contactName = [booking.contactFirstName, booking.contactLastName]
56
+ .filter(Boolean)
57
+ .join(" ")
58
+ .trim();
59
+ const signerName = contactName ||
60
+ `Storefront customer${booking.bookingNumber ? ` (${booking.bookingNumber})` : ""}`;
61
+ const result = await contractsService.signContract(db, contractId, {
62
+ signerName,
63
+ signerEmail: booking.contactEmail ?? null,
64
+ method: "electronic",
65
+ ipAddress: acceptance.clientIp ? acceptance.clientIp.slice(0, 64) : null,
66
+ userAgent: acceptance.userAgent ? acceptance.userAgent.slice(0, 500) : null,
67
+ metadata: {
68
+ source: "storefront-checkout",
69
+ templateId: acceptance.templateId,
70
+ templateSlug: acceptance.templateSlug,
71
+ acceptedAt: acceptance.acceptedAt,
72
+ acceptedMarketing: acceptance.acceptedMarketing,
73
+ renderedHtmlLength: acceptance.renderedHtmlLength,
74
+ },
75
+ }, { eventBus });
76
+ if (result.status !== "signed") {
77
+ console.warn(`[catalog-checkout] could not record acceptance signature for ${contractId}: ${result.status}`);
78
+ return;
79
+ }
80
+ if (booking.internalNotes?.includes(ACCEPTANCE_MARKER_PREFIX)) {
81
+ const cleanedNotes = booking.internalNotes
82
+ .split("\n")
83
+ .filter((line) => !line.startsWith(ACCEPTANCE_MARKER_PREFIX))
84
+ .join("\n")
85
+ .replace(/\n{3,}/g, "\n\n")
86
+ .trim();
87
+ await db
88
+ .update(bookings)
89
+ .set({
90
+ internalNotes: cleanedNotes.length > 0 ? cleanedNotes : null,
91
+ updatedAt: new Date(),
92
+ })
93
+ .where(eq(bookings.id, booking.id));
94
+ }
95
+ }
@@ -0,0 +1,42 @@
1
+ import { type CheckoutFinalizeInput } from "@voyant-travel/catalog/booking-engine";
2
+ import type { EventBus } from "@voyant-travel/core";
3
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
4
+ /**
5
+ * Optional callback that generates (or fetches existing) the contract PDF for
6
+ * a booking. Wired by the deployment and forwarded into the explicit
7
+ * `generate_contract_pdf` workflow step. The deployment supplies its
8
+ * platform bindings (`env`) when constructing it, so this package-level type
9
+ * only carries the booking-scoped inputs the step needs.
10
+ */
11
+ export type CatalogCheckoutContractPdfGenerator = (input: {
12
+ db: PostgresJsDatabase;
13
+ eventBus: EventBus;
14
+ bookingId: string;
15
+ }) => Promise<{
16
+ contractId: string;
17
+ attachmentId: string;
18
+ } | null>;
19
+ export interface DispatchCheckoutFinalizeParams {
20
+ db: PostgresJsDatabase;
21
+ eventBus: EventBus;
22
+ input: CheckoutFinalizeInput;
23
+ trigger: string;
24
+ correlationId: string | null;
25
+ tags: ReadonlyArray<string>;
26
+ parentRunId?: string | null;
27
+ triggeredByUserId?: string | null;
28
+ resumeFromStep?: string;
29
+ seedResults?: Record<string, unknown>;
30
+ generateContractPdf?: CatalogCheckoutContractPdfGenerator;
31
+ }
32
+ /**
33
+ * Run the checkout-finalize workflow for a booking: record a workflow run,
34
+ * build the finalize deps (confirm booking, issue invoice, link payments,
35
+ * generate contract PDF), execute `runCheckoutFinalize`, and mark the run
36
+ * complete/failed. The deployment owns the db + event bus + (optional)
37
+ * contract-pdf generator; this is the reusable saga driver.
38
+ */
39
+ export declare function dispatchCheckoutFinalize(params: DispatchCheckoutFinalizeParams): Promise<{
40
+ runId: string;
41
+ }>;
42
+ //# sourceMappingURL=finalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/checkout/finalize.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAInD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE;;;;;;GAMG;AACH,MAAM,MAAM,mCAAmC,GAAG,CAAC,KAAK,EAAE;IACxD,EAAE,EAAE,kBAAkB,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,KAAK,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAA;AAuKlE,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,kBAAkB,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,mBAAmB,CAAC,EAAE,mCAAmC,CAAA;CAC1D;AAMD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAgE5B"}
@@ -0,0 +1,208 @@
1
+ // agent-quality: file-size exception -- owner: commerce; the checkout-finalize
2
+ // step wiring (confirm booking, issue invoice, link payments, contract PDF) is
3
+ // one cohesive workflow definition; splitting it would scatter a single saga.
4
+ import { bookingsService } from "@voyant-travel/bookings";
5
+ import { bookingActivityLog, bookings } from "@voyant-travel/bookings/schema";
6
+ import { runCheckoutFinalize, } from "@voyant-travel/catalog/booking-engine";
7
+ import { issueInvoiceFromBooking } from "@voyant-travel/finance";
8
+ import { beginWorkflowRun } from "@voyant-travel/workflow-runs";
9
+ import { and, eq, isNull } from "drizzle-orm";
10
+ function buildCheckoutFinalizeDeps(db, eventBus, recorder, generateContractPdf) {
11
+ return {
12
+ db,
13
+ eventBus,
14
+ recorder: {
15
+ startStep: (name) => {
16
+ void recorder.startStep(name);
17
+ },
18
+ completeStep: (name, output) => {
19
+ void recorder.completeStep(name, output ?? null);
20
+ },
21
+ failStep: (name, error) => {
22
+ void recorder.failStep(name, error);
23
+ },
24
+ },
25
+ confirmBooking: async (bookingId) => {
26
+ const result = await bookingsService.confirmBooking(db, bookingId, {}, undefined, {
27
+ eventBus,
28
+ });
29
+ if (result.status === "ok")
30
+ return;
31
+ if (result.status === "hold_expired") {
32
+ const recovered = await bookingsService.recoverExpiredPaidBooking(db, bookingId, { note: "Recovered after late payment completion" }, undefined, { eventBus });
33
+ if (recovered.status === "ok")
34
+ return;
35
+ throw new Error(`checkout-finalize: late payment recovery failed (${recovered.status})`);
36
+ }
37
+ throw new Error(`checkout-finalize: booking confirmation failed (${result.status})`);
38
+ },
39
+ issueInvoice: async ({ bookingId, convertedFromInvoiceId }) => {
40
+ const [booking] = await db.select().from(bookings).where(eq(bookings.id, bookingId)).limit(1);
41
+ if (!booking)
42
+ return null;
43
+ const { bookingItems } = await import("@voyant-travel/bookings/schema");
44
+ const items = await db
45
+ .select()
46
+ .from(bookingItems)
47
+ .where(eq(bookingItems.bookingId, bookingId));
48
+ const today = new Date().toISOString().slice(0, 10);
49
+ const dueDate = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
50
+ const invoice = await issueInvoiceFromBooking(db, {
51
+ bookingId,
52
+ issueDate: today,
53
+ dueDate,
54
+ invoiceType: "invoice",
55
+ notes: convertedFromInvoiceId
56
+ ? `Converted from proforma ${convertedFromInvoiceId}`
57
+ : null,
58
+ }, {
59
+ booking: {
60
+ id: booking.id,
61
+ bookingNumber: booking.bookingNumber,
62
+ personId: booking.personId,
63
+ organizationId: booking.organizationId,
64
+ sellCurrency: booking.sellCurrency,
65
+ baseCurrency: booking.baseCurrency,
66
+ fxRateSetId: null,
67
+ sellAmountCents: booking.sellAmountCents,
68
+ baseSellAmountCents: booking.baseSellAmountCents,
69
+ },
70
+ items: items.map((item) => ({
71
+ id: item.id,
72
+ title: item.title,
73
+ quantity: item.quantity,
74
+ unitSellAmountCents: item.unitSellAmountCents,
75
+ totalSellAmountCents: item.totalSellAmountCents,
76
+ })),
77
+ }, { eventBus });
78
+ if (invoice && convertedFromInvoiceId) {
79
+ const { invoices } = await import("@voyant-travel/finance");
80
+ await db.update(invoices).set({ convertedFromInvoiceId }).where(eq(invoices.id, invoice.id));
81
+ }
82
+ return invoice ? { invoiceId: invoice.id } : null;
83
+ },
84
+ findProformaForBooking: async (bookingId) => {
85
+ const { invoices } = await import("@voyant-travel/finance");
86
+ const [proforma] = await db
87
+ .select({ id: invoices.id })
88
+ .from(invoices)
89
+ .where(eq(invoices.bookingId, bookingId))
90
+ .limit(1);
91
+ return proforma ? { invoiceId: proforma.id } : null;
92
+ },
93
+ generateContractPdf: generateContractPdf
94
+ ? async ({ bookingId }) => generateContractPdf({ db, eventBus, bookingId })
95
+ : undefined,
96
+ linkPaymentToInvoice: async ({ bookingId, invoiceId, paymentSessionId }) => {
97
+ const { paymentSessions } = await import("@voyant-travel/finance/schema");
98
+ const { financeService } = await import("@voyant-travel/finance");
99
+ const paidSessions = await db
100
+ .select()
101
+ .from(paymentSessions)
102
+ .where(and(eq(paymentSessions.bookingId, bookingId), eq(paymentSessions.status, "paid"), isNull(paymentSessions.invoiceId)));
103
+ let firstPaymentId = null;
104
+ let sessionsLinked = 0;
105
+ for (const session of paidSessions) {
106
+ await db
107
+ .update(paymentSessions)
108
+ .set({ invoiceId, updatedAt: new Date() })
109
+ .where(eq(paymentSessions.id, session.id));
110
+ const payment = await financeService.createPayment(db, invoiceId, {
111
+ amountCents: session.amountCents,
112
+ currency: session.currency,
113
+ paymentMethod: session.paymentMethod ?? "credit_card",
114
+ paymentInstrumentId: session.paymentInstrumentId ?? null,
115
+ paymentAuthorizationId: session.paymentAuthorizationId ?? null,
116
+ paymentCaptureId: session.paymentCaptureId ?? null,
117
+ status: "completed",
118
+ referenceNumber: session.providerPaymentId ??
119
+ session.externalReference ??
120
+ session.providerSessionId ??
121
+ session.id,
122
+ paymentDate: (session.completedAt ?? new Date()).toISOString().slice(0, 10),
123
+ notes: `Checkout-finalize linkage from session ${session.id}` +
124
+ (paymentSessionId && session.id !== paymentSessionId
125
+ ? ` (workflow input session: ${paymentSessionId})`
126
+ : ""),
127
+ });
128
+ if (payment?.id) {
129
+ await db
130
+ .update(paymentSessions)
131
+ .set({ paymentId: payment.id, updatedAt: new Date() })
132
+ .where(eq(paymentSessions.id, session.id));
133
+ if (!firstPaymentId)
134
+ firstPaymentId = payment.id;
135
+ }
136
+ sessionsLinked++;
137
+ }
138
+ return { paymentId: firstPaymentId, sessionsLinked };
139
+ },
140
+ };
141
+ }
142
+ function checkoutFinalizeInputRecord(input) {
143
+ return { ...input };
144
+ }
145
+ /**
146
+ * Run the checkout-finalize workflow for a booking: record a workflow run,
147
+ * build the finalize deps (confirm booking, issue invoice, link payments,
148
+ * generate contract PDF), execute `runCheckoutFinalize`, and mark the run
149
+ * complete/failed. The deployment owns the db + event bus + (optional)
150
+ * contract-pdf generator; this is the reusable saga driver.
151
+ */
152
+ export async function dispatchCheckoutFinalize(params) {
153
+ const recorder = await beginWorkflowRun(params.db, {
154
+ workflowName: "checkout-finalize",
155
+ trigger: params.trigger,
156
+ correlationId: params.correlationId ?? null,
157
+ tags: [...params.tags],
158
+ input: checkoutFinalizeInputRecord(params.input),
159
+ parentRunId: params.parentRunId ?? null,
160
+ triggeredByUserId: params.triggeredByUserId ?? null,
161
+ resumeFromStep: params.resumeFromStep ?? null,
162
+ });
163
+ if (params.parentRunId) {
164
+ try {
165
+ const action = params.resumeFromStep ? "resumed" : "rerun";
166
+ const description = params.resumeFromStep
167
+ ? `Workflow checkout-finalize ${action} from step "${params.resumeFromStep}"`
168
+ : `Workflow checkout-finalize ${action}`;
169
+ await params.db.insert(bookingActivityLog).values({
170
+ bookingId: params.input.bookingId,
171
+ actorId: params.triggeredByUserId ?? null,
172
+ activityType: "system_action",
173
+ description,
174
+ metadata: {
175
+ kind: "workflow_rerun",
176
+ workflowName: "checkout-finalize",
177
+ parentRunId: params.parentRunId,
178
+ newRunId: recorder.runId,
179
+ resumeFromStep: params.resumeFromStep ?? null,
180
+ },
181
+ });
182
+ }
183
+ catch (err) {
184
+ console.warn("[catalog-checkout] failed to write rerun activity log", err);
185
+ }
186
+ }
187
+ if (params.resumeFromStep && params.seedResults) {
188
+ for (const [stepName, output] of Object.entries(params.seedResults)) {
189
+ if (stepName === "__deps")
190
+ continue;
191
+ await recorder.recordSkippedStep(stepName, output && typeof output === "object" ? output : null);
192
+ }
193
+ }
194
+ const deps = buildCheckoutFinalizeDeps(params.db, params.eventBus, recorder, params.generateContractPdf);
195
+ try {
196
+ await runCheckoutFinalize(params.input, deps, {
197
+ skipUntil: params.resumeFromStep,
198
+ seedResults: params.seedResults,
199
+ });
200
+ await recorder.complete();
201
+ return { runId: recorder.runId };
202
+ }
203
+ catch (err) {
204
+ console.error("[catalog-checkout] checkout-finalize workflow failed", err);
205
+ await recorder.fail(err);
206
+ throw err;
207
+ }
208
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Catalog-checkout business-logic cluster, owned by `@voyant-travel/commerce`.
3
+ *
4
+ * The reusable orchestration (snapshot→booking materialization, tax-line
5
+ * derivation, acceptance signature promotion, checkout-start service, the
6
+ * checkout-finalize saga driver, and the public checkout route) lives here.
7
+ * Deployment-specific dependencies are injected via the options interfaces in
8
+ * `./options.js`:
9
+ * - `resolveBookingTaxSettings` — operator tax-mode/profile reads.
10
+ * - `getOwnedProductName` — owned product title (injected to avoid a cycle
11
+ * through `@voyant-travel/inventory`, which depends on commerce).
12
+ * - `resolveBankTransferInstructions` — operator profile / payment rows.
13
+ *
14
+ * The deployment keeps the thin HonoBundle wiring (workflow runner registry +
15
+ * event-bus subscribers) and calls `dispatchCheckoutFinalize` /
16
+ * `persistAcceptanceSignature` from there.
17
+ */
18
+ export { persistAcceptanceSignature } from "./acceptance-signature.js";
19
+ export { type CatalogCheckoutContractPdfGenerator, type DispatchCheckoutFinalizeParams, dispatchCheckoutFinalize, } from "./finalize.js";
20
+ export { type DraftPayload, type MaterializationSnapshot, materializeBookingFromSnapshot, rebuildBookingItemTaxLines, } from "./materialization.js";
21
+ export { extractBookingDates, extractItemDates, extractItemDescription, inferSnapshotTaxFacts, materializeBookingAllocations, materializeTravelerTravelDetails, resolveLineItemTitle, resolveSupplierFromSnapshot, resolveUpstreamCostCents, travelerBandToCategory, } from "./materialization-support.js";
22
+ export { materializeBookingItemTaxLine } from "./materialization-tax.js";
23
+ export type { CheckoutBankTransferInstructions, CheckoutModuleOptions, CheckoutStartOptions, } from "./options.js";
24
+ export { createCatalogCheckoutRoutes } from "./routes.js";
25
+ export { type CatalogCheckoutStartContext, CatalogCheckoutStartError, type CatalogCheckoutStartResult, type CheckoutStartInput, type CheckoutStartRequestMeta, checkoutStartSchema, startCatalogCheckout, } from "./start-service.js";
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACtE,OAAO,EACL,KAAK,mCAAmC,EACxC,KAAK,8BAA8B,EACnC,wBAAwB,GACzB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,6BAA6B,EAC7B,gCAAgC,EAChC,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAA;AACxE,YAAY,EACV,gCAAgC,EAChC,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EACL,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Catalog-checkout business-logic cluster, owned by `@voyant-travel/commerce`.
3
+ *
4
+ * The reusable orchestration (snapshot→booking materialization, tax-line
5
+ * derivation, acceptance signature promotion, checkout-start service, the
6
+ * checkout-finalize saga driver, and the public checkout route) lives here.
7
+ * Deployment-specific dependencies are injected via the options interfaces in
8
+ * `./options.js`:
9
+ * - `resolveBookingTaxSettings` — operator tax-mode/profile reads.
10
+ * - `getOwnedProductName` — owned product title (injected to avoid a cycle
11
+ * through `@voyant-travel/inventory`, which depends on commerce).
12
+ * - `resolveBankTransferInstructions` — operator profile / payment rows.
13
+ *
14
+ * The deployment keeps the thin HonoBundle wiring (workflow runner registry +
15
+ * event-bus subscribers) and calls `dispatchCheckoutFinalize` /
16
+ * `persistAcceptanceSignature` from there.
17
+ */
18
+ export { persistAcceptanceSignature } from "./acceptance-signature.js";
19
+ export { dispatchCheckoutFinalize, } from "./finalize.js";
20
+ export { materializeBookingFromSnapshot, rebuildBookingItemTaxLines, } from "./materialization.js";
21
+ export { extractBookingDates, extractItemDates, extractItemDescription, inferSnapshotTaxFacts, materializeBookingAllocations, materializeTravelerTravelDetails, resolveLineItemTitle, resolveSupplierFromSnapshot, resolveUpstreamCostCents, travelerBandToCategory, } from "./materialization-support.js";
22
+ export { materializeBookingItemTaxLine } from "./materialization-tax.js";
23
+ export { createCatalogCheckoutRoutes } from "./routes.js";
24
+ export { CatalogCheckoutStartError, checkoutStartSchema, startCatalogCheckout, } from "./start-service.js";
@@ -0,0 +1,105 @@
1
+ import type { bookings } from "@voyant-travel/bookings/schema";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ import type { DraftPayload, MaterializationSnapshot } from "./materialization.js";
4
+ import type { CheckoutModuleOptions } from "./options.js";
5
+ interface InsertedBookingItem {
6
+ id: string;
7
+ quantity?: number | null;
8
+ optionId?: string | null;
9
+ optionUnitId?: string | null;
10
+ }
11
+ export declare function materializeBookingAllocations(db: PostgresJsDatabase, booking: typeof bookings.$inferSelect, insertedItems: ReadonlyArray<InsertedBookingItem>, draftPayload: DraftPayload, snapshot: MaterializationSnapshot): Promise<void>;
12
+ export declare function inferSnapshotTaxFacts(snapshot: MaterializationSnapshot): {
13
+ hasAccommodation: boolean;
14
+ accommodationCountries: string[];
15
+ };
16
+ export declare function materializeTravelerTravelDetails(db: PostgresJsDatabase, insertedTravelers: Array<{
17
+ id: string;
18
+ }>, draftTravelers: NonNullable<DraftPayload["travelers"]>, env: Record<string, unknown>): Promise<void>;
19
+ /**
20
+ * Resolve supplier info for the booking from the catalog snapshot.
21
+ * Pulls from:
22
+ * 1. `catalog_sourced_entries.projection.supplierId` — supplier
23
+ * name/id captured at sync time (covers Bokun, demo adapter, etc.).
24
+ * 2. The frozen payload's `upstream_payload.supplierId` — fallback
25
+ * when the sourced-entries row is missing (legacy bookings).
26
+ * 3. `frozen_payload.reserve.orderId` — used as `supplierReference`
27
+ * so operators can match up against the upstream provider's
28
+ * booking reference.
29
+ *
30
+ * Returns null when no supplier can be resolved — the caller treats
31
+ * that as "skip auto-fill, leave blank for manual entry".
32
+ */
33
+ export declare function resolveSupplierFromSnapshot(db: PostgresJsDatabase, snapshot: MaterializationSnapshot): Promise<{
34
+ serviceName: string;
35
+ supplierReference: string | null;
36
+ supplierServiceId: string | null;
37
+ upstreamCostCents: number | null;
38
+ } | null>;
39
+ /**
40
+ * Resolve booking-level dates from the draft and frozen source data.
41
+ * `start_date`/`end_date` drive the admin booking header, while item
42
+ * dates drive the line table. A storefront product selection usually
43
+ * carries only `departureSlotId`, so we resolve that id against the
44
+ * quote/reserve/content payload before falling back to free-form dates.
45
+ */
46
+ export declare function extractBookingDates(snapshot: Pick<MaterializationSnapshot, "frozen_payload">, draftPayload: DraftPayload): {
47
+ startDate: string | null;
48
+ endDate: string | null;
49
+ };
50
+ /**
51
+ * Pull start/end dates for a booking-item from the most reliable
52
+ * source available. Order:
53
+ * 1. The selected `departureSlotId` resolved against reserve /
54
+ * quote / captured content payloads.
55
+ * 2. `frozen_payload.quote.upstream_payload.metadata.days[]` —
56
+ * Bokun-style itinerary captured at quote time, gives us per-day
57
+ * dates with full timezone fidelity.
58
+ * 3. Draft `configure.dateRange.checkIn`/`checkOut` — what the
59
+ * customer selected on the storefront before booking.
60
+ * 4. Draft `configure.departureDate` — single-day tour selection.
61
+ * 5. Booking row's own `start_date` / `end_date` columns — the
62
+ * caller already populated these from the same draft when
63
+ * writing the booking row, so this is a final safety net.
64
+ *
65
+ * Returns nulls when nothing resolves — the caller treats that as
66
+ * "no date data, leave NULL" rather than fabricating one.
67
+ */
68
+ export declare function extractItemDates(snapshot: MaterializationSnapshot, draftPayload: DraftPayload, booking: typeof bookings.$inferSelect): {
69
+ serviceDate: string | null;
70
+ startsAt: Date | null;
71
+ endsAt: Date | null;
72
+ };
73
+ /**
74
+ * Pull a description for the booking item from the upstream payload.
75
+ * Sourced products typically carry rich descriptions on the upstream
76
+ * entry; surfacing a short snippet on the item helps operators scan
77
+ * a multi-item booking without clicking into each line.
78
+ */
79
+ export declare function extractItemDescription(snapshot: MaterializationSnapshot): string | null;
80
+ /**
81
+ * Look up the upstream cost (net rate the operator pays the supplier)
82
+ * for a sourced entity. Returns null when the adapter doesn't expose
83
+ * a net/gross split — caller falls back to sell-as-cost (zero-markup
84
+ * default).
85
+ */
86
+ export declare function resolveUpstreamCostCents(db: PostgresJsDatabase, snapshot: MaterializationSnapshot): Promise<number | null>;
87
+ /**
88
+ * Resolve a human title for the booking line item. Tries:
89
+ * 1. `catalog_sourced_entries.projection.name` — sourced products
90
+ * (demo, Bokun, …) all carry the upstream title there.
91
+ * 2. The injected `getOwnedProductName` — owned products from this
92
+ * deployment's products module (injected because inventory
93
+ * depends on commerce; a static import would cycle).
94
+ * 3. A generic "$module booking" fallback.
95
+ *
96
+ * Errors fall through quietly — a title is purely cosmetic, the
97
+ * booking-item row should always insert successfully.
98
+ */
99
+ export declare function resolveLineItemTitle(db: PostgresJsDatabase, snapshot: {
100
+ entity_module: string;
101
+ entity_id: string;
102
+ }, options: Pick<CheckoutModuleOptions, "getOwnedProductName">): Promise<string>;
103
+ export declare function travelerBandToCategory(band: string | undefined): "adult" | "child" | "infant" | "senior" | "other";
104
+ export {};
105
+ //# sourceMappingURL=materialization-support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialization-support.d.ts","sourceRoot":"","sources":["../../src/checkout/materialization-support.ts"],"names":[],"mappings":"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"}