@voyant-travel/finance 0.190.0 → 0.191.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.
@@ -41,6 +41,7 @@ export interface CardPaymentStartArgs {
41
41
  billing: CardPaymentBilling;
42
42
  description?: string;
43
43
  returnUrl?: string;
44
+ metadata?: Record<string, unknown>;
44
45
  }
45
46
  /** Result of a card-payment start: the hosted-payment URL to redirect to. */
46
47
  export interface CardPaymentStartResult {
@@ -68,4 +69,16 @@ export interface PaymentAdapterCardPaymentStarterOptions {
68
69
  */
69
70
  resolveNotifyUrl?(c: Context): string | undefined;
70
71
  }
72
+ export interface PaymentAdapterCardPaymentExecution {
73
+ context: PaymentAdapterRuntimeContext;
74
+ runtime?: FinanceServiceRuntime;
75
+ notifyUrl?: string;
76
+ idempotencyKey?: string;
77
+ }
78
+ /**
79
+ * Start a payment through a selected adapter without requiring an HTTP
80
+ * framework context. Package runtimes use this function; the Hono-oriented
81
+ * starter below remains the checkout-surface convenience wrapper.
82
+ */
83
+ export declare function startPaymentAdapterCardPayment(adapter: PaymentAdapter, args: CardPaymentStartArgs, execution: PaymentAdapterCardPaymentExecution): Promise<CardPaymentStartResult | null>;
71
84
  export declare function createPaymentAdapterCardPaymentStarter(adapter: PaymentAdapter, options?: PaymentAdapterCardPaymentStarterOptions): CardPaymentStarter;
@@ -2,58 +2,75 @@ import { eq } from "drizzle-orm";
2
2
  import { paymentSessions } from "./schema/payment-sessions.js";
3
3
  import { financePaymentSessionCompletionService } from "./service-payment-session-completion.js";
4
4
  import { financePaymentSessionService } from "./service-payment-sessions.js";
5
+ /**
6
+ * Start a payment through a selected adapter without requiring an HTTP
7
+ * framework context. Package runtimes use this function; the Hono-oriented
8
+ * starter below remains the checkout-surface convenience wrapper.
9
+ */
10
+ export async function startPaymentAdapterCardPayment(adapter, args, execution) {
11
+ const [session] = await args.db
12
+ .select()
13
+ .from(paymentSessions)
14
+ .where(eq(paymentSessions.id, args.sessionId))
15
+ .limit(1);
16
+ if (!session)
17
+ return null;
18
+ const idempotencyKey = session.idempotencyKey ?? execution.idempotencyKey ?? `payment:${session.id}`;
19
+ const metadata = {
20
+ ...(args.metadata ?? {}),
21
+ billing: args.billing,
22
+ ...(execution.notifyUrl ? { notifyUrl: execution.notifyUrl } : {}),
23
+ };
24
+ const result = await adapter.initiate(execution.context, {
25
+ paymentSessionId: session.id,
26
+ money: { amountMinor: session.amountCents, currency: session.currency },
27
+ description: args.description ?? session.notes ?? undefined,
28
+ returnUrl: args.returnUrl ?? session.returnUrl ?? undefined,
29
+ idempotencyKey,
30
+ customer: {
31
+ email: args.billing.email,
32
+ phone: args.billing.phone ?? null,
33
+ firstName: args.billing.firstName,
34
+ lastName: args.billing.lastName ?? null,
35
+ },
36
+ metadata,
37
+ });
38
+ const providerData = {
39
+ providerSessionId: result.processorSessionId ?? undefined,
40
+ providerPaymentId: result.processorPaymentId ?? undefined,
41
+ providerPayload: result.raw === undefined ? undefined : { initiation: result.raw },
42
+ metadata: { paymentAdapterInitiationIdempotencyKey: result.idempotencyKey },
43
+ };
44
+ if (result.nextState === "paid" || result.nextState === "authorized") {
45
+ await financePaymentSessionService.updatePaymentSession(args.db, session.id, {
46
+ provider: adapter.id,
47
+ redirectUrl: result.checkout?.url ?? undefined,
48
+ idempotencyKey,
49
+ });
50
+ await financePaymentSessionCompletionService.completePaymentSession(args.db, session.id, {
51
+ status: result.nextState,
52
+ captureMode: "automatic",
53
+ ...providerData,
54
+ }, execution.runtime);
55
+ }
56
+ else {
57
+ await financePaymentSessionService.updatePaymentSession(args.db, session.id, {
58
+ provider: adapter.id,
59
+ status: result.nextState,
60
+ redirectUrl: result.checkout?.url ?? undefined,
61
+ idempotencyKey,
62
+ ...providerData,
63
+ });
64
+ }
65
+ return { redirectUrl: result.checkout?.url ?? null };
66
+ }
5
67
  export function createPaymentAdapterCardPaymentStarter(adapter, options = {}) {
6
68
  return async (c, args) => {
7
- const [session] = await args.db
8
- .select()
9
- .from(paymentSessions)
10
- .where(eq(paymentSessions.id, args.sessionId))
11
- .limit(1);
12
- if (!session)
13
- return null;
14
- const idempotencyKey = session.idempotencyKey ?? options.idempotencyKey?.(session.id) ?? `payment:${session.id}`;
15
- const notifyUrl = options.resolveNotifyUrl?.(c);
16
- const result = await adapter.initiate(options.resolveContext?.(c) ?? { env: c.env }, {
17
- paymentSessionId: session.id,
18
- money: { amountMinor: session.amountCents, currency: session.currency },
19
- description: args.description ?? session.notes ?? undefined,
20
- returnUrl: args.returnUrl ?? session.returnUrl ?? undefined,
21
- idempotencyKey,
22
- customer: {
23
- email: args.billing.email,
24
- phone: args.billing.phone ?? null,
25
- firstName: args.billing.firstName,
26
- lastName: args.billing.lastName ?? null,
27
- },
28
- ...(notifyUrl ? { metadata: { notifyUrl } } : {}),
69
+ return startPaymentAdapterCardPayment(adapter, args, {
70
+ context: options.resolveContext?.(c) ?? { env: c.env },
71
+ runtime: options.resolveRuntime?.(c),
72
+ notifyUrl: options.resolveNotifyUrl?.(c),
73
+ idempotencyKey: options.idempotencyKey?.(args.sessionId),
29
74
  });
30
- const providerData = {
31
- providerSessionId: result.processorSessionId ?? undefined,
32
- providerPaymentId: result.processorPaymentId ?? undefined,
33
- providerPayload: result.raw === undefined ? undefined : { initiation: result.raw },
34
- metadata: { paymentAdapterInitiationIdempotencyKey: result.idempotencyKey },
35
- };
36
- if (result.nextState === "paid" || result.nextState === "authorized") {
37
- await financePaymentSessionService.updatePaymentSession(args.db, session.id, {
38
- provider: adapter.id,
39
- redirectUrl: result.checkout?.url ?? undefined,
40
- idempotencyKey,
41
- });
42
- await financePaymentSessionCompletionService.completePaymentSession(args.db, session.id, {
43
- status: result.nextState,
44
- captureMode: "automatic",
45
- ...providerData,
46
- }, options.resolveRuntime?.(c));
47
- }
48
- else {
49
- await financePaymentSessionService.updatePaymentSession(args.db, session.id, {
50
- provider: adapter.id,
51
- status: result.nextState,
52
- redirectUrl: result.checkout?.url ?? undefined,
53
- idempotencyKey,
54
- ...providerData,
55
- });
56
- }
57
- return { redirectUrl: result.checkout?.url ?? null };
58
75
  };
59
76
  }
@@ -15,6 +15,7 @@ export type CheckoutRoutesOptions = {
15
15
  notificationDispatcher?: CheckoutNotificationDispatcher | null;
16
16
  resolveNotificationDispatcher?: (bindings: Record<string, unknown>) => CheckoutNotificationDispatcher | null;
17
17
  paymentStarters?: Record<string, CheckoutPaymentStarter>;
18
+ resolveSelectedPaymentStarter?: (bindings: Record<string, unknown>) => CheckoutPaymentStarter | null;
18
19
  resolvePaymentStarters?: (bindings: Record<string, unknown>) => Record<string, CheckoutPaymentStarter>;
19
20
  bankTransferDetails?: CheckoutBankTransferDetails | null;
20
21
  resolveBankTransferDetails?: (bindings: Record<string, unknown>) => CheckoutBankTransferDetails | null;
@@ -31,6 +32,7 @@ export interface CheckoutReminderRunList {
31
32
  export type CheckoutRouteRuntime = {
32
33
  bindings: Record<string, unknown>;
33
34
  notificationDispatcher: CheckoutNotificationDispatcher | null;
35
+ selectedPaymentStarter: CheckoutPaymentStarter | null;
34
36
  paymentStarters: Record<string, CheckoutPaymentStarter>;
35
37
  bankTransferDetails: CheckoutBankTransferDetails | null;
36
38
  publicCheckoutBaseUrl?: string | null;
@@ -121,7 +121,9 @@ function resolveCheckoutRouteRuntime(bindings, options, container) {
121
121
  return buildCheckoutRouteRuntime(bindings, options);
122
122
  }
123
123
  function assertCheckoutRuntimeSupportsCollection(runtime, input) {
124
- if (input.method === "card" && Object.keys(runtime.paymentStarters).length === 0) {
124
+ if (input.method === "card" &&
125
+ !runtime.selectedPaymentStarter &&
126
+ Object.keys(runtime.paymentStarters).length === 0) {
125
127
  throw new CheckoutRouteRuntimeNotConfiguredError();
126
128
  }
127
129
  }
@@ -129,6 +131,7 @@ export function buildCheckoutRouteRuntime(bindings, options = {}) {
129
131
  return {
130
132
  bindings,
131
133
  notificationDispatcher: options.resolveNotificationDispatcher?.(bindings) ?? options.notificationDispatcher ?? null,
134
+ selectedPaymentStarter: options.resolveSelectedPaymentStarter?.(bindings) ?? null,
132
135
  paymentStarters: options.resolvePaymentStarters?.(bindings) ?? options.paymentStarters ?? {},
133
136
  bankTransferDetails: options.resolveBankTransferDetails?.(bindings) ?? options.bankTransferDetails ?? null,
134
137
  publicCheckoutBaseUrl: options.resolvePublicCheckoutBaseUrl?.(bindings) ?? options.publicCheckoutBaseUrl ?? null,
@@ -105,6 +105,8 @@ export interface CheckoutRuntimeOptions {
105
105
  bindings?: Record<string, unknown>;
106
106
  bankTransferDetails?: CheckoutBankTransferDetails | null;
107
107
  notificationDispatcher?: CheckoutNotificationDispatcher | null;
108
+ /** Deployment-selected PaymentAdapter bridge. Takes precedence over legacy keyed starters. */
109
+ selectedPaymentStarter?: CheckoutPaymentStarter | null;
108
110
  paymentStarters?: Record<string, CheckoutPaymentStarter>;
109
111
  publicCheckoutBaseUrl?: string | null;
110
112
  }
@@ -256,9 +256,16 @@ export async function initiateCheckoutCollection(db, bookingId, input, options =
256
256
  if (!paymentSession) {
257
257
  throw new Error("No payment session available for provider start");
258
258
  }
259
- const starter = runtime.paymentStarters?.[input.startProvider.provider];
259
+ // Processor selection belongs to the deployment. A selected PaymentAdapter
260
+ // therefore wins even when an older client still sends a provider hint.
261
+ // Keyed starters remain as a compatibility path for self-hosted plugins.
262
+ const requestedProvider = input.startProvider.provider;
263
+ const starter = runtime.selectedPaymentStarter ??
264
+ (requestedProvider ? runtime.paymentStarters?.[requestedProvider] : undefined);
260
265
  if (!starter) {
261
- throw new Error(`Payment provider "${input.startProvider.provider}" is not configured`);
266
+ throw new Error(requestedProvider
267
+ ? `Payment provider "${requestedProvider}" is not configured`
268
+ : "No payment adapter is selected for card collection");
262
269
  }
263
270
  providerStart = await starter({
264
271
  db,
@@ -46,7 +46,7 @@ export declare const checkoutReminderTargetTypeSchema: z.ZodEnum<{
46
46
  booking_cancelled_non_payment: "booking_cancelled_non_payment";
47
47
  }>;
48
48
  export declare const checkoutProviderStartInputSchema: z.ZodObject<{
49
- provider: z.ZodString;
49
+ provider: z.ZodOptional<z.ZodString>;
50
50
  payload: z.ZodNullable<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
51
51
  }, z.core.$strip>;
52
52
  export declare const checkoutNotificationAttachmentSchema: z.ZodObject<{
@@ -407,7 +407,7 @@ export declare const initiateCheckoutCollectionSchema: z.ZodObject<{
407
407
  paymentLinkBaseUrl: z.ZodNullable<z.ZodOptional<z.ZodString>>;
408
408
  }, z.core.$strip>>;
409
409
  startProvider: z.ZodOptional<z.ZodObject<{
410
- provider: z.ZodString;
410
+ provider: z.ZodOptional<z.ZodString>;
411
411
  payload: z.ZodNullable<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
412
412
  }, z.core.$strip>>;
413
413
  notes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
@@ -659,7 +659,7 @@ export declare const bootstrapCheckoutCollectionSchema: z.ZodObject<{
659
659
  paymentLinkBaseUrl: z.ZodNullable<z.ZodOptional<z.ZodString>>;
660
660
  }, z.core.$strip>>;
661
661
  startProvider: z.ZodOptional<z.ZodObject<{
662
- provider: z.ZodString;
662
+ provider: z.ZodOptional<z.ZodString>;
663
663
  payload: z.ZodNullable<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
664
664
  }, z.core.$strip>>;
665
665
  notes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
@@ -28,7 +28,12 @@ export const checkoutReminderTargetTypeSchema = z.enum([
28
28
  "invoice",
29
29
  ]);
30
30
  export const checkoutProviderStartInputSchema = z.object({
31
- provider: z.string().min(1).max(255),
31
+ /**
32
+ * Legacy provider hint. Deployments with a selected PaymentAdapter ignore
33
+ * this value and always use their selected adapter. New clients should omit
34
+ * it so processor selection remains deployment-owned.
35
+ */
36
+ provider: z.string().min(1).max(255).optional(),
32
37
  payload: z.record(z.string(), z.unknown()).optional().nullable(),
33
38
  });
34
39
  export const checkoutNotificationAttachmentSchema = z
package/dist/index.d.ts CHANGED
@@ -28,8 +28,8 @@ export { PAYMENT_ADAPTER_CONTRACT_VERSION, PAYMENT_ADAPTER_RUNTIME_PORT_ID, paym
28
28
  export { type BookingCancellationSettlementInput, buildPaidBookingCancellationSettlementNote, closeTerminalBookingPaymentSchedules, financeBookingLifecycle, recordPaidBookingCancellationSettlement, } from "./booking-lifecycle.js";
29
29
  export { type BookingTaxRouteOptions, type BookingTaxSettings, computeBookingItemTaxLine, createBookingTaxPreviewApiExtension, createBookingTaxPreviewRoutes, createBookingTaxSettingsApiExtension, createBookingTaxSettingsRoutes, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxPreviewRoutes, mountBookingTaxSettingsRoutes, type ProductTaxFacts, type ResolveBookingSellTaxRateOptions, type ResolveBookingTaxSettings, type ResolvedBookingSellTaxRate, resolveBookingSellTaxRate, type TaxPolicyCondition, type UpdateBookingTaxSettings, } from "./booking-tax.js";
30
30
  export { BOOKING_TAX_PREVIEW_RUNTIME_KEY, BOOKING_TAX_SETTINGS_RUNTIME_KEY, type BookingTaxRuntime, createBookingTaxPreviewVoyantRuntime, createBookingTaxSettingsVoyantRuntime, } from "./booking-tax-runtime.js";
31
- export type { CardPaymentBilling, CardPaymentStartArgs, CardPaymentStarter, CardPaymentStartResult, PaymentAdapterCardPaymentStarterOptions, } from "./card-payment.js";
32
- export { createPaymentAdapterCardPaymentStarter } from "./card-payment.js";
31
+ export type { CardPaymentBilling, CardPaymentStartArgs, CardPaymentStarter, CardPaymentStartResult, PaymentAdapterCardPaymentExecution, PaymentAdapterCardPaymentStarterOptions, } from "./card-payment.js";
32
+ export { createPaymentAdapterCardPaymentStarter, startPaymentAdapterCardPayment, } from "./card-payment.js";
33
33
  export { type DocumentDownloadEnvelope, type DocumentDownloadResolution, type DocumentDownloadResolver, resolveStoredDocumentDownload, type StoredDocumentReference, } from "./document-download.js";
34
34
  export { createInvoiceFxApiExtension, createInvoiceFxRoutes, createVoyantDataFxExchangeRateResolver, type InvoiceExchangeRateResolution, type InvoiceFxContext, type InvoiceFxOptions, type InvoiceFxRouteOptions, type InvoiceFxSettings, mountInvoiceFxRoutes, type ResolvedInvoiceFxSettings, type ResolveInvoiceExchangeRate, type ResolveInvoiceExchangeRateInput, type ResolveInvoiceFxSettings, resolveInvoiceFxContext, resolveInvoiceFxSettingsOrDefault, type UpdateInvoiceFxSettings, type VoyantDataFxResolverOptions, } from "./invoice-fx.js";
35
35
  export { type CreateOrderPaymentSessionsOptions, createOrderPaymentSessions, type EnsureOrderSessionParams, type OrderPaymentSessionSummary, type OrderPaymentSessions, type OrderPaymentSessionTargetType, type StartOrderPaymentProvider, } from "./order-payment-sessions.js";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { registerBookingFinancialLifecycle } from "@voyant-travel/bookings";
4
4
  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
+ import { paymentAdapterRuntimePort } from "@voyant-travel/payments";
7
8
  import { financeBookingLifecycle } from "./booking-lifecycle.js";
8
9
  import { createBookingTaxSettingsRoutes } from "./booking-tax.js";
9
10
  import { buildFinanceCheckoutRouteRuntime, createFinanceCheckoutAdminRoutes, createFinanceCheckoutRoutes, FINANCE_CHECKOUT_ROUTE_RUNTIME_CONTAINER_KEY, } from "./checkout-routes.js";
@@ -68,7 +69,9 @@ export const financeApiModule = createFinanceApiModule();
68
69
  export const createFinanceVoyantRuntime = defineGraphRuntimeFactory(async ({ api, getPort, getPorts, hasPort }) => {
69
70
  const configured = createFinanceApiModule(createFinanceRuntime(await getPort(financeHostRuntimePort), await getPort(customFieldsRuntimePort), await getPort(financeNotificationsRuntimePort), hasPort(financeCheckoutPaymentStartersRuntimePort)
70
71
  ? await getPort(financeCheckoutPaymentStartersRuntimePort)
71
- : undefined, await getPorts(financeInvoiceSettlementPollerRuntimePort)));
72
+ : undefined, await getPorts(financeInvoiceSettlementPollerRuntimePort), hasPort(paymentAdapterRuntimePort)
73
+ ? await getPort(paymentAdapterRuntimePort)
74
+ : undefined));
72
75
  const bootstrap = configured.module.bootstrap;
73
76
  const selected = {
74
77
  module: {
@@ -91,7 +94,7 @@ export { PAYMENT_ADAPTER_CONTRACT_VERSION, PAYMENT_ADAPTER_RUNTIME_PORT_ID, paym
91
94
  export { buildPaidBookingCancellationSettlementNote, closeTerminalBookingPaymentSchedules, financeBookingLifecycle, recordPaidBookingCancellationSettlement, } from "./booking-lifecycle.js";
92
95
  export { computeBookingItemTaxLine, createBookingTaxPreviewApiExtension, createBookingTaxPreviewRoutes, createBookingTaxSettingsApiExtension, createBookingTaxSettingsRoutes, loadProductTaxFacts, matchesTaxPolicyCondition, mountBookingTaxPreviewRoutes, mountBookingTaxSettingsRoutes, resolveBookingSellTaxRate, } from "./booking-tax.js";
93
96
  export { BOOKING_TAX_PREVIEW_RUNTIME_KEY, BOOKING_TAX_SETTINGS_RUNTIME_KEY, createBookingTaxPreviewVoyantRuntime, createBookingTaxSettingsVoyantRuntime, } from "./booking-tax-runtime.js";
94
- export { createPaymentAdapterCardPaymentStarter } from "./card-payment.js";
97
+ export { createPaymentAdapterCardPaymentStarter, startPaymentAdapterCardPayment, } from "./card-payment.js";
95
98
  export { resolveStoredDocumentDownload, } from "./document-download.js";
96
99
  export { createInvoiceFxApiExtension, createInvoiceFxRoutes, createVoyantDataFxExchangeRateResolver, mountInvoiceFxRoutes, resolveInvoiceFxContext, resolveInvoiceFxSettingsOrDefault, } from "./invoice-fx.js";
97
100
  export { createOrderPaymentSessions, } from "./order-payment-sessions.js";
package/dist/runtime.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { CustomFieldsRuntime } from "@voyant-travel/core/custom-fields";
2
+ import type { PaymentAdapter } from "@voyant-travel/payments";
2
3
  import type { FinanceApiModuleOptions } from "./index.js";
3
4
  import type { FinanceAccommodationsPaymentPolicyRuntime, FinanceBookingScheduleRuntime, FinanceCheckoutPaymentStartersRuntime, FinanceCruisesPaymentPolicyRuntime, FinanceDistributionPaymentPolicyRuntime, FinanceHostRuntime, FinanceInventoryPaymentPolicyRuntime, FinanceInvoiceSettlementPollerProvider, FinanceNotificationsRuntime, FinanceOperatorSettingsRuntime } from "./runtime-port.js";
4
5
  import type { InvoiceSettlementPoller } from "./service-settlement.js";
5
6
  /** Compose Finance's main HTTP runtime from generic host and selected providers. */
6
- export declare function createFinanceRuntime(host: FinanceHostRuntime, customFields: CustomFieldsRuntime, notifications: FinanceNotificationsRuntime, checkoutPaymentStarters?: FinanceCheckoutPaymentStartersRuntime, invoiceSettlementPollerProviders?: readonly FinanceInvoiceSettlementPollerProvider[]): FinanceApiModuleOptions;
7
+ export declare function createFinanceRuntime(host: FinanceHostRuntime, customFields: CustomFieldsRuntime, notifications: FinanceNotificationsRuntime, checkoutPaymentStarters?: FinanceCheckoutPaymentStartersRuntime, invoiceSettlementPollerProviders?: readonly FinanceInvoiceSettlementPollerProvider[], selectedPaymentAdapter?: PaymentAdapter): FinanceApiModuleOptions;
7
8
  /** Compose Finance's payment schedule from statically selected domain providers. */
8
9
  export declare function createFinanceBookingScheduleRuntime(host: FinanceHostRuntime, settings: FinanceOperatorSettingsRuntime, distribution: FinanceDistributionPaymentPolicyRuntime, accommodations: FinanceAccommodationsPaymentPolicyRuntime, cruises: FinanceCruisesPaymentPolicyRuntime, inventory: FinanceInventoryPaymentPolicyRuntime): FinanceBookingScheduleRuntime;
9
10
  export declare function createFinanceBookingTaxRuntime(settings: FinanceOperatorSettingsRuntime): FinanceApiModuleOptions;
package/dist/runtime.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { startPaymentAdapterCardPayment } from "./card-payment.js";
1
2
  import { createVoyantDataFxExchangeRateResolver, } from "./invoice-fx.js";
3
+ import { financeService } from "./service.js";
2
4
  /** Compose Finance's main HTTP runtime from generic host and selected providers. */
3
- export function createFinanceRuntime(host, customFields, notifications, checkoutPaymentStarters, invoiceSettlementPollerProviders = []) {
5
+ export function createFinanceRuntime(host, customFields, notifications, checkoutPaymentStarters, invoiceSettlementPollerProviders = [], selectedPaymentAdapter) {
4
6
  const { primitives } = host;
5
7
  return {
6
8
  resolveDocumentDownloadUrl: primitives.storage.downloadUrl,
@@ -18,11 +20,93 @@ export function createFinanceRuntime(host, customFields, notifications, checkout
18
20
  invoiceDueDateResolver: ({ issueDate, dueDate, bookingPaymentSchedule }) => bookingPaymentSchedule && dueDate < issueDate ? issueDate : dueDate,
19
21
  resolveNotificationDispatcher: notifications.resolveNotificationDispatcher,
20
22
  resolvePaymentStarters: (bindings) => checkoutPaymentStarters?.resolvePaymentStarters(bindings) ?? {},
23
+ resolveSelectedPaymentStarter: selectedPaymentAdapter
24
+ ? (bindings) => createSelectedPaymentAdapterStarter(host, selectedPaymentAdapter, bindings)
25
+ : undefined,
21
26
  resolveBankTransferDetails: (bindings) => resolveBankTransferDetails(primitives.env(bindings)),
22
27
  resolvePublicCheckoutBaseUrl: (bindings) => resolvePublicCheckoutBaseUrl(primitives.env(bindings)),
23
28
  listBookingReminderRuns: notifications.listBookingReminderRuns,
24
29
  };
25
30
  }
31
+ function createSelectedPaymentAdapterStarter(host, adapter, bindings) {
32
+ return async (checkout) => {
33
+ const payload = checkout.startProvider.payload ?? {};
34
+ const billing = readCardPaymentBilling(payload.billing);
35
+ const env = host.primitives.env(bindings);
36
+ const started = await startPaymentAdapterCardPayment(adapter, {
37
+ db: checkout.db,
38
+ sessionId: checkout.paymentSession.id,
39
+ billing,
40
+ description: stringValue(payload.description),
41
+ returnUrl: stringValue(payload.returnUrl) ?? checkout.paymentSession.returnUrl ?? undefined,
42
+ metadata: recordValue(payload.metadata),
43
+ }, {
44
+ context: { env },
45
+ notifyUrl: resolvePaymentCallbackUrl(env),
46
+ });
47
+ const session = (await financeService.getPaymentSessionById(checkout.db, checkout.paymentSession.id)) ??
48
+ checkout.paymentSession;
49
+ return {
50
+ provider: adapter.id,
51
+ paymentSessionId: session.id,
52
+ redirectUrl: started?.redirectUrl ?? null,
53
+ externalReference: session.externalReference,
54
+ providerSessionId: session.providerSessionId,
55
+ providerPaymentId: session.providerPaymentId,
56
+ response: null,
57
+ };
58
+ };
59
+ }
60
+ function readCardPaymentBilling(value) {
61
+ const billing = recordValue(value);
62
+ const email = nonEmpty(billing.email);
63
+ const firstName = nonEmpty(billing.firstName);
64
+ if (!email || !firstName) {
65
+ throw new Error("Card payment billing requires email and firstName");
66
+ }
67
+ const phone = nonEmpty(billing.phone);
68
+ const lastName = nonEmpty(billing.lastName);
69
+ const city = nonEmpty(billing.city);
70
+ const country = typeof billing.country === "number" || typeof billing.country === "string"
71
+ ? billing.country
72
+ : undefined;
73
+ const state = nonEmpty(billing.state);
74
+ const postalCode = nonEmpty(billing.postalCode);
75
+ const details = nonEmpty(billing.details);
76
+ return {
77
+ email,
78
+ firstName,
79
+ ...(phone ? { phone } : {}),
80
+ ...(lastName ? { lastName } : {}),
81
+ ...(city ? { city } : {}),
82
+ ...(country !== undefined ? { country } : {}),
83
+ ...(state ? { state } : {}),
84
+ ...(postalCode ? { postalCode } : {}),
85
+ ...(details ? { details } : {}),
86
+ };
87
+ }
88
+ function recordValue(value) {
89
+ return value && typeof value === "object" && !Array.isArray(value)
90
+ ? value
91
+ : {};
92
+ }
93
+ function resolvePaymentCallbackUrl(env) {
94
+ const configured = nonEmpty(env.PAYMENT_CALLBACK_BASE_URL) ??
95
+ nonEmpty(env.DASH_BASE_URL) ??
96
+ nonEmpty(env.APP_URL)?.replace(/\/api\/?$/, "");
97
+ if (!configured)
98
+ return undefined;
99
+ const url = new URL(configured);
100
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
101
+ url.username ||
102
+ url.password ||
103
+ url.pathname !== "/" ||
104
+ url.search ||
105
+ url.hash) {
106
+ throw new Error("Payment callback base must be an absolute HTTP(S) origin");
107
+ }
108
+ return `${url.origin}/api/v1/public/payment-link/callback`;
109
+ }
26
110
  /** Compose Finance's payment schedule from statically selected domain providers. */
27
111
  export function createFinanceBookingScheduleRuntime(host, settings, distribution, accommodations, cruises, inventory) {
28
112
  const paymentPolicy = inventory.createPaymentPolicyRuntime({
package/dist/voyant.js CHANGED
@@ -8,6 +8,7 @@ import { financeReceivablesDatasetDefinition, financeReportingTemplates, finance
8
8
  import { financeAccommodationsPaymentPolicyRuntimePort, financeCheckoutPaymentStartersRuntimePort, financeCruisesPaymentPolicyRuntimePort, financeDistributionPaymentPolicyRuntimePort, financeHostRuntimePort, financeInventoryPaymentPolicyRuntimePort, financeInvoiceSettlementPollerRuntimePort, financeNotificationsRuntimePort, financeOperatorSettingsRuntimePort, } from "./runtime-port.js";
9
9
  import { financeVoyantAdmin } from "./voyant-admin.js";
10
10
  import { bookingContractDocumentRequestedPayloadSchema, bookingCreatedPayloadSchema, bookingCreateRejectedPayloadSchema, bookingDualCreatedPayloadSchema, bookingPaymentSchedulePaidPayloadSchema, invoiceDocumentGeneratedPayloadSchema, invoiceIssuanceExternalPayloadSchema, invoicePaymentRecordedExternalPayloadSchema, invoiceProformaConvertedExternalPayloadSchema, invoiceRenderedPayloadSchema, invoiceSettledPayloadSchema, invoiceVoidedExternalPayloadSchema, paymentCompletedPayloadSchema, } from "./voyant-event-schemas.js";
11
+ const paymentAdapterRuntimePortReference = { id: "payments.adapter.runtime" };
11
12
  /** Import-cheap deployment declaration owned by the finance package. */
12
13
  export const financeVoyantModule = defineModule({
13
14
  id: "@voyant-travel/finance",
@@ -19,6 +20,7 @@ export const financeVoyantModule = defineModule({
19
20
  requirePort(customFieldsRuntimePort),
20
21
  requirePort(financeNotificationsRuntimePort),
21
22
  requirePort(financeCheckoutPaymentStartersRuntimePort, { optional: true }),
23
+ { ...paymentAdapterRuntimePortReference, optional: true },
22
24
  requirePort(financeInvoiceSettlementPollerRuntimePort, {
23
25
  optional: true,
24
26
  cardinality: "many",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/finance",
3
- "version": "0.190.0",
3
+ "version": "0.191.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -157,7 +157,7 @@
157
157
  "hono": "^4.12.27",
158
158
  "zod": "^4.4.3",
159
159
  "@voyant-travel/action-ledger": "^0.111.13",
160
- "@voyant-travel/bookings": "^0.190.0",
160
+ "@voyant-travel/bookings": "^0.191.0",
161
161
  "@voyant-travel/core": "^0.131.0",
162
162
  "@voyant-travel/db": "^0.118.0",
163
163
  "@voyant-travel/types": "^0.109.9",