@wix/headless-bookings 1.0.5 → 1.0.7

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 (39) hide show
  1. package/cjs/dist/__mocks__/booking/booking-client-service.js +4 -1
  2. package/cjs/dist/api/create-bulk-booking/index.d.ts +9 -6
  3. package/cjs/dist/api/create-bulk-booking/index.js +2 -0
  4. package/cjs/dist/api/create-multi-service-booking/index.d.ts +7 -6
  5. package/cjs/dist/api/create-multi-service-booking/index.js +2 -0
  6. package/cjs/dist/react/core/booking/Booking.d.ts +6 -1
  7. package/cjs/dist/react/core/booking/Booking.js +2 -0
  8. package/cjs/dist/services/booking/book-action/buildBookingRequest.js +4 -2
  9. package/cjs/dist/services/booking/book-action/buildDayRangeBookingRequest.d.ts +3 -0
  10. package/cjs/dist/services/booking/book-action/buildDayRangeBookingRequest.js +1 -0
  11. package/cjs/dist/services/booking/book-action/createBookingsForCart.js +2 -0
  12. package/cjs/dist/services/booking/book-action/types.d.ts +15 -0
  13. package/cjs/dist/services/booking/booking.d.ts +20 -1
  14. package/cjs/dist/services/booking/booking.js +8 -0
  15. package/cjs/dist/services/payment/payment.d.ts +4 -0
  16. package/cjs/dist/services/payment/payment.def.d.ts +6 -0
  17. package/cjs/dist/services/payment/payment.js +39 -17
  18. package/cjs/dist/services/time-slot-list/time-slot-list.d.ts +1 -1
  19. package/cjs/dist/services/time-slot-list/time-slot-list.js +3 -3
  20. package/dist/__mocks__/booking/booking-client-service.js +4 -1
  21. package/dist/api/create-bulk-booking/index.d.ts +9 -6
  22. package/dist/api/create-bulk-booking/index.js +2 -0
  23. package/dist/api/create-multi-service-booking/index.d.ts +7 -6
  24. package/dist/api/create-multi-service-booking/index.js +2 -0
  25. package/dist/react/core/booking/Booking.d.ts +6 -1
  26. package/dist/react/core/booking/Booking.js +2 -0
  27. package/dist/services/booking/book-action/buildBookingRequest.js +4 -2
  28. package/dist/services/booking/book-action/buildDayRangeBookingRequest.d.ts +3 -0
  29. package/dist/services/booking/book-action/buildDayRangeBookingRequest.js +1 -0
  30. package/dist/services/booking/book-action/createBookingsForCart.js +2 -0
  31. package/dist/services/booking/book-action/types.d.ts +15 -0
  32. package/dist/services/booking/booking.d.ts +20 -1
  33. package/dist/services/booking/booking.js +8 -0
  34. package/dist/services/payment/payment.d.ts +4 -0
  35. package/dist/services/payment/payment.def.d.ts +6 -0
  36. package/dist/services/payment/payment.js +39 -17
  37. package/dist/services/time-slot-list/time-slot-list.d.ts +1 -1
  38. package/dist/services/time-slot-list/time-slot-list.js +1 -1
  39. package/package.json +4 -4
@@ -29,16 +29,18 @@ function createMockSignalsService() {
29
29
  };
30
30
  }
31
31
  function createMockBookingService(config = {}) {
32
- const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, } = config;
32
+ const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, selectedPaymentOption, } = config;
33
33
  const serviceSelectionsSignal = mockSignal(serviceSelections);
34
34
  const locationSignal = mockSignal(location);
35
35
  const timezoneSignal = mockSignal(timezone);
36
36
  const formSubmissionSignal = mockSignal(formSubmission);
