import type { EventBus } from "@voyant-travel/core"; import type { StorefrontIntakeContext } from "@voyant-travel/relationships-contracts/storefront-intake"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import { type StorefrontCustomerSignalCreatedEvent, type StorefrontIntakeGuard, type StorefrontIntakeOptions, type StorefrontIntakePersistence, type StorefrontIntakePersistenceResolver, type StorefrontIntakePerson, type StorefrontIntakeSignal } from "./service-intake.js"; import { type StorefrontDepartureListQuery, type StorefrontDeparturePricePreviewInput, type StorefrontLeadIntakeInput, type StorefrontNewsletterSubscribeInput, type StorefrontOfferApplyInput, type StorefrontOfferMutationResult, type StorefrontOfferRedeemInput, type StorefrontProductAvailabilitySummaryQuery, type StorefrontPromotionalOffer, type StorefrontSettings, type StorefrontSettingsInput, type StorefrontSettingsPatchInput } from "./validation.js"; import type { StorefrontTransportEligibilityInput, StorefrontTransportEligibilityResult, StorefrontTransportEligibilityRuleInput } from "./validation-transport-eligibility.js"; export interface StorefrontServiceOptions { settings?: StorefrontSettingsInput; resolveSettings?: (context: StorefrontRequestContext) => Promise | StorefrontSettingsInput; updateSettings?: (input: StorefrontSettings, context: StorefrontRequestContext) => Promise | StorefrontSettingsInput | StorefrontSettings; offers?: StorefrontOfferResolvers; resolveOffers?: (context: StorefrontRequestContext) => Promise | StorefrontOfferResolvers | null | undefined; transportEligibilityRules?: StorefrontTransportEligibilityRuleInput[]; resolveTransportEligibilityRules?: (input: { departureId: string; productId?: string | null; travelStartsOn?: string | null; travelEndsOn?: string | null; } & StorefrontRequestContext) => Promise | StorefrontTransportEligibilityRuleInput[]; intake?: StorefrontIntakeOptions; publication?: StorefrontPublicationGuard; } export interface StorefrontRequestContext extends StorefrontIntakeContext { db?: PostgresJsDatabase; eventBus?: EventBus; env?: unknown; context?: unknown; /** Server-derived storefront identity; never sourced from public params. */ storefrontId?: string | null; /** Server-derived sales channel bound to the storefront identity. */ channelId?: string | null; channelStatus?: string | null; } export interface StorefrontPublicationGuard { isProductPublished(input: { productId: string; context: StorefrontRequestContext; }): Promise | boolean; } export interface StorefrontOfferResolvers { listApplicableOffers?: (input: { productId: string; departureId?: string; locale?: string; } & StorefrontRequestContext) => Promise | StorefrontPromotionalOffer[]; getOfferBySlug?: (input: { slug: string; locale?: string; } & StorefrontRequestContext) => Promise | StorefrontPromotionalOffer | null; applyOffer?: (input: { slug: string; body: StorefrontOfferApplyInput; } & StorefrontRequestContext) => Promise | StorefrontOfferMutationResult; redeemOffer?: (input: { body: StorefrontOfferRedeemInput; } & StorefrontRequestContext) => Promise | StorefrontOfferMutationResult; } export declare function resolveStorefrontSettings(input?: StorefrontSettingsInput): StorefrontSettings; export declare function mergeStorefrontSettingsPatch(current: StorefrontSettings, patch: StorefrontSettingsPatchInput): StorefrontSettings; export declare function createStorefrontService(options?: StorefrontServiceOptions): { getSettings(): StorefrontSettings; resolveSettings: (context?: StorefrontRequestContext) => Promise<{ support: { email: string | null; phone: string | null; links: { label: string; url: string; }[]; }; legal: { termsUrl: string | null; privacyUrl: string | null; cancellationUrl: string | null; defaultContractTemplateId: string | null; }; localization: { defaultLocale: string | null; currencyDisplay: "symbol" | "name" | "code"; }; forms: { billing: { fields: { key: string; label: string; type: "date" | "email" | "select" | "text" | "tel" | "textarea" | "checkbox" | "country"; required: boolean; placeholder: string | null; description: string | null; autocomplete: string | null; options: { value: string; label: string; }[]; }[]; }; travelers: { fields: { key: string; label: string; type: "date" | "email" | "select" | "text" | "tel" | "textarea" | "checkbox" | "country"; required: boolean; placeholder: string | null; description: string | null; autocomplete: string | null; options: { value: string; label: string; }[]; }[]; }; }; payment: { defaultMethod: "card" | "bank_transfer" | "cash" | "travel_credit" | "invoice" | null; methods: { code: "card" | "bank_transfer" | "cash" | "travel_credit" | "invoice"; label: string; description: string | null; enabled: boolean; }[]; structure: "split" | "full"; schedule: { percent: number; dueInDays: number; dueCondition: "after_booking" | "before_departure"; }[]; defaultSchedule: { depositPercent: number | null; balanceDueDaysBeforeDeparture: number | null; } | null; bankTransfer: { dueDays: number | null; account: { provider: string | null; currency: string | null; iban: string; beneficiary: string; bank: string; } | null; accountHolder: string | null; bankName: string | null; iban: string | null; bic: string | null; paymentReference: string | null; instructions: string | null; } | null; }; }>; updateSettings: (patch: StorefrontSettingsPatchInput, context?: StorefrontRequestContext) => Promise<{ support: { email: string | null; phone: string | null; links: { label: string; url: string; }[]; }; legal: { termsUrl: string | null; privacyUrl: string | null; cancellationUrl: string | null; defaultContractTemplateId: string | null; }; localization: { defaultLocale: string | null; currencyDisplay: "symbol" | "name" | "code"; }; forms: { billing: { fields: { key: string; label: string; type: "date" | "email" | "select" | "text" | "tel" | "textarea" | "checkbox" | "country"; required: boolean; placeholder: string | null; description: string | null; autocomplete: string | null; options: { value: string; label: string; }[]; }[]; }; travelers: { fields: { key: string; label: string; type: "date" | "email" | "select" | "text" | "tel" | "textarea" | "checkbox" | "country"; required: boolean; placeholder: string | null; description: string | null; autocomplete: string | null; options: { value: string; label: string; }[]; }[]; }; }; payment: { defaultMethod: "card" | "bank_transfer" | "cash" | "travel_credit" | "invoice" | null; methods: { code: "card" | "bank_transfer" | "cash" | "travel_credit" | "invoice"; label: string; description: string | null; enabled: boolean; }[]; structure: "split" | "full"; schedule: { percent: number; dueInDays: number; dueCondition: "after_booking" | "before_departure"; }[]; defaultSchedule: { depositPercent: number | null; balanceDueDaysBeforeDeparture: number | null; } | null; bankTransfer: { dueDays: number | null; account: { provider: string | null; currency: string | null; iban: string; beneficiary: string; bank: string; } | null; accountHolder: string | null; bankName: string | null; iban: string | null; bic: string | null; paymentReference: string | null; instructions: string | null; } | null; }; } | null>; getDeparture(db: PostgresJsDatabase, departureId: string): Promise<{ id: string; productId: string; itineraryId: string; optionId: string | null; dateLocal: string | null; startAt: string | null; endAt: string | null; timezone: string; startTime: { id: string; label: string | null; startTimeLocal: string; durationMinutes: number | null; } | null; meetingPoint: string | null; capacity: number | null; remaining: number | null; departureStatus: import("./service-boundary-sql.js").StorefrontSlotStatus | "on_request"; nights: number | null; days: number | null; ratePlans: { id: string; active: boolean; name: string; pricingModel: string; basePrices: { amount: number; currencyCode: string; }[]; roomPrices: { amount: number; currencyCode: string; roomType: { id: string; name: string; occupancy: { adultsMin: number; adultsMax: number; childrenMax: number; }; }; }[]; }[]; resourceManifest: { kinds: { capacity: number; assigned: number; available: number; kind: string; }[]; resources: { id: string; kind: string; label: string | null; refType: string | null; refId: string | null; capacity: number; assigned: number; available: number; parentId: string | null; flags: Record; }[]; } | null; } | null>; listProductDepartures(db: PostgresJsDatabase, productId: string, query: StorefrontDepartureListQuery): Promise<{ data: { id: string; productId: string; itineraryId: string; optionId: string | null; dateLocal: string | null; startAt: string | null; endAt: string | null; timezone: string; startTime: { id: string; label: string | null; startTimeLocal: string; durationMinutes: number | null; } | null; meetingPoint: string | null; capacity: number | null; remaining: number | null; departureStatus: import("./service-boundary-sql.js").StorefrontSlotStatus | "on_request"; nights: number | null; days: number | null; ratePlans: { id: string; active: boolean; name: string; pricingModel: string; basePrices: { amount: number; currencyCode: string; }[]; roomPrices: { amount: number; currencyCode: string; roomType: { id: string; name: string; occupancy: { adultsMin: number; adultsMax: number; childrenMax: number; }; }; }[]; }[]; resourceManifest: { kinds: { capacity: number; assigned: number; available: number; kind: string; }[]; resources: { id: string; kind: string; label: string | null; refType: string | null; refId: string | null; capacity: number; assigned: number; available: number; parentId: string | null; flags: Record; }[]; } | null; }[]; total: number; limit: number; offset: number; }>; previewDeparturePrice(db: PostgresJsDatabase, departureId: string, input: StorefrontDeparturePricePreviewInput, context?: StorefrontRequestContext): Promise<{ departureId: string; productId: string; optionId: string | null; currencyCode: string; basePrice: number; taxAmount: number; total: number; notes: string | null; lineItems: { name: string; total: number; quantity: number; unitPrice: number; }[]; allocation: { slot: { id: string; productId: string; optionId: string | null; dateLocal: string | null; startAt: string | null; endAt: string | null; timezone: string; status: import("./service-boundary-sql.js").StorefrontSlotStatus | "on_request"; availabilityState: import("./service-departures-core.js").StorefrontProductAvailabilityState; capacity: number | null; remaining: number | null; pastCutoff: boolean; tooEarly: boolean; resourceManifest: { kinds: { capacity: number; assigned: number; available: number; kind: string; }[]; resources: { id: string; kind: string; label: string | null; refType: string | null; refId: string | null; capacity: number; assigned: number; available: number; parentId: string | null; flags: Record; }[]; } | null; }; pax: { adults: number; children: number; infants: number; total: number; }; requestedUnits: { unitId: string | null; requestRef: string | null; name: string; unitType: string | null; quantity: number; pricingMode: string | null; unitPrice: number; total: number; currencyCode: string; tierId: string | null; }[]; rooms: { unitId: string; name: string; occupancy: number; quantity: number; pax: number; pricingMode: string | null; unitPrice: number; total: number; currencyCode: string; tierId: string | null; }[]; }; units: { unitId: string | null; requestRef: string | null; name: string; unitType: string | null; quantity: number; pricingMode: string | null; unitPrice: number; total: number; currencyCode: string; tierId: string | null; }[]; rooms: { unitId: string; name: string; occupancy: number; quantity: number; pax: number; pricingMode: string | null; unitPrice: number; total: number; currencyCode: string; tierId: string | null; }[]; extras: { extraId: string; name: string; required: boolean; selectable: boolean; selected: boolean; pricingMode: string; quantity: number; unitPrice: number; total: number; currencyCode: string; }[]; offers: { available: { offer: { id: string; name: string; slug: string | null; description: string | null; discountType: "percentage" | "fixed_amount"; discountValue: string; currency: string | null; applicableProductIds: string[]; applicableDepartureIds: string[]; validFrom: string | null; validTo: string | null; minTravelers: number | null; imageMobileUrl: string | null; imageDesktopUrl: string | null; stackable: boolean; createdAt: string; updatedAt: string; }; status: string; reason: string | null; selected: boolean; discountAppliedCents: number; discountedPriceCents: number; }[]; requested: ({ kind: "slug"; value: string; result: { status: "applied" | "not_applicable" | "invalid" | "conflict"; reason: "currency" | "conflict" | "offer_not_found" | "offer_expired" | "offer_not_yet_valid" | "code_not_found" | "code_required" | "code_expired" | "code_not_yet_valid" | "scope" | "min_pax" | "eligibility" | "no_discount" | "booking_mismatch" | "session_mismatch" | null; offer: { id: string; name: string; slug: string | null; description: string | null; discountType: "percentage" | "fixed_amount"; discountValue: string; currency: string | null; applicableProductIds: string[]; applicableDepartureIds: string[]; validFrom: string | null; validTo: string | null; minTravelers: number | null; imageMobileUrl: string | null; imageDesktopUrl: string | null; stackable: boolean; createdAt: string; updatedAt: string; } | null; target: { bookingId: string | null; sessionId: string | null; productId: string; departureId: string | null; }; pricing: { basePriceCents: number; currency: string; discountAppliedCents: number; discountedPriceCents: number; }; appliedOffers: { offerId: string; offerName: string; discountAppliedCents: number; discountedPriceCents: number; currency: string; discountKind: "percentage" | "fixed_amount"; discountPercent: number | null; discountAmountCents: number | null; appliedCode: string | null; stackable: boolean; }[]; conflict: { policy: "best_discount_wins" | "stackable_compose"; autoAppliedOfferIds: string[]; manualOfferId: string | null; selectedOfferIds: string[]; message: string; } | null; } | null; } | { kind: "code"; value: string; result: { status: "applied" | "not_applicable" | "invalid" | "conflict"; reason: "currency" | "conflict" | "offer_not_found" | "offer_expired" | "offer_not_yet_valid" | "code_not_found" | "code_required" | "code_expired" | "code_not_yet_valid" | "scope" | "min_pax" | "eligibility" | "no_discount" | "booking_mismatch" | "session_mismatch" | null; offer: { id: string; name: string; slug: string | null; description: string | null; discountType: "percentage" | "fixed_amount"; discountValue: string; currency: string | null; applicableProductIds: string[]; applicableDepartureIds: string[]; validFrom: string | null; validTo: string | null; minTravelers: number | null; imageMobileUrl: string | null; imageDesktopUrl: string | null; stackable: boolean; createdAt: string; updatedAt: string; } | null; target: { bookingId: string | null; sessionId: string | null; productId: string; departureId: string | null; }; pricing: { basePriceCents: number; currency: string; discountAppliedCents: number; discountedPriceCents: number; }; appliedOffers: { offerId: string; offerName: string; discountAppliedCents: number; discountedPriceCents: number; currency: string; discountKind: "percentage" | "fixed_amount"; discountPercent: number | null; discountAmountCents: number | null; appliedCode: string | null; stackable: boolean; }[]; conflict: { policy: "best_discount_wins" | "stackable_compose"; autoAppliedOfferIds: string[]; manualOfferId: string | null; selectedOfferIds: string[]; message: string; } | null; } | null; })[]; applied: { offerId: string; offerName: string; discountAppliedCents: number; discountedPriceCents: number; currency: string; discountKind: "percentage" | "fixed_amount"; discountPercent: number | null; discountAmountCents: number | null; appliedCode: string | null; stackable: boolean; }[]; conflict: { policy: "best_discount_wins" | "stackable_compose"; autoAppliedOfferIds: string[]; manualOfferId: string | null; selectedOfferIds: string[]; message: string; } | { policy: string; autoAppliedOfferIds: string[]; manualOfferId: null; selectedOfferIds: string[]; message: string; } | null; discountTotal: number; discountTotalCents: number; totalAfterDiscount: number; currencyCode: string; }; totals: { currencyCode: string; base: number; extras: number; subtotal: number; discount: number; tax: number; total: number; perPerson: number; perBooking: number; }; } | null>; getProductExtensions(db: PostgresJsDatabase, productId: string, optionId?: string): Promise<{ extensions: { id: string; name: string; label: string; required: boolean; selectable: boolean; hasOptions: boolean; refProductId: string | null; thumb: string | null; pricePerPerson: number | null; currencyCode: string; pricingMode: string; defaultQuantity: number | null; minQuantity: number | null; maxQuantity: number | null; }[]; items: { id: string; name: string; label: string; required: boolean; selectable: boolean; hasOptions: boolean; refProductId: string | null; thumb: string | null; pricePerPerson: number | null; currencyCode: string; pricingMode: string; defaultQuantity: number | null; minQuantity: number | null; maxQuantity: number | null; }[]; details: { [k: string]: { description: string | null; media: { url: string; alt: string | null; }[]; }; }; currencyCode: string; }>; getProductAvailabilitySummary(db: PostgresJsDatabase, productId: string, query: StorefrontProductAvailabilitySummaryQuery): Promise<{ productId: string; availabilityState: import("./service-departures-core.js").StorefrontProductAvailabilityState; counts: { total: number; open: number; closed: number; soldOut: number; cancelled: number; onRequest: number; pastCutoff: number; tooEarly: number; available: number; }; departures: { id: string; productId: string; optionId: string | null; dateLocal: string | null; startAt: string | null; endAt: string | null; timezone: string; status: import("./service-boundary-sql.js").StorefrontSlotStatus | "on_request"; availabilityState: import("./service-departures-core.js").StorefrontProductAvailabilityState; capacity: number | null; remaining: number | null; pastCutoff: boolean; tooEarly: boolean; }[]; total: number; limit: number; offset: number; }>; getDepartureItinerary(db: PostgresJsDatabase, input: { departureId: string; productId: string; languageTag?: string | null; }): Promise<{ id: string; itineraryId: string; days: { id: string; title: string; description: string | null; thumbnail: { url: string; } | null; segments: { id: string; title: string; description: string | null; }[]; }[]; } | null>; checkDepartureTransportEligibility(input: { departureId: string; productId?: string | null; body: StorefrontTransportEligibilityInput; context?: StorefrontRequestContext; }): Promise; listApplicableOffers(input: { productId: string; departureId?: string; locale?: string; context?: StorefrontRequestContext; }): Promise; getOfferBySlug(input: { slug: string; locale?: string; context?: StorefrontRequestContext; }): Promise; applyOffer(input: { slug: string; body: StorefrontOfferApplyInput; context?: StorefrontRequestContext; }): Promise; redeemOffer(input: { body: StorefrontOfferRedeemInput; context?: StorefrontRequestContext; }): Promise; checkIntakeGuard: (input: { kind: "lead"; body: StorefrontLeadIntakeInput; context: StorefrontRequestContext; } | { kind: "newsletter"; body: StorefrontNewsletterSubscribeInput; context: StorefrontRequestContext; }) => Promise; createLead(input: { body: StorefrontLeadIntakeInput; context: StorefrontRequestContext; }): Promise<{ id: string; personId: string; kind: "wishlist" | "notify" | "inquiry" | "request_offer" | "referral"; source: "admin" | "form" | "website" | "phone" | "abandoned_cart" | "booking"; status: "new" | "contacted" | "qualified" | "converted" | "lost" | "expired"; duplicate: boolean; }>; subscribeNewsletter(input: { body: StorefrontNewsletterSubscribeInput; context: StorefrontRequestContext; }): Promise<{ id: string; personId: string; kind: "wishlist" | "notify" | "inquiry" | "request_offer" | "referral"; source: "admin" | "form" | "website" | "phone" | "abandoned_cart" | "booking"; status: "new" | "contacted" | "qualified" | "converted" | "lost" | "expired"; duplicate: boolean; doubleOptIn: "not_configured" | "requested"; }>; }; export type { StorefrontCustomerSignalCreatedEvent, StorefrontIntakeGuard, StorefrontIntakeOptions, StorefrontIntakePersistence, StorefrontIntakePersistenceResolver, StorefrontIntakePerson, StorefrontIntakeSignal, };