@voyant-travel/finance 0.167.0 → 0.169.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/README.md CHANGED
@@ -114,14 +114,19 @@ Mounted Finance routes include:
114
114
  ## Booking Tax Preview
115
115
 
116
116
  Booking creation UIs can show the same tax line that booking finalization will
117
- persist by mounting the booking-tax extension:
117
+ persist by mounting the booking-tax extensions. The surface is split in two:
118
+ tax **settings** live on the finance admin surface, and tax **preview** lives on
119
+ the bookings admin surface:
118
120
 
119
121
  ```typescript
120
- import { createBookingTaxApiExtension } from "@voyant-travel/finance/booking-tax"
122
+ import {
123
+ createBookingTaxSettingsApiExtension,
124
+ createBookingTaxPreviewApiExtension,
125
+ } from "@voyant-travel/finance/booking-tax"
121
126
 
122
127
  createApp({
123
128
  extensions: [
124
- createBookingTaxApiExtension({
129
+ createBookingTaxSettingsApiExtension({
125
130
  resolveBookingTaxSettings: async (db) => {
126
131
  const settings = await getTaxSettings(db)
127
132
  return {
@@ -129,6 +134,10 @@ createApp({
129
134
  taxPolicyProfileId: settings?.taxPolicyProfileId ?? null,
130
135
  }
131
136
  },
137
+ updateBookingTaxSettings: async (db, next) => saveTaxSettings(db, next),
138
+ }),
139
+ createBookingTaxPreviewApiExtension({
140
+ resolveBookingTaxSettings: async (db) => getTaxSettings(db),
132
141
  }),
133
142
  ],
134
143
  })
@@ -139,16 +148,21 @@ settings table, KV, environment configuration, or any other deployment-owned
139
148
  store, while `@voyant-travel/finance` owns the tax policy rule walker, tax-regime
140
149
  lookup, product tax-class fallback, and inclusive/exclusive math.
141
150
 
142
- Templates that already mount custom routes can call `mountBookingTaxRoutes(...)`
143
- from the same entrypoint instead of using the API extension.
151
+ Templates that already mount custom routes can call
152
+ `mountBookingTaxSettingsRoutes(...)` / `mountBookingTaxPreviewRoutes(...)` from
153
+ the same entrypoint instead of using the API extensions.
144
154
 
145
- Mounting this route registers:
155
+ Mounting these routes registers:
146
156
 
147
- - `GET /v1/admin/bookings/tax-settings`
148
- - `PATCH /v1/admin/bookings/tax-settings` when `updateBookingTaxSettings` is supplied
157
+ - `GET /v1/admin/finance/tax-settings`
158
+ - `PATCH /v1/admin/finance/tax-settings` when `updateBookingTaxSettings` is supplied
149
159
  - `POST /v1/admin/bookings/tax-preview`
150
160
 
151
- The preview endpoint is consumed by `@voyant-travel/bookings-react` tax-preview
161
+ Tax settings sit on the finance admin surface (alongside tax regimes, policy
162
+ rules, and invoice-fx) so the managed runtime's per-unit, prefix-first-match
163
+ admin dispatch does not let the bookings `GET /{id}` route swallow
164
+ `/tax-settings`. The preview endpoint is consumed by
165
+ `@voyant-travel/bookings-react` tax-preview
152
166
  hooks. Consumers that use the booking-create dialog without mounting the route
153
167
  will silently lose tax rows in the dialog summary because the client treats a
154
168
  missing preview as "no tax to show".
@@ -13,12 +13,6 @@ export type ResolvedBookingSellTaxRate = {
13
13
  priceMode: "inclusive" | "exclusive";
14
14
  };
15
15
  export type InvoicingMode = "direct" | "proforma-first";
16
- /**
17
- * Official FX reference-rate source. `ecb` (default) covers most EU
18
- * operators; `bnr` is the National Bank of Romania. A setting, not
19
- * per-country code — new jurisdictions add values here, not modules.
20
- */
21
- export type FxReferenceSource = "ecb" | "bnr";
22
16
  export type BookingTaxSettings = {
23
17
  taxPriceMode?: "inclusive" | "exclusive" | null;
24
18
  taxPolicyProfileId?: string | null;
@@ -30,10 +24,6 @@ export type BookingTaxSettings = {
30
24
  * never consult this. Absent/null → `proforma-first`.
31
25
  */
32
26
  invoicingMode?: InvoicingMode | null;
33
- /**
34
- * Operator's official FX reference-rate source. Absent/null → `ecb`.
35
- */
36
- fxReferenceSource?: FxReferenceSource | null;
37
27
  };
38
28
  export type ResolveBookingTaxSettings = (db: PostgresJsDatabase) => BookingTaxSettings | null | undefined | Promise<BookingTaxSettings | null | undefined>;
39
29
  export type UpdateBookingTaxSettings = (db: PostgresJsDatabase, settings: BookingTaxSettings) => BookingTaxSettings | null | undefined | Promise<BookingTaxSettings | null | undefined>;
@@ -71,7 +61,16 @@ export declare function computeBookingItemTaxLine(taxRate: ResolvedBookingSellTa
71
61
  } | null;
72
62
  export declare function loadProductTaxFacts(db: PostgresJsDatabase, productId: string): Promise<ProductTaxFacts>;
73
63
  export declare function matchesTaxPolicyCondition(condition: TaxPolicyCondition | null | undefined, facts: ProductTaxFacts): boolean;
74
- export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions): OpenAPIHono<{
64
+ /**
65
+ * Booking tax **settings** routes: `GET`/`PATCH /tax-settings`.
66
+ *
67
+ * Mounted on the finance admin surface (`/v1/admin/finance/tax-settings`)
68
+ * so it lands alongside the other finance-owned settings (tax regimes,
69
+ * tax-policy-rules, invoice-fx) rather than under `/v1/admin/bookings`,
70
+ * where the bookings package's `GET /{id}` route would otherwise capture
71
+ * `/tax-settings` under the managed runtime's prefix-first-match dispatch.
72
+ */
73
+ export declare function createBookingTaxSettingsRoutes(options?: BookingTaxRouteOptions): OpenAPIHono<{
75
74
  Variables: {
76
75
  db: PostgresJsDatabase;
77
76
  };
@@ -84,7 +83,6 @@ export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions)
84
83
  taxPriceMode: "inclusive" | "exclusive";
85
84
  taxPolicyProfileId: string | null;
86
85
  invoicingMode: "direct" | "proforma-first";
87
- fxReferenceSource: "ecb" | "bnr";
88
86
  };
89
87
  };
90
88
  outputFormat: "json";
@@ -99,7 +97,6 @@ export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions)
99
97
  taxPriceMode?: "inclusive" | "exclusive" | undefined;
100
98
  taxPolicyProfileId?: string | null | undefined;
101
99
  invoicingMode?: "direct" | "proforma-first" | undefined;
102
- fxReferenceSource?: "ecb" | "bnr" | undefined;
103
100
  };
104
101
  };
105
102
  output: {};
@@ -111,7 +108,6 @@ export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions)
111
108
  taxPriceMode?: "inclusive" | "exclusive" | undefined;
112
109
  taxPolicyProfileId?: string | null | undefined;
113
110
  invoicingMode?: "direct" | "proforma-first" | undefined;
114
- fxReferenceSource?: "ecb" | "bnr" | undefined;
115
111
  };
116
112
  };
117
113
  output: {
@@ -119,14 +115,25 @@ export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions)
119
115
  taxPriceMode: "inclusive" | "exclusive";
120
116
  taxPolicyProfileId: string | null;
121
117
  invoicingMode: "direct" | "proforma-first";
122
- fxReferenceSource: "ecb" | "bnr";
123
118
  };
124
119
  };
125
120
  outputFormat: "json";
126
121
  status: 200;
127
122
  };
128
123
  };
129
- } & {
124
+ }, "/">;
125
+ /**
126
+ * Booking tax **preview** route: `POST /tax-preview`.
127
+ *
128
+ * Stays on the bookings admin surface (`/v1/admin/bookings/tax-preview`);
129
+ * `POST` does not collide with bookings' `GET /{id}`, and
130
+ * `@voyant-travel/bookings-react` consumes it there.
131
+ */
132
+ export declare function createBookingTaxPreviewRoutes(options?: BookingTaxRouteOptions): OpenAPIHono<{
133
+ Variables: {
134
+ db: PostgresJsDatabase;
135
+ };
136
+ }, {
130
137
  "/tax-preview": {
131
138
  $post: {
132
139
  input: {
@@ -155,6 +162,11 @@ export declare function createBookingTaxRoutes(options?: BookingTaxRouteOptions)
155
162
  };
156
163
  };
157
164
  }, "/">;
158
- export declare function mountBookingTaxRoutes(hono: Hono, options?: BookingTaxRouteOptions): void;
159
- export declare function createBookingTaxApiExtension(options?: BookingTaxRouteOptions): ApiExtension;
160
- export declare const createBookingTaxVoyantRuntime: import("@voyant-travel/core/project").VoyantGraphRuntimeFactory<ApiExtension>;
165
+ /** Mount the tax-settings routes under the finance admin surface. */
166
+ export declare function mountBookingTaxSettingsRoutes(hono: Hono, options?: BookingTaxRouteOptions): void;
167
+ /** Mount the tax-preview route under the bookings admin surface. */
168
+ export declare function mountBookingTaxPreviewRoutes(hono: Hono, options?: BookingTaxRouteOptions): void;
169
+ export declare function createBookingTaxSettingsApiExtension(options?: BookingTaxRouteOptions): ApiExtension;
170
+ export declare function createBookingTaxPreviewApiExtension(options?: BookingTaxRouteOptions): ApiExtension;
171
+ export declare const createBookingTaxSettingsVoyantRuntime: import("@voyant-travel/core/project").VoyantGraphRuntimeFactory<ApiExtension>;
172
+ export declare const createBookingTaxPreviewVoyantRuntime: import("@voyant-travel/core/project").VoyantGraphRuntimeFactory<ApiExtension>;
@@ -15,14 +15,12 @@ const bookingTaxSettingsPatchSchema = z.object({
15
15
  taxPriceMode: z.enum(["inclusive", "exclusive"]).optional(),
16
16
  taxPolicyProfileId: z.string().min(1).nullable().optional(),
17
17
  invoicingMode: z.enum(["direct", "proforma-first"]).optional(),
18
- fxReferenceSource: z.enum(["ecb", "bnr"]).optional(),
19
18
  });
20
19
  const bookingTaxSettingsResponseSchema = z.object({
21
20
  data: z.object({
22
21
  taxPriceMode: z.enum(["inclusive", "exclusive"]),
23
22
  taxPolicyProfileId: z.string().nullable(),
24
23
  invoicingMode: z.enum(["direct", "proforma-first"]),
25
- fxReferenceSource: z.enum(["ecb", "bnr"]),
26
24
  }),
27
25
  });
28
26
  const bookingTaxPreviewResponseSchema = z.object({
@@ -118,7 +116,6 @@ async function resolveBookingTaxSettingsOrDefault(db, options = {}) {
118
116
  taxPriceMode: settings?.taxPriceMode === "exclusive" ? "exclusive" : "inclusive",
119
117
  taxPolicyProfileId: settings?.taxPolicyProfileId ?? null,
120
118
  invoicingMode: settings?.invoicingMode === "direct" ? "direct" : "proforma-first",
121
- fxReferenceSource: settings?.fxReferenceSource === "bnr" ? "bnr" : "ecb",
122
119
  };
123
120
  }
124
121
  export function computeBookingItemTaxLine(taxRate, amountCents, currency, sortOrder = 0) {
@@ -260,7 +257,16 @@ async function resolveProductTaxClassRate(db, productId, priceMode) {
260
257
  const resolved = await loadResolvedTaxRegime(db, klass.defaultRegimeId, klass.code);
261
258
  return resolved ? { ...resolved, priceMode } : null;
262
259
  }
263
- export function createBookingTaxRoutes(options = {}) {
260
+ /**
261
+ * Booking tax **settings** routes: `GET`/`PATCH /tax-settings`.
262
+ *
263
+ * Mounted on the finance admin surface (`/v1/admin/finance/tax-settings`)
264
+ * so it lands alongside the other finance-owned settings (tax regimes,
265
+ * tax-policy-rules, invoice-fx) rather than under `/v1/admin/bookings`,
266
+ * where the bookings package's `GET /{id}` route would otherwise capture
267
+ * `/tax-settings` under the managed runtime's prefix-first-match dispatch.
268
+ */
269
+ export function createBookingTaxSettingsRoutes(options = {}) {
264
270
  const routes = new OpenAPIHono({ defaultHook: openApiValidationHook })
265
271
  .openapi(getBookingTaxSettingsRoute, async (c) => {
266
272
  return c.json({
@@ -282,13 +288,22 @@ export function createBookingTaxRoutes(options = {}) {
282
288
  ? current.taxPolicyProfileId
283
289
  : patch.taxPolicyProfileId,
284
290
  invoicingMode: patch.invoicingMode ?? current.invoicingMode,
285
- fxReferenceSource: patch.fxReferenceSource ?? current.fxReferenceSource,
286
291
  });
287
292
  return c.json({
288
293
  data: await resolveBookingTaxSettingsOrDefault(c.get("db"), { settings: next }),
289
294
  }, 200);
290
- })
291
- .openapi(previewBookingTaxRoute, async (c) => {
295
+ });
296
+ return stampOpenApiRegistryApiId(routes, "@voyant-travel/finance#booking-tax-settings-extension.api");
297
+ }
298
+ /**
299
+ * Booking tax **preview** route: `POST /tax-preview`.
300
+ *
301
+ * Stays on the bookings admin surface (`/v1/admin/bookings/tax-preview`);
302
+ * `POST` does not collide with bookings' `GET /{id}`, and
303
+ * `@voyant-travel/bookings-react` consumes it there.
304
+ */
305
+ export function createBookingTaxPreviewRoutes(options = {}) {
306
+ const routes = new OpenAPIHono({ defaultHook: openApiValidationHook }).openapi(previewBookingTaxRoute, async (c) => {
292
307
  const body = c.req.valid("json");
293
308
  const taxRate = await resolveBookingSellTaxRate(c.get("db"), { productId: body.productId }, options);
294
309
  const taxLine = computeBookingItemTaxLine(taxRate, body.subtotalCents, body.currency);
@@ -323,21 +338,39 @@ export function createBookingTaxRoutes(options = {}) {
323
338
  },
324
339
  }, 200);
325
340
  });
326
- return stampOpenApiRegistryApiId(routes, "@voyant-travel/finance#booking-tax-extension.api");
341
+ return stampOpenApiRegistryApiId(routes, "@voyant-travel/finance#booking-tax-preview-extension.api");
327
342
  }
328
- export function mountBookingTaxRoutes(hono, options = {}) {
329
- hono.route("/v1/admin/bookings", createBookingTaxRoutes(options));
343
+ /** Mount the tax-settings routes under the finance admin surface. */
344
+ export function mountBookingTaxSettingsRoutes(hono, options = {}) {
345
+ hono.route("/v1/admin/finance", createBookingTaxSettingsRoutes(options));
330
346
  }
331
- export function createBookingTaxApiExtension(options = {}) {
347
+ /** Mount the tax-preview route under the bookings admin surface. */
348
+ export function mountBookingTaxPreviewRoutes(hono, options = {}) {
349
+ hono.route("/v1/admin/bookings", createBookingTaxPreviewRoutes(options));
350
+ }
351
+ export function createBookingTaxSettingsApiExtension(options = {}) {
332
352
  const extension = {
333
- name: "booking-tax",
353
+ name: "booking-tax-settings",
354
+ module: "finance",
355
+ };
356
+ return {
357
+ extension,
358
+ adminRoutes: createBookingTaxSettingsRoutes(options),
359
+ };
360
+ }
361
+ export function createBookingTaxPreviewApiExtension(options = {}) {
362
+ const extension = {
363
+ name: "booking-tax-preview",
334
364
  module: "bookings",
335
365
  };
336
366
  return {
337
367
  extension,
338
- adminRoutes: createBookingTaxRoutes(options),
368
+ adminRoutes: createBookingTaxPreviewRoutes(options),
339
369
  };
340
370
  }
341
- export const createBookingTaxVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {
342
- return createBookingTaxApiExtension(createFinanceBookingTaxRuntime(await getPort(financeOperatorSettingsRuntimePort)));
371
+ export const createBookingTaxSettingsVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {
372
+ return createBookingTaxSettingsApiExtension(createFinanceBookingTaxRuntime(await getPort(financeOperatorSettingsRuntimePort)));
373
+ });
374
+ export const createBookingTaxPreviewVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {
375
+ return createBookingTaxPreviewApiExtension(createFinanceBookingTaxRuntime(await getPort(financeOperatorSettingsRuntimePort)));
343
376
  });
package/dist/index.d.ts CHANGED
@@ -26,7 +26,7 @@ export declare const createFinanceVoyantRuntime: import("@voyant-travel/core/pro
26
26
  export type { PaymentAdapter, PaymentAdapterCapabilities, PaymentAdapterConformanceHarness, PaymentAdapterConformanceResult, PaymentAdapterDiagnostics, PaymentAdapterRuntimeContext, PaymentCallbackEvent, PaymentCallbackRequest, PaymentCallbackVerificationResult, PaymentInitiationInput, PaymentInitiationResult, PaymentMoney, PaymentOperationInput, PaymentOperationResult, PaymentStatusInput, PaymentStatusResult, } from "@voyant-travel/payments";
27
27
  export { PAYMENT_ADAPTER_CONTRACT_VERSION, PAYMENT_ADAPTER_RUNTIME_PORT_ID, paymentAdapterRuntimePort, runPaymentAdapterConformance, } from "@voyant-travel/payments";
28
28
  export { type BookingCancellationSettlementInput, buildPaidBookingCancellationSettlementNote, closeTerminalBookingPaymentSchedules, financeBookingLifecycle, recordPaidBookingCancellationSettlement, } from "./booking-lifecycle.js";
29
- export { type BookingTaxRouteOptions, type BookingTaxSettings, computeBookingItemTaxLine, createBookingTaxApiExtension, createBookingTaxRoutes, createBookingTaxVoyantRuntime, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxRoutes, type ProductTaxFacts, type ResolveBookingSellTaxRateOptions, type ResolveBookingTaxSettings, type ResolvedBookingSellTaxRate, resolveBookingSellTaxRate, type TaxPolicyCondition, type UpdateBookingTaxSettings, } from "./booking-tax.js";
29
+ export { type BookingTaxRouteOptions, type BookingTaxSettings, computeBookingItemTaxLine, createBookingTaxPreviewApiExtension, createBookingTaxPreviewRoutes, createBookingTaxPreviewVoyantRuntime, createBookingTaxSettingsApiExtension, createBookingTaxSettingsRoutes, createBookingTaxSettingsVoyantRuntime, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxPreviewRoutes, mountBookingTaxSettingsRoutes, type ProductTaxFacts, type ResolveBookingSellTaxRateOptions, type ResolveBookingTaxSettings, type ResolvedBookingSellTaxRate, resolveBookingSellTaxRate, type TaxPolicyCondition, type UpdateBookingTaxSettings, } from "./booking-tax.js";
30
30
  export type { CardPaymentBilling, CardPaymentStartArgs, CardPaymentStarter, CardPaymentStartResult, PaymentAdapterCardPaymentStarterOptions, } from "./card-payment.js";
31
31
  export { createPaymentAdapterCardPaymentStarter } from "./card-payment.js";
32
32
  export { type DocumentDownloadEnvelope, type DocumentDownloadResolution, type DocumentDownloadResolver, resolveStoredDocumentDownload, type StoredDocumentReference, } from "./document-download.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { customFieldsRuntimePort } from "@voyant-travel/core/custom-fields";
5
5
  import { defineGraphRuntimeFactory } from "@voyant-travel/core/project";
6
6
  import { stampOpenApiRegistryApiId } from "@voyant-travel/hono";
7
7
  import { financeBookingLifecycle } from "./booking-lifecycle.js";
8
- import { createBookingTaxRoutes } from "./booking-tax.js";
8
+ import { createBookingTaxSettingsRoutes } from "./booking-tax.js";
9
9
  import { buildFinanceCheckoutRouteRuntime, createFinanceCheckoutAdminRoutes, createFinanceCheckoutRoutes, FINANCE_CHECKOUT_ROUTE_RUNTIME_CONTAINER_KEY, } from "./checkout-routes.js";
10
10
  import { createInvoiceFxRoutes } from "./invoice-fx.js";
11
11
  import { financeLinkable } from "./linkables.js";
@@ -39,7 +39,7 @@ export function createFinanceApiModule(options = {}) {
39
39
  .route("/", supplierInvoiceRoutes)
40
40
  .route("/", createInvoiceFxRoutes(options))
41
41
  .route("/", createFinanceAdminDocumentRoutes(options))
42
- .route("/", createFinanceAdminSettlementRoutes(options)), "@voyant-travel/finance#api.admin").route("/", createBookingTaxRoutes(options));
42
+ .route("/", createFinanceAdminSettlementRoutes(options)), "@voyant-travel/finance#api.admin").route("/", createBookingTaxSettingsRoutes(options));
43
43
  const module = {
44
44
  ...financeModule,
45
45
  bootstrap: ({ bindings, container, eventBus }) => {
@@ -89,7 +89,7 @@ export const createFinanceVoyantRuntime = defineGraphRuntimeFactory(async ({ api
89
89
  });
90
90
  export { PAYMENT_ADAPTER_CONTRACT_VERSION, PAYMENT_ADAPTER_RUNTIME_PORT_ID, paymentAdapterRuntimePort, runPaymentAdapterConformance, } from "@voyant-travel/payments";
91
91
  export { buildPaidBookingCancellationSettlementNote, closeTerminalBookingPaymentSchedules, financeBookingLifecycle, recordPaidBookingCancellationSettlement, } from "./booking-lifecycle.js";
92
- export { computeBookingItemTaxLine, createBookingTaxApiExtension, createBookingTaxRoutes, createBookingTaxVoyantRuntime, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxRoutes, resolveBookingSellTaxRate, } from "./booking-tax.js";
92
+ export { computeBookingItemTaxLine, createBookingTaxPreviewApiExtension, createBookingTaxPreviewRoutes, createBookingTaxPreviewVoyantRuntime, createBookingTaxSettingsApiExtension, createBookingTaxSettingsRoutes, createBookingTaxSettingsVoyantRuntime, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxPreviewRoutes, mountBookingTaxSettingsRoutes, resolveBookingSellTaxRate, } from "./booking-tax.js";
93
93
  export { createPaymentAdapterCardPaymentStarter } from "./card-payment.js";
94
94
  export { resolveStoredDocumentDownload, } from "./document-download.js";
95
95
  export { createInvoiceFxApiExtension, createInvoiceFxRoutes, createVoyantDataFxExchangeRateResolver, mountInvoiceFxRoutes, resolveInvoiceFxContext, resolveInvoiceFxSettingsOrDefault, } from "./invoice-fx.js";
@@ -1,7 +1,7 @@
1
1
  import type { VoyantRuntimeHostPrimitives } from "@voyant-travel/core";
2
2
  import type { AnyDrizzleDb } from "@voyant-travel/db";
3
3
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
4
- import type { BookingTaxRouteOptions, FxReferenceSource } from "./booking-tax.js";
4
+ import type { BookingTaxRouteOptions } from "./booking-tax.js";
5
5
  import type { CheckoutRoutesOptions } from "./checkout-routes.js";
6
6
  import type { CheckoutPaymentStarter } from "./checkout-service.js";
7
7
  import type { PaymentPolicy } from "./payment-policy.js";
@@ -24,17 +24,18 @@ export interface FinanceOperatorSettingsRuntime {
24
24
  * settled proforma should be auto-converted to a fiscal invoice.
25
25
  */
26
26
  resolveInvoicingMode: (db: PostgresJsDatabase) => Promise<"direct" | "proforma-first">;
27
- /**
28
- * Resolve the operator's official FX reference-rate source
29
- * (`ecb` | `bnr`, default `ecb`). Read by the finance fx-reference
30
- * helper to pick the host-provided rate source for the operator.
31
- */
32
- resolveFxReferenceSource: (db: PostgresJsDatabase) => Promise<FxReferenceSource>;
33
27
  }
34
28
  export interface FinanceNotificationsRuntime {
35
29
  resolveNotificationDispatcher: NonNullable<CheckoutRoutesOptions["resolveNotificationDispatcher"]>;
36
30
  listBookingReminderRuns: NonNullable<CheckoutRoutesOptions["listBookingReminderRuns"]>;
37
31
  }
32
+ /**
33
+ * Label for the official source that published a resolved reference rate
34
+ * (e.g. `ecb`, `bnr`). This is an OUTPUT annotation only — the operator
35
+ * does not pick a source; the host adapter knows its own. Free-form so a
36
+ * host can report whatever series it drew from.
37
+ */
38
+ export type FxReferenceSource = string;
38
39
  /** Request for one official FX reference rate on a given date. */
39
40
  export interface FxReferenceRateRequest {
40
41
  /** Currency to convert from, e.g. `EUR` (ISO 4217). */
@@ -56,17 +57,14 @@ export interface FxReferenceRate {
56
57
  /**
57
58
  * Host-provided official FX reference-rate source. Finance defines the
58
59
  * seam only; hosts/deployments wire it to their own FX data source. No
59
- * HTTP client or API key lives inside finance.
60
+ * HTTP client or API key lives inside finance. The operator does not pick
61
+ * a source — the host adapter knows its own (managed FX on Voyant Cloud;
62
+ * a self-hoster's own adapter otherwise; a legally mandated source such
63
+ * as BNR for RO). The chosen source is reported back on the rate.
60
64
  */
61
65
  export interface FinanceFxReferenceRuntime {
62
- /**
63
- * Resolve one official reference rate. `source` is the operator's
64
- * configured reference source (e.g. `ecb`, `bnr`); the host uses it
65
- * to pick the correct published series.
66
- */
67
- resolveReferenceRate(request: FxReferenceRateRequest & {
68
- source: FxReferenceSource;
69
- }): Promise<FxReferenceRate>;
66
+ /** Resolve one official reference rate for the host's own source. */
67
+ resolveReferenceRate(request: FxReferenceRateRequest): Promise<FxReferenceRate>;
70
68
  }
71
69
  export interface FinanceDistributionPaymentPolicyRuntime {
72
70
  resolveSupplierPolicy: PolicyReader;
@@ -115,23 +113,21 @@ export declare const financeFxReferenceRuntimePort: import("@voyant-travel/core/
115
113
  */
116
114
  export declare class FinanceFxReferenceSourceUnavailableError extends Error {
117
115
  readonly code = "finance_fx_reference_source_unavailable";
118
- constructor(source: FxReferenceSource);
116
+ constructor();
119
117
  }
120
118
  export interface ResolveReferenceRateHelperInput {
121
119
  base: string;
122
120
  quote: string;
123
121
  date?: string;
124
- /** The operator's configured reference source (e.g. `ecb`, `bnr`). */
125
- source: FxReferenceSource;
126
122
  /** Host-provided implementation. Absent → typed unavailable error. */
127
123
  provider?: FinanceFxReferenceRuntime | null;
128
124
  }
129
125
  /**
130
- * Typed helper that resolves an official FX reference rate for an
131
- * operator's configured source by delegating to the host-provided
132
- * `finance.fx-reference.runtime` implementation. When no provider is
133
- * wired, throws {@link FinanceFxReferenceSourceUnavailableError} — a
134
- * clear, typed signal rather than a silent fallback. Finance holds no
126
+ * Typed helper that resolves an official FX reference rate by delegating
127
+ * to the host-provided `finance.fx-reference.runtime` implementation. The
128
+ * source is the host adapter's own — the operator does not pick one. When
129
+ * no provider is wired, throws {@link FinanceFxReferenceSourceUnavailableError}
130
+ * — a clear, typed signal rather than a silent fallback. Finance holds no
135
131
  * FX data itself.
136
132
  */
137
133
  export declare function resolveReferenceRate(input: ResolveReferenceRateHelperInput): Promise<FxReferenceRate>;
@@ -20,7 +20,6 @@ export const financeOperatorSettingsRuntimePort = objectPort("finance.operator-s
20
20
  "resolveBookingTaxSettings",
21
21
  "updateBookingTaxSettings",
22
22
  "resolveInvoicingMode",
23
- "resolveFxReferenceSource",
24
23
  ]);
25
24
  export const financeNotificationsRuntimePort = objectPort("finance.notifications.runtime", ["resolveNotificationDispatcher", "listBookingReminderRuns"]);
26
25
  export const financeFxReferenceRuntimePort = objectPort("finance.fx-reference.runtime", ["resolveReferenceRate"]);
@@ -32,25 +31,25 @@ export const financeFxReferenceRuntimePort = objectPort("finance.fx-reference.ru
32
31
  */
33
32
  export class FinanceFxReferenceSourceUnavailableError extends Error {
34
33
  code = "finance_fx_reference_source_unavailable";
35
- constructor(source) {
36
- super(`No FX reference-rate source is configured for "${source}". A host must provide the finance.fx-reference.runtime port.`);
34
+ constructor() {
35
+ super("No FX reference-rate source is configured. A host must provide the finance.fx-reference.runtime port.");
37
36
  this.name = "FinanceFxReferenceSourceUnavailableError";
38
37
  }
39
38
  }
40
39
  /**
41
- * Typed helper that resolves an official FX reference rate for an
42
- * operator's configured source by delegating to the host-provided
43
- * `finance.fx-reference.runtime` implementation. When no provider is
44
- * wired, throws {@link FinanceFxReferenceSourceUnavailableError} — a
45
- * clear, typed signal rather than a silent fallback. Finance holds no
40
+ * Typed helper that resolves an official FX reference rate by delegating
41
+ * to the host-provided `finance.fx-reference.runtime` implementation. The
42
+ * source is the host adapter's own — the operator does not pick one. When
43
+ * no provider is wired, throws {@link FinanceFxReferenceSourceUnavailableError}
44
+ * — a clear, typed signal rather than a silent fallback. Finance holds no
46
45
  * FX data itself.
47
46
  */
48
47
  export function resolveReferenceRate(input) {
49
- const { provider, source, base, quote, date } = input;
48
+ const { provider, base, quote, date } = input;
50
49
  if (!provider) {
51
- throw new FinanceFxReferenceSourceUnavailableError(source);
50
+ throw new FinanceFxReferenceSourceUnavailableError();
52
51
  }
53
- return provider.resolveReferenceRate({ base, quote, date, source });
52
+ return provider.resolveReferenceRate({ base, quote, date });
54
53
  }
55
54
  export const financeDistributionPaymentPolicyRuntimePort = objectPort("finance.distribution-payment-policy.runtime", ["resolveSupplierPolicy", "resolveSupplierPolicyById"]);
56
55
  export const financeAccommodationsPaymentPolicyRuntimePort = objectPort("finance.accommodations-payment-policy.runtime", ["resolveBookingPolicy", "resolveEntityPolicy"]);
package/dist/voyant.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** Import-cheap deployment declaration owned by the finance package. */
2
2
  export declare const financeVoyantModule: import("@voyant-travel/core").VoyantGraphUnitManifest;
3
- export declare const financeBookingTaxVoyantPlugin: import("@voyant-travel/core").VoyantGraphUnitManifest;
3
+ export declare const financeBookingTaxSettingsVoyantPlugin: import("@voyant-travel/core").VoyantGraphUnitManifest;
4
+ export declare const financeBookingTaxPreviewVoyantPlugin: import("@voyant-travel/core").VoyantGraphUnitManifest;
4
5
  export declare const financeBookingsCreateVoyantPlugin: import("@voyant-travel/core").VoyantGraphUnitManifest;
5
6
  export declare const financeBookingScheduleVoyantPlugin: import("@voyant-travel/core").VoyantGraphUnitManifest;
6
7
  export default financeVoyantModule;
package/dist/voyant.js CHANGED
@@ -364,22 +364,58 @@ export const financeVoyantModule = defineModule({
364
364
  ownership: "package",
365
365
  },
366
366
  });
367
- export const financeBookingTaxVoyantPlugin = defineExtension({
368
- id: "@voyant-travel/finance#booking-tax-extension",
367
+ // The booking-tax facets are two independent extensions so the selected-graph
368
+ // composition keeps them as separate composed extensions. Each `defineExtension`
369
+ // yields one composed extension keyed on its localId; declaring both api facets
370
+ // under a single extension would collapse them and drop the preview facet.
371
+ //
372
+ // Tax settings (GET/PATCH /tax-settings) live on the finance admin surface. On
373
+ // the managed runtime admin routes dispatch per-unit with prefix-first-match,
374
+ // so mounting them under `bookings` let the bookings `GET /{id}` route swallow
375
+ // `/tax-settings`; the finance surface already serves `/v1/admin/finance/*`
376
+ // settings safely.
377
+ export const financeBookingTaxSettingsVoyantPlugin = defineExtension({
378
+ id: "@voyant-travel/finance#booking-tax-settings-extension",
369
379
  packageName: "@voyant-travel/finance",
370
- localId: "finance.booking-tax-extension",
371
- runtime: { entry: "@voyant-travel/finance", export: "createBookingTaxVoyantRuntime" },
380
+ localId: "finance.booking-tax-settings-extension",
381
+ runtime: { entry: "@voyant-travel/finance", export: "createBookingTaxSettingsVoyantRuntime" },
372
382
  runtimePorts: [requirePort(financeOperatorSettingsRuntimePort)],
373
383
  api: [
374
384
  {
375
- id: "@voyant-travel/finance#booking-tax-extension.api",
385
+ id: "@voyant-travel/finance#booking-tax-settings-extension.api",
386
+ surface: "admin",
387
+ mount: "finance",
388
+ openapi: { document: "booking-tax-settings" },
389
+ transactional: true,
390
+ runtime: {
391
+ entry: "@voyant-travel/finance",
392
+ export: "createBookingTaxSettingsApiExtension",
393
+ },
394
+ },
395
+ ],
396
+ meta: {
397
+ ownership: "package",
398
+ },
399
+ });
400
+ // Tax preview (POST /tax-preview) stays on the bookings admin surface — POST
401
+ // does not collide with the bookings `GET /{id}` route and bookings-react
402
+ // consumes it at `/v1/admin/bookings/tax-preview`.
403
+ export const financeBookingTaxPreviewVoyantPlugin = defineExtension({
404
+ id: "@voyant-travel/finance#booking-tax-preview-extension",
405
+ packageName: "@voyant-travel/finance",
406
+ localId: "finance.booking-tax-preview-extension",
407
+ runtime: { entry: "@voyant-travel/finance", export: "createBookingTaxPreviewVoyantRuntime" },
408
+ runtimePorts: [requirePort(financeOperatorSettingsRuntimePort)],
409
+ api: [
410
+ {
411
+ id: "@voyant-travel/finance#booking-tax-preview-extension.api",
376
412
  surface: "admin",
377
413
  mount: "bookings",
378
- openapi: { document: "booking-tax" },
414
+ openapi: { document: "booking-tax-preview" },
379
415
  transactional: true,
380
416
  runtime: {
381
417
  entry: "@voyant-travel/finance",
382
- export: "createBookingTaxApiExtension",
418
+ export: "createBookingTaxPreviewApiExtension",
383
419
  },
384
420
  },
385
421
  ],
@@ -0,0 +1,33 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": {
4
+ "title": "Booking Tax Preview API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/v1/admin/bookings/tax-preview": {
9
+ "post": {
10
+ "summary": "Preview tax for a booking item subtotal",
11
+ "x-voyant-api-id": "@voyant-travel/finance#booking-tax-preview-extension.api",
12
+ "requestBody": {
13
+ "required": true,
14
+ "content": {
15
+ "application/json": {
16
+ "schema": {
17
+ "type": "object",
18
+ "required": ["productId", "subtotalCents", "currency"],
19
+ "properties": {
20
+ "productId": { "type": "string", "minLength": 1 },
21
+ "subtotalCents": { "type": "integer", "minimum": 0 },
22
+ "currency": { "type": "string", "minLength": 3, "maxLength": 8 }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "responses": { "200": { "description": "The calculated tax preview" } }
29
+ }
30
+ }
31
+ },
32
+ "components": { "schemas": {} }
33
+ }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
- "title": "Booking Tax API",
4
+ "title": "Booking Tax Settings API",
5
5
  "version": "1.0.0"
6
6
  },
7
7
  "paths": {
8
- "/v1/admin/bookings/tax-settings": {
8
+ "/v1/admin/finance/tax-settings": {
9
9
  "get": {
10
10
  "summary": "Get booking tax settings",
11
- "x-voyant-api-id": "@voyant-travel/finance#booking-tax-extension.api",
11
+ "x-voyant-api-id": "@voyant-travel/finance#booking-tax-settings-extension.api",
12
12
  "responses": {
13
13
  "200": {
14
14
  "description": "The effective booking tax settings",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "patch": {
28
28
  "summary": "Update booking tax settings",
29
- "x-voyant-api-id": "@voyant-travel/finance#booking-tax-extension.api",
29
+ "x-voyant-api-id": "@voyant-travel/finance#booking-tax-settings-extension.api",
30
30
  "requestBody": {
31
31
  "required": true,
32
32
  "content": {
@@ -36,8 +36,7 @@
36
36
  "properties": {
37
37
  "taxPriceMode": { "type": "string", "enum": ["inclusive", "exclusive"] },
38
38
  "taxPolicyProfileId": { "type": ["string", "null"] },
39
- "invoicingMode": { "type": "string", "enum": ["direct", "proforma-first"] },
40
- "fxReferenceSource": { "type": "string", "enum": ["ecb", "bnr"] }
39
+ "invoicingMode": { "type": "string", "enum": ["direct", "proforma-first"] }
41
40
  }
42
41
  }
43
42
  }
@@ -48,41 +47,17 @@
48
47
  "409": { "description": "This deployment does not support tax setting updates" }
49
48
  }
50
49
  }
51
- },
52
- "/v1/admin/bookings/tax-preview": {
53
- "post": {
54
- "summary": "Preview tax for a booking item subtotal",
55
- "x-voyant-api-id": "@voyant-travel/finance#booking-tax-extension.api",
56
- "requestBody": {
57
- "required": true,
58
- "content": {
59
- "application/json": {
60
- "schema": {
61
- "type": "object",
62
- "required": ["productId", "subtotalCents", "currency"],
63
- "properties": {
64
- "productId": { "type": "string", "minLength": 1 },
65
- "subtotalCents": { "type": "integer", "minimum": 0 },
66
- "currency": { "type": "string", "minLength": 3, "maxLength": 8 }
67
- }
68
- }
69
- }
70
- }
71
- },
72
- "responses": { "200": { "description": "The calculated tax preview" } }
73
- }
74
50
  }
75
51
  },
76
52
  "components": {
77
53
  "schemas": {
78
54
  "BookingTaxSettings": {
79
55
  "type": "object",
80
- "required": ["taxPriceMode", "taxPolicyProfileId", "invoicingMode", "fxReferenceSource"],
56
+ "required": ["taxPriceMode", "taxPolicyProfileId", "invoicingMode"],
81
57
  "properties": {
82
58
  "taxPriceMode": { "type": "string", "enum": ["inclusive", "exclusive"] },
83
59
  "taxPolicyProfileId": { "type": ["string", "null"] },
84
- "invoicingMode": { "type": "string", "enum": ["direct", "proforma-first"] },
85
- "fxReferenceSource": { "type": "string", "enum": ["ecb", "bnr"] }
60
+ "invoicingMode": { "type": "string", "enum": ["direct", "proforma-first"] }
86
61
  }
87
62
  }
88
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/finance",
3
- "version": "0.167.0",
3
+ "version": "0.169.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -140,7 +140,8 @@
140
140
  "default": "./dist/voyant.js"
141
141
  },
142
142
  "./openapi/admin": "./openapi/admin/finance.json",
143
- "./openapi/admin/booking-tax": "./openapi/admin/booking-tax.json",
143
+ "./openapi/admin/booking-tax-settings": "./openapi/admin/booking-tax-settings.json",
144
+ "./openapi/admin/booking-tax-preview": "./openapi/admin/booking-tax-preview.json",
144
145
  "./openapi/storefront": "./openapi/storefront/finance.json"
145
146
  },
146
147
  "dependencies": {
@@ -150,18 +151,18 @@
150
151
  "fflate": "^0.8.2",
151
152
  "hono": "^4.12.27",
152
153
  "zod": "^4.4.3",
154
+ "@voyant-travel/core": "^0.125.2",
153
155
  "@voyant-travel/action-ledger": "^0.111.1",
154
- "@voyant-travel/bookings": "^0.167.0",
155
- "@voyant-travel/core": "^0.125.0",
156
+ "@voyant-travel/bookings": "^0.169.0",
157
+ "@voyant-travel/types": "^0.109.4",
156
158
  "@voyant-travel/db": "^0.114.10",
157
- "@voyant-travel/types": "^0.109.3",
158
159
  "@voyant-travel/finance-contracts": "^0.106.2",
159
- "@voyant-travel/hono": "^0.128.1",
160
- "@voyant-travel/payments": "^0.2.1",
160
+ "@voyant-travel/hono": "^0.128.3",
161
161
  "@voyant-travel/public-document-delivery": "^0.4.1",
162
- "@voyant-travel/storage": "^0.111.1",
162
+ "@voyant-travel/payments": "^0.2.1",
163
+ "@voyant-travel/tools": "^0.3.0",
163
164
  "@voyant-travel/utils": "^0.107.1",
164
- "@voyant-travel/tools": "^0.3.0"
165
+ "@voyant-travel/storage": "^0.111.1"
165
166
  },
166
167
  "devDependencies": {
167
168
  "drizzle-kit": "^0.31.10",