37
+ const selectedPaymentOptionSignal = mockSignal(selectedPaymentOption);
37
38
  return {
38
39
  serviceSelections: serviceSelectionsSignal,
39
40
  location: locationSignal,
40
41
  timezone: timezoneSignal,
41
42
  formSubmission: formSubmissionSignal,
43
+ selectedPaymentOption: selectedPaymentOptionSignal,
42
44
  actions: {
43
45
  updateItem: vitest_1.vi.fn(),
44
46
  setItem: vitest_1.vi.fn(),
@@ -53,6 +55,7 @@ function createMockBookingService(config = {}) {
53
55
  setTimezone: vitest_1.vi.fn(),
54
56
  setFormSubmission: vitest_1.vi.fn(),
55
57
  clearFormSubmission: vitest_1.vi.fn(),
58
+ setSelectedPaymentOption: vitest_1.vi.fn(),
56
59
  clearAll: vitest_1.vi.fn(),
57
60
  book: vitest_1.vi.fn(),
58
61
  },
@@ -8,20 +8,22 @@
8
8
  * createMultiServiceBooking.
9
9
  */
10
10
  import { type CreateBookingInfo, type BulkCreateBookingResponse, type ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
11
+ import type { FormValues } from '@wix/forms/components';
11
12
  /**
12
13
  * Options for creating bookings in bulk.
13
14
  *
14
- * `formSubmission` is intentionally absent: the SDK's
15
- * `BulkCreateBookingOptions` does not accept it. Customer details flow
16
- * through the cart layer instead. We always set `returnFullEntity: true` so
17
- * the response includes per-booking entities (with `contactDetails`) for
18
- * cart wiring.
15
+ * The SDK accepts `formSubmission` on each `CreateBookingInfo` entry, while
16
+ * this wrapper accepts one shared payload and applies it to every entry.
17
+ * We always set `returnFullEntity: true` so the response includes per-booking
18
+ * entities (with `contactDetails`) for cart wiring.
19
19
  */
20
20
  export interface BulkCreateBookingOptions {
21
21
  /** Notification settings for participants. Applied to every entry. */
22
22
  participantNotification?: ParticipantNotification;
23
23
  /** Whether to send SMS reminder. Applied to every entry. */
24
- sendSmsReminder?: boolean;
24
+ sendSmsReminder?: boolean | null;
25
+ /** Form submission data. Applied to every entry. */
26
+ formSubmission?: FormValues | null;
25
27
  }
26
28
  /**
27
29
  * Creates 1-8 bookings in a single bulk call.
@@ -44,6 +46,7 @@ export interface BulkCreateBookingOptions {
44
46
  * {
45
47
  * participantNotification: { notifyParticipants: true },
46
48
  * sendSmsReminder: true,
49
+ * formSubmission: formValues,
47
50
  * }
48
51
  * );
49
52
  * const ids = response.results
@@ -32,6 +32,7 @@ const auto_sdk_bookings_bookings_1 = require("@wix/auto_sdk_bookings_bookings");
32
32
  * {
33
33
  * participantNotification: { notifyParticipants: true },
34
34
  * sendSmsReminder: true,
35
+ * formSubmission: formValues,
35
36
  * }
36
37
  * );
37
38
  * const ids = response.results
@@ -47,6 +48,7 @@ function bulkCreateBooking(createBookingsInfo, options) {
47
48
  ...info,
48
49
  participantNotification: info.participantNotification ?? options?.participantNotification,
49
50
  sendSmsReminder: info.sendSmsReminder ?? options?.sendSmsReminder,
51
+ formSubmission: info.formSubmission ?? options?.formSubmission,
50
52
  }));
51
53
  return (0, auto_sdk_bookings_bookings_1.bulkCreateBooking)(enriched, {
52
54
  returnFullEntity: true,
@@ -8,20 +8,20 @@
8
8
  * picked slot.
9
9
  */
10
10
  import { type Booking, type CreateMultiServiceBookingResponse, type ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
11
+ import type { FormValues } from '@wix/forms/components';
11
12
  /**
12
13
  * Options for creating a multi-service booking.
13
14
  *
14
- * `formSubmission` is intentionally absent: the SDK's
15
- * `CreateMultiServiceBookingOptions` does not accept it. Customer details
16
- * flow through the cart layer instead. We always set `returnFullEntity: true`
17
- * so the response includes per-booking entities (with `contactDetails`) for
18
- * cart wiring.
15
+ * We always set `returnFullEntity: true` so the response includes per-booking
16
+ * entities (with `contactDetails`) for cart wiring.
19
17
  */
20
18
  export interface CreateMultiServiceBookingOptions {
21
19
  /** Notification settings for participants */
22
20
  participantNotification?: ParticipantNotification;
23
21
  /** Whether to send SMS reminder */
24
- sendSmsReminder?: boolean;
22
+ sendSmsReminder?: boolean | null;
23
+ /** Form submission data */
24
+ formSubmission?: FormValues | null;
25
25
  }
26
26
  /**
27
27
  * Creates a multi-service booking from 2-8 single-service booking specs.
@@ -43,6 +43,7 @@ export interface CreateMultiServiceBookingOptions {
43
43
  * {
44
44
  * participantNotification: { notifyParticipants: true },
45
45
  * sendSmsReminder: true,
46
+ * formSubmission: formValues,
46
47
  * }
47
48
  * );
48
49
  * const ids = response.multiServiceBooking?.bookings
@@ -31,6 +31,7 @@ const auto_sdk_bookings_bookings_1 = require("@wix/auto_sdk_bookings_bookings");
31
31
  * {
32
32
  * participantNotification: { notifyParticipants: true },
33
33
  * sendSmsReminder: true,
34
+ * formSubmission: formValues,
34
35
  * }
35
36
  * );
36
37
  * const ids = response.multiServiceBooking?.bookings
@@ -42,6 +43,7 @@ function createMultiServiceBooking(bookings, options) {
42
43
  return (0, auto_sdk_bookings_bookings_1.createMultiServiceBooking)(bookings, {
43
44
  participantNotification: options?.participantNotification,
44
45
  sendSmsReminder: options?.sendSmsReminder,
46
+ formSubmission: options?.formSubmission,
45
47
  returnFullEntity: true,
46
48
  });
47
49
  }
@@ -3,7 +3,7 @@
3
3
  * Provides low-level access to BookingService via render props and service setup
4
4
  */
5
5
  import React from 'react';
6
- import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions } from '../../../services/booking/booking.js';
6
+ import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions, type SelectedPaymentOption } from '../../../services/booking/booking.js';
7
7
  import type { FormValues } from '@wix/forms/components';
8
8
  import type { TimeSlot, Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import { type Service, type V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
@@ -40,6 +40,11 @@ export interface BookingData {
40
40
  location: ServiceLocationType | null;
41
41
  timezone: string | undefined;
42
42
  formSubmission: FormValues | null;
43
+ /**
44
+ * Consumer-selected payment option. `undefined` means the service-config
45
+ * heuristic applies. Set via `actions.setSelectedPaymentOption`.
46
+ */
47
+ selectedPaymentOption: SelectedPaymentOption | undefined;
43
48
  actions: BookingServiceActions;
44
49
  }
45
50
  /**
@@ -117,6 +117,7 @@ function Data(props) {
117
117
  const bookingLocation = bookingService.location.get();
118
118
  const timezone = bookingService.timezone.get();
119
119
  const formSubmission = bookingService.formSubmission.get();
120
+ const selectedPaymentOption = bookingService.selectedPaymentOption.get();
120
121
  const { actions } = bookingService;
121
122
  const location = resolveLocation(bookingLocation, serviceSelections);
122
123
  return children({
@@ -124,6 +125,7 @@ function Data(props) {
124
125
  location,
125
126
  timezone,
126
127
  formSubmission,
128
+ selectedPaymentOption,
127
129
  actions,
128
130
  });
129
131
  }
@@ -58,8 +58,10 @@ function buildBookingRequest(params) {
58
58
  const slotLocation = item.timeSlot.location ?? location;
59
59
  return {
60
60
  booking: {
61
- // Payment option derived from service payment settings
62
- selectedPaymentOption: deriveSelectedPaymentOption(item.service),
61
+ // Payment option: consumer override (if set) wins over the
62
+ // service-config heuristic.
63
+ selectedPaymentOption: params.selectedPaymentOption ??
64
+ deriveSelectedPaymentOption(item.service),
63
65
  // Participants
64
66
  totalParticipants: item.totalParticipants || 1,
65
67
  // Booked entity slot
@@ -12,6 +12,7 @@
12
12
  * The 8-day cap is enforced server-side; the builder does not gate on it.
13
13
  */
14
14
  import type { Booking, ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
15
+ import type { FormValues } from '@wix/forms/components';
15
16
  import type { BookActionParams } from './types.js';
16
17
  export interface BuildDayRangeBookingRequest {
17
18
  /**
@@ -23,6 +24,8 @@ export interface BuildDayRangeBookingRequest {
23
24
  participantNotification?: ParticipantNotification;
24
25
  /** Whether to send SMS reminder. */
25
26
  sendSmsReminder: boolean;
27
+ /** Form submission data forwarded to booking creation. */
28
+ formSubmission?: FormValues | null;
26
29
  }
27
30
  /**
28
31
  * Builds N Bookings from a DAY-range selection.
@@ -66,5 +66,6 @@ function buildDayRangeBookingRequest(params) {
66
66
  bookings,
67
67
  participantNotification: baseRequest.participantNotification,
68
68
  sendSmsReminder: baseRequest.sendSmsReminder ?? true,
69
+ formSubmission: baseRequest.formSubmission,
69
70
  };
70
71
  }
@@ -35,6 +35,7 @@ async function createBookingsForCart(params) {
35
35
  const response = await (0, index_js_2.bulkCreateBooking)([{ booking: req.bookings[0] }], {
36
36
  participantNotification: req.participantNotification,
37
37
  sendSmsReminder: req.sendSmsReminder,
38
+ formSubmission: req.formSubmission,
38
39
  });
39
40
  const results = response.results ?? [];
40
41
  return {
@@ -46,6 +47,7 @@ async function createBookingsForCart(params) {
46
47
  const response = await (0, index_js_3.createMultiServiceBooking)(req.bookings, {
47
48
  participantNotification: req.participantNotification,
48
49
  sendSmsReminder: req.sendSmsReminder,
50
+ formSubmission: req.formSubmission,
49
51
  });
50
52
  const bookings = response.multiServiceBooking?.bookings ?? [];
51
53
  return {
@@ -4,6 +4,14 @@
4
4
  import type { ServiceSelection } from '../booking.js';
5
5
  import type { FormValues } from '@wix/forms/components';
6
6
  import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
7
+ import type { SelectedPaymentOptionWithLiterals } from '@wix/auto_sdk_bookings_bookings';
8
+ /**
9
+ * Narrowed booking-API `selectedPaymentOption` to the two values a consumer
10
+ * can pick in the UI. Derived from the SDK enum (`SelectedPaymentOption`) so
11
+ * the type stays in sync; MEMBERSHIP variants are intentionally excluded
12
+ * because they require pricing-plan plumbing not exposed here.
13
+ */
14
+ export type SelectedPaymentOption = Extract<SelectedPaymentOptionWithLiterals, 'ONLINE' | 'OFFLINE'>;
7
15
  /**
8
16
  * Enum for book result types
9
17
  */
@@ -38,6 +46,13 @@ export interface BookActionParams {
38
46
  timezone?: string;
39
47
  /** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
40
48
  appId?: string;
49
+ /**
50
+ * Optional consumer-selected payment option. When provided, overrides the
51
+ * heuristic derived from `service.payment.options` in `buildBookingRequest`
52
+ * and matches the line item's `paymentOption` produced by the payment
53
+ * service. When omitted, the existing service-config heuristic applies.
54
+ */
55
+ selectedPaymentOption?: SelectedPaymentOption;
41
56
  }
42
57
  /**
43
58
  * Props passed to children of Book component
@@ -7,7 +7,8 @@ import { type Signal } from '@wix/services-definitions/core-services/signals';
7
7
  import type { Service, V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
8
8
  import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import type { FormValues } from '@wix/forms/components';
10
- import type { BookResult } from './book-action/types.js';
10
+ import type { BookResult, SelectedPaymentOption } from './book-action/types.js';
11
+ export type { SelectedPaymentOption } from './book-action/types.js';
11
12
  /**
12
13
  * Extended Resource type with a type field for categorization
13
14
  */
@@ -80,6 +81,13 @@ export interface BookingServiceActions {
80
81
  setTimezone(timezone: string): void;
81
82
  setFormSubmission(values: FormValues | null): void;
82
83
  clearFormSubmission(): void;
84
+ /**
85
+ * Set (or clear with `null`) the consumer-selected payment option. When set
86
+ * to `'ONLINE'` or `'OFFLINE'`, this overrides the service-config heuristic
87
+ * used by `book()` and the payment preview. When set to `null`, the override
88
+ * is cleared and the heuristic applies again.
89
+ */
90
+ setSelectedPaymentOption(value: SelectedPaymentOption | null): void;
83
91
  clearAll(): void;
84
92
  book(): Promise<BookResult>;
85
93
  }
@@ -96,6 +104,12 @@ export interface BookingService {
96
104
  timezone: Signal<string | undefined>;
97
105
  /** Signal containing the form submission */
98
106
  formSubmission: Signal<FormValues | null>;
107
+ /**
108
+ * Signal containing the consumer-selected payment option. `undefined` means
109
+ * "not chosen" — `book()` and the payment preview fall back to the
110
+ * service-config heuristic. Set to `'ONLINE'` or `'OFFLINE'` to override.
111
+ */
112
+ selectedPaymentOption: Signal<SelectedPaymentOption | undefined>;
99
113
  /** Actions for modifying booking state */
100
114
  actions: BookingServiceActions;
101
115
  }
@@ -120,6 +134,11 @@ export interface BookingServiceConfig {
120
134
  timezone?: string;
121
135
  /** Optional initial form submission */
122
136
  formSubmission?: FormValues | null;
137
+ /**
138
+ * Optional initial consumer-selected payment option. Omit to use the
139
+ * service-config heuristic; set to `'ONLINE'` or `'OFFLINE'` to override.
140
+ */
141
+ selectedPaymentOption?: SelectedPaymentOption;
123
142
  /** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
124
143
  appId?: string;
125
144
  }
@@ -73,6 +73,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
73
73
  const locationSignal = signalsService.signal(config.location || null);
74
74
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
75
75
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
76
+ const selectedPaymentOptionSignal = signalsService.signal(config.selectedPaymentOption);
76
77
  // ========================================================================
77
78
  // Helpers
78
79
  // ========================================================================
@@ -167,6 +168,10 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
167
168
  clearFormSubmission() {
168
169
  formSubmissionSignal.set(null);
169
170
  },
171
+ // Payment option action
172
+ setSelectedPaymentOption(value) {
173
+ selectedPaymentOptionSignal.set(value ?? undefined);
174
+ },
170
175
  // Clear all
171
176
  clearAll() {
172
177
  this.clearItems();
@@ -180,6 +185,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
180
185
  const formSubmission = formSubmissionSignal.get();
181
186
  const location = locationSignal.get();
182
187
  const timezone = timezoneSignal.get();
188
+ const selectedPaymentOption = selectedPaymentOptionSignal.get();
183
189
  // Validation happens inside executeBookAction
184
190
  return (0, bookAction_js_1.executeBookAction)({
185
191
  serviceSelections,
@@ -187,6 +193,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
187
193
  location,
188
194
  timezone,
189
195
  appId: config.appId,
196
+ selectedPaymentOption,
190
197
  });
191
198
  },
192
199
  };
@@ -195,6 +202,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
195
202
  location: locationSignal,
196
203
  timezone: timezoneSignal,
197
204
  formSubmission: formSubmissionSignal,
205
+ selectedPaymentOption: selectedPaymentOptionSignal,
198
206
  actions,
199
207
  };
200
208
  });
@@ -4,6 +4,7 @@
4
4
  * Supports two modes: SSR (config with pre-fetched data) and lazy loading (reactive)
5
5
  */
6
6
  import { type PaymentServiceConfig, type PaymentServiceInternalConfig, type PricingServiceSelection } from './payment.def.js';
7
+ import type { SelectedPaymentOption } from '../booking/book-action/types.js';
7
8
  export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, type ServicePaymentOptions, } from './payment.def.js';
8
9
  export type { PaymentServiceAPI, PaymentServiceInternalConfig, } from './payment.def.js';
9
10
  export declare const PaymentService: import("@wix/services-definitions").ServiceFactory<string & {
@@ -25,8 +26,11 @@ export type ErrorPaymentConfigResult = {
25
26
  *
26
27
  * @param params.pricingServiceSelections - Pricing service selections for calculation
27
28
  * @param params.totalParticipants - Number of participants (default: 1)
29
+ * @param params.selectedPaymentOption - Optional consumer-selected payment option;
30
+ * overrides the service-config heuristic on each line item's `paymentOption`.
28
31
  */
29
32
  export declare function loadPaymentConfig(params: {
30
33
  pricingServiceSelections: PricingServiceSelection[];
31
34
  totalParticipants?: number;
35
+ selectedPaymentOption?: SelectedPaymentOption;
32
36
  }): Promise<SuccessPaymentConfigResult | ErrorPaymentConfigResult>;
@@ -5,6 +5,7 @@
5
5
  import type { Signal } from '@wix/services-definitions/core-services/signals';
6
6
  import type { CalculateTotalsResponse } from '@wix/auto_sdk_ecom_totals-calculator';
7
7
  import type { Service } from '@wix/auto_sdk_bookings_services';
8
+ import type { SelectedPaymentOption } from '../booking/book-action/types.js';
8
9
  /**
9
10
  * Service payment options derived from service.payment.options.
10
11
  * Describes which payment methods the service supports.
@@ -74,6 +75,11 @@ export interface PaymentServiceInternalConfig extends PaymentServiceConfig {
74
75
  pricingServiceSelections?: PricingServiceSelection[];
75
76
  /** Number of participants for lazy loading (internal use) */
76
77
  totalParticipants?: number;
78
+ /**
79
+ * Optional consumer-selected payment option used for lazy loading. When
80
+ * set, overrides the service-config heuristic in line item `paymentOption`.
81
+ */
82
+ selectedPaymentOption?: SelectedPaymentOption;
77
83
  }
78
84
  export interface PaymentServiceAPI {
79
85
  paymentDetailsSignal: Signal<PaymentDetails | null>;
@@ -57,7 +57,21 @@ function getAdditionalInfo(service, lineItemId) {
57
57
  : undefined;
58
58
  return { lineItemId, customPrice };
59
59
  }
60
- function getPaymentOption(service, hasDeposit) {
60
+ /**
61
+ * Resolves the line item's `paymentOption`.
62
+ *
63
+ * Precedence: explicit consumer choice (`selectedPaymentOption`) wins over the
64
+ * service-config heuristic so the preview always agrees with what `book()`
65
+ * will send. When unset, the existing heuristic is preserved for backward
66
+ * compat.
67
+ */
68
+ function getPaymentOption(service, hasDeposit, selectedPaymentOption) {
69
+ if (selectedPaymentOption === 'OFFLINE') {
70
+ return 'FULL_PAYMENT_OFFLINE';
71
+ }
72
+ if (selectedPaymentOption === 'ONLINE') {
73
+ return hasDeposit ? 'DEPOSIT_ONLINE' : 'FULL_PAYMENT_ONLINE';
74
+ }
61
75
  const options = service.payment?.options;
62
76
  if (options?.inPerson) {
63
77
  return 'FULL_PAYMENT_OFFLINE';
@@ -68,7 +82,7 @@ function getPaymentOption(service, hasDeposit) {
68
82
  }
69
83
  return 'FULL_PAYMENT_ONLINE';
70
84
  }
71
- function buildLineItem(service, totalParticipants, lineItemId) {
85
+ function buildLineItem(service, totalParticipants, lineItemId, selectedPaymentOption) {
72
86
  // Get fixed price from service
73
87
  const servicePrice = service.payment?.fixed?.price?.value;
74
88
  const price = Number(servicePrice || 0) * totalParticipants;
@@ -79,7 +93,7 @@ function buildLineItem(service, totalParticipants, lineItemId) {
79
93
  ? Number(depositValue) * totalParticipants
80
94
  : undefined;
81
95
  // Get payment option from service configuration (pass hasDeposit for correct API behavior)
82
- const paymentOption = getPaymentOption(service, hasDeposit);
96
+ const paymentOption = getPaymentOption(service, hasDeposit, selectedPaymentOption);
83
97
  return {
84
98
  _id: lineItemId,
85
99
  catalogReference: {
@@ -117,7 +131,7 @@ async function getPreviewedPrice(service, localStartDate, localEndDate, timeZone
117
131
  deposit: result.priceInfo?.deposit ?? null,
118
132
  };
119
133
  }
120
- async function buildLineItemWithPreview(item, totalParticipants) {
134
+ async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption) {
121
135
  if (item.localStartDate &&
122
136
  item.localEndDate &&
123
137
  getDurationRange(item.service)) {
@@ -142,18 +156,18 @@ async function buildLineItemWithPreview(item, totalParticipants) {
142
156
  price: String(previewedPrice.price),
143
157
  ...(deposit ? { depositAmount: String(deposit) } : {}),
144
158
  quantity: 1,
145
- paymentOption: getPaymentOption(item.service, hasDeposit),
159
+ paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption),
146
160
  };
147
161
  }
148
162
  }
149
- return buildLineItem(item.service, totalParticipants, item.lineItemId);
163
+ return buildLineItem(item.service, totalParticipants, item.lineItemId, selectedPaymentOption);
150
164
  }
151
- async function calculatePaymentDetails(pricingServiceSelections, totalParticipants) {
165
+ async function calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption) {
152
166
  const data = pricingServiceSelections.map((selection) => ({
153
167
  ...selection,
154
168
  lineItemId: selection.lineItemId || (0, guid_js_1.generateLineItemId)(),
155
169
  }));
156
- const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants)));
170
+ const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption)));
157
171
  const response = await (0, auto_sdk_ecom_totals_calculator_1.calculateTotals)({
158
172
  lineItems,
159
173
  });
@@ -184,7 +198,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
184
198
  const errorSignal = signalsService.signal(null);
185
199
  const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
186
200
  // Internal calculate function
187
- const calculate = async (pricingServiceSelections, totalParticipants) => {
201
+ const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption) => {
188
202
  if (!pricingServiceSelections.length) {
189
203
  paymentDetailsSignal.set(null);
190
204
  return;
@@ -192,7 +206,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
192
206
  isLoadingSignal.set(true);
193
207
  errorSignal.set(null);
194
208
  try {
195
- const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
209
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
196
210
  paymentDetailsSignal.set(paymentDetails);
197
211
  }
198
212
  catch (error) {
@@ -220,24 +234,30 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
220
234
  // Mode 2: Lazy loading - pricingServiceSelections provided via internal config
221
235
  else if (config.pricingServiceSelections?.length) {
222
236
  serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
223
- void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1);
237
+ void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption);
224
238
  }
225
239
  // Mode 3: Reactive - observe BookingService signals (client-side only)
226
240
  else if (typeof window !== 'undefined' && bookingService) {
227
- // Computed signal that changes when services or selected time slots change (not participants)
241
+ // Computed signal that changes when services, selected time slots, or
242
+ // the consumer-selected payment option change (not participants).
228
243
  const servicesKey = signalsService.computed(() => {
229
244
  const selections = bookingService?.serviceSelections.get() || [];
230
245
  const timezone = bookingService?.timezone.get();
246
+ const selectedPaymentOption = bookingService?.selectedPaymentOption.get();
231
247
  return (selections
232
248
  .map((s) => `${s.service._id}:${s.timeSlot?.startDate ?? ''}:${s.timeSlot?.endDate ?? ''}`)
233
- .join(',') + `:${timezone ?? ''}`);
249
+ .join(',') +
250
+ `:${timezone ?? ''}` +
251
+ `:${selectedPaymentOption ?? ''}`);
234
252
  });
235
253
  signalsService.effect(() => {
236
- // Subscribe to servicesKey to trigger effect when service or time slot changes
254
+ // Subscribe to servicesKey to trigger effect when service, time
255
+ // slot, or selected payment option changes.
237
256
  servicesKey.get();
238
257
  // Read current selections without subscribing (peek)
239
258
  const selections = bookingService?.serviceSelections.peek() || [];
240
259
  const timezone = bookingService?.timezone.peek();
260
+ const selectedPaymentOption = bookingService?.selectedPaymentOption.peek();
241
261
  // For single service mode, take the first selection
242
262
  const firstSelection = selections[0];
243
263
  const numParticipants = firstSelection?.totalParticipants || 1;
@@ -251,7 +271,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
251
271
  if (pricingServiceSelections.length) {
252
272
  serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
253
273
  }
254
- void calculate(pricingServiceSelections, numParticipants);
274
+ void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption);
255
275
  });
256
276
  }
257
277
  return {
@@ -267,10 +287,12 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
267
287
  *
268
288
  * @param params.pricingServiceSelections - Pricing service selections for calculation
269
289
  * @param params.totalParticipants - Number of participants (default: 1)
290
+ * @param params.selectedPaymentOption - Optional consumer-selected payment option;
291
+ * overrides the service-config heuristic on each line item's `paymentOption`.
270
292
  */
271
293
  async function loadPaymentConfig(params) {
272
294
  try {
273
- const { pricingServiceSelections, totalParticipants = 1 } = params;
295
+ const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, } = params;
274
296
  if (!pricingServiceSelections.length) {
275
297
  return {
276
298
  type: 'success',
@@ -280,7 +302,7 @@ async function loadPaymentConfig(params) {
280
302
  },
281
303
  };
282
304
  }
283
- const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
305
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
284
306
  return {
285
307
  type: 'success',
286
308
  config: {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { Service } from '@wix/auto_sdk_bookings_services';
6
6
  import { type TimeSlotListServiceConfig, type TimeSlotsFilters } from './time-slot-list.def.js';
7
- import { TimeSlot } from '@wix/auto_sdk_bookings_event-time-slots';
7
+ import { TimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
8
8
  /**
9
9
  * Helper to get staff member name from time slots by ID
10
10
  * @internal - exported for testing
@@ -9,7 +9,7 @@ exports.loadTimeSlotListConfig = loadTimeSlotListConfig;
9
9
  const services_definitions_1 = require("@wix/services-definitions");
10
10
  const signals_1 = require("@wix/services-definitions/core-services/signals");
11
11
  const time_slot_list_def_js_1 = require("./time-slot-list.def.js");
12
- const auto_sdk_bookings_event_time_slots_1 = require("@wix/auto_sdk_bookings_event-time-slots");
12
+ const auto_sdk_bookings_availability_time_slots_1 = require("@wix/auto_sdk_bookings_availability-time-slots");
13
13
  const index_js_1 = require("../../api/fetch-availability/index.js");
14
14
  const booking_js_1 = require("../booking/booking.js");
15
15
  // ============================================================================
@@ -61,8 +61,8 @@ exports.TimeSlotListService = services_definitions_1.implementService.withConfig
61
61
  const getFilters = () => {
62
62
  const baseFilters = config.filters ?? {};
63
63
  const selectedLocation = bookingService.location.peek();
64
- const isCustomOrCustomerLocation = selectedLocation?.type === auto_sdk_bookings_event_time_slots_1.LocationType.CUSTOM ||
65
- selectedLocation?.type === auto_sdk_bookings_event_time_slots_1.LocationType.CUSTOMER;
64
+ const isCustomOrCustomerLocation = selectedLocation?.type === auto_sdk_bookings_availability_time_slots_1.LocationType.CUSTOM ||
65
+ selectedLocation?.type === auto_sdk_bookings_availability_time_slots_1.LocationType.CUSTOMER;
66
66
  const location = selectedLocation
67
67
  ? {
68
68
  id: isCustomOrCustomerLocation ? undefined : selectedLocation._id,
@@ -24,16 +24,18 @@ export function createMockSignalsService() {
24
24
  };
25
25
  }
26
26
  export function createMockBookingService(config = {}) {
27
- const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, } = config;
27
+ const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, selectedPaymentOption, } = config;
28
28
  const serviceSelectionsSignal = mockSignal(serviceSelections);
29
29
  const locationSignal = mockSignal(location);
30
30
  const timezoneSignal = mockSignal(timezone);
31
31
  const formSubmissionSignal = mockSignal(formSubmission);
32
+ const selectedPaymentOptionSignal = mockSignal(selectedPaymentOption);
32
33
  return {
33
34
  serviceSelections: serviceSelectionsSignal,
34
35
  location: locationSignal,
35
36
  timezone: timezoneSignal,
36
37
  formSubmission: formSubmissionSignal,
38
+ selectedPaymentOption: selectedPaymentOptionSignal,
37
39
  actions: {
38
40
  updateItem: vi.fn(),
39
41
  setItem: vi.fn(),
@@ -48,6 +50,7 @@ export function createMockBookingService(config = {}) {
48
50
  setTimezone: vi.fn(),
49
51
  setFormSubmission: vi.fn(),
50
52
  clearFormSubmission: vi.fn(),
53
+ setSelectedPaymentOption: vi.fn(),
51
54
  clearAll: vi.fn(),
52
55
  book: vi.fn(),
53
56
  },
@@ -8,20 +8,22 @@
8
8
  * createMultiServiceBooking.
9
9
  */
10
10
  import { type CreateBookingInfo, type BulkCreateBookingResponse, type ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
11
+ import type { FormValues } from '@wix/forms/components';
11
12
  /**
12
13
  * Options for creating bookings in bulk.
13
14
  *
14
- * `formSubmission` is intentionally absent: the SDK's
15
- * `BulkCreateBookingOptions` does not accept it. Customer details flow
16
- * through the cart layer instead. We always set `returnFullEntity: true` so
17
- * the response includes per-booking entities (with `contactDetails`) for
18
- * cart wiring.
15
+ * The SDK accepts `formSubmission` on each `CreateBookingInfo` entry, while
16
+ * this wrapper accepts one shared payload and applies it to every entry.
17
+ * We always set `returnFullEntity: true` so the response includes per-booking
18
+ * entities (with `contactDetails`) for cart wiring.
19
19
  */
20
20
  export interface BulkCreateBookingOptions {
21
21
  /** Notification settings for participants. Applied to every entry. */
22
22
  participantNotification?: ParticipantNotification;
23
23
  /** Whether to send SMS reminder. Applied to every entry. */
24
- sendSmsReminder?: boolean;
24
+ sendSmsReminder?: boolean | null;
25
+ /** Form submission data. Applied to every entry. */
26
+ formSubmission?: FormValues | null;
25
27
  }
26
28
  /**
27
29
  * Creates 1-8 bookings in a single bulk call.
@@ -44,6 +46,7 @@ export interface BulkCreateBookingOptions {
44
46
  * {
45
47
  * participantNotification: { notifyParticipants: true },
46
48
  * sendSmsReminder: true,
49
+ * formSubmission: formValues,
47
50
  * }
48
51
  * );
49
52
  * const ids = response.results
@@ -29,6 +29,7 @@ import { bulkCreateBooking as bulkCreateBookingSdk, } from '@wix/auto_sdk_bookin
29
29
  * {
30
30
  * participantNotification: { notifyParticipants: true },
31
31
  * sendSmsReminder: true,
32
+ * formSubmission: formValues,
32
33
  * }
33
34
  * );
34
35
  * const ids = response.results
@@ -44,6 +45,7 @@ export function bulkCreateBooking(createBookingsInfo, options) {
44
45
  ...info,
45
46
  participantNotification: info.participantNotification ?? options?.participantNotification,
46
47
  sendSmsReminder: info.sendSmsReminder ?? options?.sendSmsReminder,
48
+ formSubmission: info.formSubmission ?? options?.formSubmission,
47
49
  }));
48
50
  return bulkCreateBookingSdk(enriched, {
49
51
  returnFullEntity: true,
@@ -8,20 +8,20 @@
8
8
  * picked slot.
9
9
  */
10
10
  import { type Booking, type CreateMultiServiceBookingResponse, type ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
11
+ import type { FormValues } from '@wix/forms/components';
11
12
  /**
12
13
  * Options for creating a multi-service booking.
13
14
  *
14
- * `formSubmission` is intentionally absent: the SDK's
15
- * `CreateMultiServiceBookingOptions` does not accept it. Customer details
16
- * flow through the cart layer instead. We always set `returnFullEntity: true`
17
- * so the response includes per-booking entities (with `contactDetails`) for
18
- * cart wiring.
15
+ * We always set `returnFullEntity: true` so the response includes per-booking
16
+ * entities (with `contactDetails`) for cart wiring.
19
17
  */
20
18
  export interface CreateMultiServiceBookingOptions {
21
19
  /** Notification settings for participants */
22
20
  participantNotification?: ParticipantNotification;
23
21
  /** Whether to send SMS reminder */
24
- sendSmsReminder?: boolean;
22
+ sendSmsReminder?: boolean | null;
23
+ /** Form submission data */
24
+ formSubmission?: FormValues | null;
25
25
  }
26
26
  /**
27
27
  * Creates a multi-service booking from 2-8 single-service booking specs.
@@ -43,6 +43,7 @@ export interface CreateMultiServiceBookingOptions {
43
43
  * {
44
44
  * participantNotification: { notifyParticipants: true },
45
45
  * sendSmsReminder: true,
46
+ * formSubmission: formValues,
46
47
  * }
47
48
  * );
48
49
  * const ids = response.multiServiceBooking?.bookings
@@ -28,6 +28,7 @@ import { createMultiServiceBooking as createMultiServiceBookingSdk, } from '@wix
28
28
  * {
29
29
  * participantNotification: { notifyParticipants: true },
30
30
  * sendSmsReminder: true,
31
+ * formSubmission: formValues,
31
32
  * }
32
33
  * );
33
34
  * const ids = response.multiServiceBooking?.bookings
@@ -39,6 +40,7 @@ export function createMultiServiceBooking(bookings, options) {
39
40
  return createMultiServiceBookingSdk(bookings, {
40
41
  participantNotification: options?.participantNotification,
41
42
  sendSmsReminder: options?.sendSmsReminder,
43
+ formSubmission: options?.formSubmission,
42
44
  returnFullEntity: true,
43
45
  });
44
46
  }
@@ -3,7 +3,7 @@
3
3
  * Provides low-level access to BookingService via render props and service setup
4
4
  */
5
5
  import React from 'react';
6
- import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions } from '../../../services/booking/booking.js';
6
+ import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions, type SelectedPaymentOption } from '../../../services/booking/booking.js';
7
7
  import type { FormValues } from '@wix/forms/components';
8
8
  import type { TimeSlot, Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import { type Service, type V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
@@ -40,6 +40,11 @@ export interface BookingData {
40
40
  location: ServiceLocationType | null;
41
41
  timezone: string | undefined;
42
42
  formSubmission: FormValues | null;
43
+ /**
44
+ * Consumer-selected payment option. `undefined` means the service-config
45
+ * heuristic applies. Set via `actions.setSelectedPaymentOption`.
46
+ */
47
+ selectedPaymentOption: SelectedPaymentOption | undefined;
43
48
  actions: BookingServiceActions;
44
49
  }
45
50
  /**
@@ -105,6 +105,7 @@ export function Data(props) {
105
105
  const bookingLocation = bookingService.location.get();
106
106
  const timezone = bookingService.timezone.get();
107
107
  const formSubmission = bookingService.formSubmission.get();
108
+ const selectedPaymentOption = bookingService.selectedPaymentOption.get();
108
109
  const { actions } = bookingService;
109
110
  const location = resolveLocation(bookingLocation, serviceSelections);
110
111
  return children({
@@ -112,6 +113,7 @@ export function Data(props) {
112
113
  location,
113
114
  timezone,
114
115
  formSubmission,
116
+ selectedPaymentOption,
115
117
  actions,
116
118
  });
117
119
  }
@@ -55,8 +55,10 @@ export function buildBookingRequest(params) {
55
55
  const slotLocation = item.timeSlot.location ?? location;
56
56
  return {
57
57
  booking: {
58
- // Payment option derived from service payment settings
59
- selectedPaymentOption: deriveSelectedPaymentOption(item.service),
58
+ // Payment option: consumer override (if set) wins over the
59
+ // service-config heuristic.
60
+ selectedPaymentOption: params.selectedPaymentOption ??
61
+ deriveSelectedPaymentOption(item.service),
60
62
  // Participants
61
63
  totalParticipants: item.totalParticipants || 1,
62
64
  // Booked entity slot
@@ -12,6 +12,7 @@
12
12
  * The 8-day cap is enforced server-side; the builder does not gate on it.
13
13
  */
14
14
  import type { Booking, ParticipantNotification } from '@wix/auto_sdk_bookings_bookings';
15
+ import type { FormValues } from '@wix/forms/components';
15
16
  import type { BookActionParams } from './types.js';
16
17
  export interface BuildDayRangeBookingRequest {
17
18
  /**
@@ -23,6 +24,8 @@ export interface BuildDayRangeBookingRequest {
23
24
  participantNotification?: ParticipantNotification;
24
25
  /** Whether to send SMS reminder. */
25
26
  sendSmsReminder: boolean;
27
+ /** Form submission data forwarded to booking creation. */
28
+ formSubmission?: FormValues | null;
26
29
  }
27
30
  /**
28
31
  * Builds N Bookings from a DAY-range selection.
@@ -63,5 +63,6 @@ export function buildDayRangeBookingRequest(params) {
63
63
  bookings,
64
64
  participantNotification: baseRequest.participantNotification,
65
65
  sendSmsReminder: baseRequest.sendSmsReminder ?? true,
66
+ formSubmission: baseRequest.formSubmission,
66
67
  };
67
68
  }
@@ -32,6 +32,7 @@ export async function createBookingsForCart(params) {
32
32
  const response = await bulkCreateBooking([{ booking: req.bookings[0] }], {
33
33
  participantNotification: req.participantNotification,
34
34
  sendSmsReminder: req.sendSmsReminder,
35
+ formSubmission: req.formSubmission,
35
36
  });
36
37
  const results = response.results ?? [];
37
38
  return {
@@ -43,6 +44,7 @@ export async function createBookingsForCart(params) {
43
44
  const response = await createMultiServiceBooking(req.bookings, {
44
45
  participantNotification: req.participantNotification,
45
46
  sendSmsReminder: req.sendSmsReminder,
47
+ formSubmission: req.formSubmission,
46
48
  });
47
49
  const bookings = response.multiServiceBooking?.bookings ?? [];
48
50
  return {
@@ -4,6 +4,14 @@
4
4
  import type { ServiceSelection } from '../booking.js';
5
5
  import type { FormValues } from '@wix/forms/components';
6
6
  import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
7
+ import type { SelectedPaymentOptionWithLiterals } from '@wix/auto_sdk_bookings_bookings';
8
+ /**
9
+ * Narrowed booking-API `selectedPaymentOption` to the two values a consumer
10
+ * can pick in the UI. Derived from the SDK enum (`SelectedPaymentOption`) so
11
+ * the type stays in sync; MEMBERSHIP variants are intentionally excluded
12
+ * because they require pricing-plan plumbing not exposed here.
13
+ */
14
+ export type SelectedPaymentOption = Extract<SelectedPaymentOptionWithLiterals, 'ONLINE' | 'OFFLINE'>;
7
15
  /**
8
16
  * Enum for book result types
9
17
  */
@@ -38,6 +46,13 @@ export interface BookActionParams {
38
46
  timezone?: string;
39
47
  /** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
40
48
  appId?: string;
49
+ /**
50
+ * Optional consumer-selected payment option. When provided, overrides the
51
+ * heuristic derived from `service.payment.options` in `buildBookingRequest`
52
+ * and matches the line item's `paymentOption` produced by the payment
53
+ * service. When omitted, the existing service-config heuristic applies.
54
+ */
55
+ selectedPaymentOption?: SelectedPaymentOption;
41
56
  }
42
57
  /**
43
58
  * Props passed to children of Book component
@@ -7,7 +7,8 @@ import { type Signal } from '@wix/services-definitions/core-services/signals';
7
7
  import type { Service, V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
8
8
  import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import type { FormValues } from '@wix/forms/components';
10
- import type { BookResult } from './book-action/types.js';
10
+ import type { BookResult, SelectedPaymentOption } from './book-action/types.js';
11
+ export type { SelectedPaymentOption } from './book-action/types.js';
11
12
  /**
12
13
  * Extended Resource type with a type field for categorization
13
14
  */
@@ -80,6 +81,13 @@ export interface BookingServiceActions {
80
81
  setTimezone(timezone: string): void;
81
82
  setFormSubmission(values: FormValues | null): void;
82
83
  clearFormSubmission(): void;
84
+ /**
85
+ * Set (or clear with `null`) the consumer-selected payment option. When set
86
+ * to `'ONLINE'` or `'OFFLINE'`, this overrides the service-config heuristic
87
+ * used by `book()` and the payment preview. When set to `null`, the override
88
+ * is cleared and the heuristic applies again.
89
+ */
90
+ setSelectedPaymentOption(value: SelectedPaymentOption | null): void;
83
91
  clearAll(): void;
84
92
  book(): Promise<BookResult>;
85
93
  }
@@ -96,6 +104,12 @@ export interface BookingService {
96
104
  timezone: Signal<string | undefined>;
97
105
  /** Signal containing the form submission */
98
106
  formSubmission: Signal<FormValues | null>;
107
+ /**
108
+ * Signal containing the consumer-selected payment option. `undefined` means
109
+ * "not chosen" — `book()` and the payment preview fall back to the
110
+ * service-config heuristic. Set to `'ONLINE'` or `'OFFLINE'` to override.
111
+ */
112
+ selectedPaymentOption: Signal<SelectedPaymentOption | undefined>;
99
113
  /** Actions for modifying booking state */
100
114
  actions: BookingServiceActions;
101
115
  }
@@ -120,6 +134,11 @@ export interface BookingServiceConfig {
120
134
  timezone?: string;
121
135
  /** Optional initial form submission */
122
136
  formSubmission?: FormValues | null;
137
+ /**
138
+ * Optional initial consumer-selected payment option. Omit to use the
139
+ * service-config heuristic; set to `'ONLINE'` or `'OFFLINE'` to override.
140
+ */
141
+ selectedPaymentOption?: SelectedPaymentOption;
123
142
  /** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
124
143
  appId?: string;
125
144
  }
@@ -70,6 +70,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
70
70
  const locationSignal = signalsService.signal(config.location || null);
71
71
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
72
72
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
73
+ const selectedPaymentOptionSignal = signalsService.signal(config.selectedPaymentOption);
73
74
  // ========================================================================
74
75
  // Helpers
75
76
  // ========================================================================
@@ -164,6 +165,10 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
164
165
  clearFormSubmission() {
165
166
  formSubmissionSignal.set(null);
166
167
  },
168
+ // Payment option action
169
+ setSelectedPaymentOption(value) {
170
+ selectedPaymentOptionSignal.set(value ?? undefined);
171
+ },
167
172
  // Clear all
168
173
  clearAll() {
169
174
  this.clearItems();
@@ -177,6 +182,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
177
182
  const formSubmission = formSubmissionSignal.get();
178
183
  const location = locationSignal.get();
179
184
  const timezone = timezoneSignal.get();
185
+ const selectedPaymentOption = selectedPaymentOptionSignal.get();
180
186
  // Validation happens inside executeBookAction
181
187
  return executeBookAction({
182
188
  serviceSelections,
@@ -184,6 +190,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
184
190
  location,
185
191
  timezone,
186
192
  appId: config.appId,
193
+ selectedPaymentOption,
187
194
  });
188
195
  },
189
196
  };
@@ -192,6 +199,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
192
199
  location: locationSignal,
193
200
  timezone: timezoneSignal,
194
201
  formSubmission: formSubmissionSignal,
202
+ selectedPaymentOption: selectedPaymentOptionSignal,
195
203
  actions,
196
204
  };
197
205
  });
@@ -4,6 +4,7 @@
4
4
  * Supports two modes: SSR (config with pre-fetched data) and lazy loading (reactive)
5
5
  */
6
6
  import { type PaymentServiceConfig, type PaymentServiceInternalConfig, type PricingServiceSelection } from './payment.def.js';
7
+ import type { SelectedPaymentOption } from '../booking/book-action/types.js';
7
8
  export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, type ServicePaymentOptions, } from './payment.def.js';
8
9
  export type { PaymentServiceAPI, PaymentServiceInternalConfig, } from './payment.def.js';
9
10
  export declare const PaymentService: import("@wix/services-definitions").ServiceFactory<string & {
@@ -25,8 +26,11 @@ export type ErrorPaymentConfigResult = {
25
26
  *
26
27
  * @param params.pricingServiceSelections - Pricing service selections for calculation
27
28
  * @param params.totalParticipants - Number of participants (default: 1)
29
+ * @param params.selectedPaymentOption - Optional consumer-selected payment option;
30
+ * overrides the service-config heuristic on each line item's `paymentOption`.
28
31
  */
29
32
  export declare function loadPaymentConfig(params: {
30
33
  pricingServiceSelections: PricingServiceSelection[];
31
34
  totalParticipants?: number;
35
+ selectedPaymentOption?: SelectedPaymentOption;
32
36
  }): Promise<SuccessPaymentConfigResult | ErrorPaymentConfigResult>;
@@ -5,6 +5,7 @@
5
5
  import type { Signal } from '@wix/services-definitions/core-services/signals';
6
6
  import type { CalculateTotalsResponse } from '@wix/auto_sdk_ecom_totals-calculator';
7
7
  import type { Service } from '@wix/auto_sdk_bookings_services';
8
+ import type { SelectedPaymentOption } from '../booking/book-action/types.js';
8
9
  /**
9
10
  * Service payment options derived from service.payment.options.
10
11
  * Describes which payment methods the service supports.
@@ -74,6 +75,11 @@ export interface PaymentServiceInternalConfig extends PaymentServiceConfig {
74
75
  pricingServiceSelections?: PricingServiceSelection[];
75
76
  /** Number of participants for lazy loading (internal use) */
76
77
  totalParticipants?: number;
78
+ /**
79
+ * Optional consumer-selected payment option used for lazy loading. When
80
+ * set, overrides the service-config heuristic in line item `paymentOption`.
81
+ */
82
+ selectedPaymentOption?: SelectedPaymentOption;
77
83
  }
78
84
  export interface PaymentServiceAPI {
79
85
  paymentDetailsSignal: Signal<PaymentDetails | null>;
@@ -52,7 +52,21 @@ function getAdditionalInfo(service, lineItemId) {
52
52
  : undefined;
53
53
  return { lineItemId, customPrice };
54
54
  }
55
- function getPaymentOption(service, hasDeposit) {
55
+ /**
56
+ * Resolves the line item's `paymentOption`.
57
+ *
58
+ * Precedence: explicit consumer choice (`selectedPaymentOption`) wins over the
59
+ * service-config heuristic so the preview always agrees with what `book()`
60
+ * will send. When unset, the existing heuristic is preserved for backward
61
+ * compat.
62
+ */
63
+ function getPaymentOption(service, hasDeposit, selectedPaymentOption) {
64
+ if (selectedPaymentOption === 'OFFLINE') {
65
+ return 'FULL_PAYMENT_OFFLINE';
66
+ }
67
+ if (selectedPaymentOption === 'ONLINE') {
68
+ return hasDeposit ? 'DEPOSIT_ONLINE' : 'FULL_PAYMENT_ONLINE';
69
+ }
56
70
  const options = service.payment?.options;
57
71
  if (options?.inPerson) {
58
72
  return 'FULL_PAYMENT_OFFLINE';
@@ -63,7 +77,7 @@ function getPaymentOption(service, hasDeposit) {
63
77
  }
64
78
  return 'FULL_PAYMENT_ONLINE';
65
79
  }
66
- function buildLineItem(service, totalParticipants, lineItemId) {
80
+ function buildLineItem(service, totalParticipants, lineItemId, selectedPaymentOption) {
67
81
  // Get fixed price from service
68
82
  const servicePrice = service.payment?.fixed?.price?.value;
69
83
  const price = Number(servicePrice || 0) * totalParticipants;
@@ -74,7 +88,7 @@ function buildLineItem(service, totalParticipants, lineItemId) {
74
88
  ? Number(depositValue) * totalParticipants
75
89
  : undefined;
76
90
  // Get payment option from service configuration (pass hasDeposit for correct API behavior)
77
- const paymentOption = getPaymentOption(service, hasDeposit);
91
+ const paymentOption = getPaymentOption(service, hasDeposit, selectedPaymentOption);
78
92
  return {
79
93
  _id: lineItemId,
80
94
  catalogReference: {
@@ -112,7 +126,7 @@ async function getPreviewedPrice(service, localStartDate, localEndDate, timeZone
112
126
  deposit: result.priceInfo?.deposit ?? null,
113
127
  };
114
128
  }
115
- async function buildLineItemWithPreview(item, totalParticipants) {
129
+ async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption) {
116
130
  if (item.localStartDate &&
117
131
  item.localEndDate &&
118
132
  getDurationRange(item.service)) {
@@ -137,18 +151,18 @@ async function buildLineItemWithPreview(item, totalParticipants) {
137
151
  price: String(previewedPrice.price),
138
152
  ...(deposit ? { depositAmount: String(deposit) } : {}),
139
153
  quantity: 1,
140
- paymentOption: getPaymentOption(item.service, hasDeposit),
154
+ paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption),
141
155
  };
142
156
  }
143
157
  }
144
- return buildLineItem(item.service, totalParticipants, item.lineItemId);
158
+ return buildLineItem(item.service, totalParticipants, item.lineItemId, selectedPaymentOption);
145
159
  }
146
- async function calculatePaymentDetails(pricingServiceSelections, totalParticipants) {
160
+ async function calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption) {
147
161
  const data = pricingServiceSelections.map((selection) => ({
148
162
  ...selection,
149
163
  lineItemId: selection.lineItemId || generateLineItemId(),
150
164
  }));
151
- const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants)));
165
+ const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption)));
152
166
  const response = await calculateTotals({
153
167
  lineItems,
154
168
  });
@@ -179,7 +193,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
179
193
  const errorSignal = signalsService.signal(null);
180
194
  const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
181
195
  // Internal calculate function
182
- const calculate = async (pricingServiceSelections, totalParticipants) => {
196
+ const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption) => {
183
197
  if (!pricingServiceSelections.length) {
184
198
  paymentDetailsSignal.set(null);
185
199
  return;
@@ -187,7 +201,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
187
201
  isLoadingSignal.set(true);
188
202
  errorSignal.set(null);
189
203
  try {
190
- const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
204
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
191
205
  paymentDetailsSignal.set(paymentDetails);
192
206
  }
193
207
  catch (error) {
@@ -215,24 +229,30 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
215
229
  // Mode 2: Lazy loading - pricingServiceSelections provided via internal config
216
230
  else if (config.pricingServiceSelections?.length) {
217
231
  serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
218
- void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1);
232
+ void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption);
219
233
  }
220
234
  // Mode 3: Reactive - observe BookingService signals (client-side only)
221
235
  else if (typeof window !== 'undefined' && bookingService) {
222
- // Computed signal that changes when services or selected time slots change (not participants)
236
+ // Computed signal that changes when services, selected time slots, or
237
+ // the consumer-selected payment option change (not participants).
223
238
  const servicesKey = signalsService.computed(() => {
224
239
  const selections = bookingService?.serviceSelections.get() || [];
225
240
  const timezone = bookingService?.timezone.get();
241
+ const selectedPaymentOption = bookingService?.selectedPaymentOption.get();
226
242
  return (selections
227
243
  .map((s) => `${s.service._id}:${s.timeSlot?.startDate ?? ''}:${s.timeSlot?.endDate ?? ''}`)
228
- .join(',') + `:${timezone ?? ''}`);
244
+ .join(',') +
245
+ `:${timezone ?? ''}` +
246
+ `:${selectedPaymentOption ?? ''}`);
229
247
  });
230
248
  signalsService.effect(() => {
231
- // Subscribe to servicesKey to trigger effect when service or time slot changes
249
+ // Subscribe to servicesKey to trigger effect when service, time
250
+ // slot, or selected payment option changes.
232
251
  servicesKey.get();
233
252
  // Read current selections without subscribing (peek)
234
253
  const selections = bookingService?.serviceSelections.peek() || [];
235
254
  const timezone = bookingService?.timezone.peek();
255
+ const selectedPaymentOption = bookingService?.selectedPaymentOption.peek();
236
256
  // For single service mode, take the first selection
237
257
  const firstSelection = selections[0];
238
258
  const numParticipants = firstSelection?.totalParticipants || 1;
@@ -246,7 +266,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
246
266
  if (pricingServiceSelections.length) {
247
267
  serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
248
268
  }
249
- void calculate(pricingServiceSelections, numParticipants);
269
+ void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption);
250
270
  });
251
271
  }
252
272
  return {
@@ -262,10 +282,12 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
262
282
  *
263
283
  * @param params.pricingServiceSelections - Pricing service selections for calculation
264
284
  * @param params.totalParticipants - Number of participants (default: 1)
285
+ * @param params.selectedPaymentOption - Optional consumer-selected payment option;
286
+ * overrides the service-config heuristic on each line item's `paymentOption`.
265
287
  */
266
288
  export async function loadPaymentConfig(params) {
267
289
  try {
268
- const { pricingServiceSelections, totalParticipants = 1 } = params;
290
+ const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, } = params;
269
291
  if (!pricingServiceSelections.length) {
270
292
  return {
271
293
  type: 'success',
@@ -275,7 +297,7 @@ export async function loadPaymentConfig(params) {
275
297
  },
276
298
  };
277
299
  }
278
- const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
300
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
279
301
  return {
280
302
  type: 'success',
281
303
  config: {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { Service } from '@wix/auto_sdk_bookings_services';
6
6
  import { type TimeSlotListServiceConfig, type TimeSlotsFilters } from './time-slot-list.def.js';
7
- import { TimeSlot } from '@wix/auto_sdk_bookings_event-time-slots';
7
+ import { TimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
8
8
  /**
9
9
  * Helper to get staff member name from time slots by ID
10
10
  * @internal - exported for testing
@@ -5,7 +5,7 @@
5
5
  import { implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
7
7
  import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
8
- import { LocationType, } from '@wix/auto_sdk_bookings_event-time-slots';
8
+ import { LocationType, } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import { fetchAvailability } from '../../api/fetch-availability/index.js';
10
10
  import { BookingServiceDefinition, } from '../booking/booking.js';
11
11
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -34,8 +34,8 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@wix/auto_sdk_bookings_bookings": "^1.0.183",
38
- "@wix/auto_sdk_bookings_event-time-slots": "^1.0.64",
37
+ "@wix/auto_sdk_bookings_bookings": "^1.0.192",
38
+ "@wix/auto_sdk_bookings_event-time-slots": "^1.0.112",
39
39
  "@wix/auto_sdk_bookings_pricing": "^1.0.87",
40
40
  "@wix/auto_sdk_bookings_services": "^1.0.226",
41
41
  "@wix/auto_sdk_ecom_cart-v-2": "^1.0.86",
@@ -75,5 +75,5 @@
75
75
  "groupId": "com.wixpress.headless-components"
76
76
  }
77
77
  },
78
- "falconPackageHash": "874dff357223e1f787076e35a7a2f3268eeb3e12e98104b2a779ebf8"
78
+ "falconPackageHash": "60796dfbabc73eb7b2ec7543eb4062986b78906203ede122471a8080"
79
79
  }