@wix/headless-bookings 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/cjs/dist/__mocks__/booking/booking-client-service.js +4 -1
  2. package/cjs/dist/__mocks__/services/services-mocks.d.ts +13 -0
  3. package/cjs/dist/__mocks__/services/services-mocks.js +92 -1
  4. package/cjs/dist/react/core/booking/Booking.d.ts +6 -0
  5. package/cjs/dist/react/core/booking/Booking.js +2 -0
  6. package/cjs/dist/react/core/payment/Payment.d.ts +2 -1
  7. package/cjs/dist/react/core/payment/Payment.js +11 -8
  8. package/cjs/dist/react/core/service/Service.js +4 -2
  9. package/cjs/dist/react/payment/Payment.d.ts +3 -2
  10. package/cjs/dist/react/payment/Payment.js +11 -5
  11. package/cjs/dist/services/booking/book-action/buildBookingRequest.js +13 -4
  12. package/cjs/dist/services/booking/book-action/types.d.ts +6 -0
  13. package/cjs/dist/services/booking/booking.d.ts +17 -0
  14. package/cjs/dist/services/booking/booking.js +8 -0
  15. package/cjs/dist/services/payment/payment.d.ts +3 -0
  16. package/cjs/dist/services/payment/payment.def.d.ts +15 -1
  17. package/cjs/dist/services/payment/payment.js +89 -58
  18. package/cjs/dist/services/service/service.js +1 -1
  19. package/cjs/dist/services/servicePayment.d.ts +26 -0
  20. package/cjs/dist/services/servicePayment.js +46 -0
  21. package/cjs/dist/utils/money.d.ts +12 -2
  22. package/cjs/dist/utils/money.js +30 -18
  23. package/dist/__mocks__/booking/booking-client-service.js +4 -1
  24. package/dist/__mocks__/services/services-mocks.d.ts +13 -0
  25. package/dist/__mocks__/services/services-mocks.js +91 -0
  26. package/dist/react/core/booking/Booking.d.ts +6 -0
  27. package/dist/react/core/booking/Booking.js +2 -0
  28. package/dist/react/core/payment/Payment.d.ts +2 -1
  29. package/dist/react/core/payment/Payment.js +11 -8
  30. package/dist/react/core/service/Service.js +4 -2
  31. package/dist/react/payment/Payment.d.ts +3 -2
  32. package/dist/react/payment/Payment.js +11 -5
  33. package/dist/services/booking/book-action/buildBookingRequest.js +13 -4
  34. package/dist/services/booking/book-action/types.d.ts +6 -0
  35. package/dist/services/booking/booking.d.ts +17 -0
  36. package/dist/services/booking/booking.js +8 -0
  37. package/dist/services/payment/payment.d.ts +3 -0
  38. package/dist/services/payment/payment.def.d.ts +15 -1
  39. package/dist/services/payment/payment.js +89 -58
  40. package/dist/services/service/service.js +1 -1
  41. package/dist/services/servicePayment.d.ts +26 -0
  42. package/dist/services/servicePayment.js +40 -0
  43. package/dist/utils/money.d.ts +12 -2
  44. package/dist/utils/money.js +30 -18
  45. package/package.json +3 -3
@@ -29,18 +29,20 @@ function createMockSignalsService() {
29
29
  };
30
30
  }
31
31
  function createMockBookingService(config = {}) {
32
- const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, selectedPaymentOption, } = config;
32
+ const { serviceSelections = [], location = null, timezone = 'UTC', formSubmission = null, selectedPaymentOption, depositSelected, } = 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
37
  const selectedPaymentOptionSignal = mockSignal(selectedPaymentOption);
38
+ const depositSelectedSignal = mockSignal(depositSelected);
38
39
  return {
39
40
  serviceSelections: serviceSelectionsSignal,
40
41
  location: locationSignal,
41
42
  timezone: timezoneSignal,
42
43
  formSubmission: formSubmissionSignal,
43
44
  selectedPaymentOption: selectedPaymentOptionSignal,
45
+ depositSelected: depositSelectedSignal,
44
46
  actions: {
45
47
  updateItem: vitest_1.vi.fn(),
46
48
  setItem: vitest_1.vi.fn(),
@@ -56,6 +58,7 @@ function createMockBookingService(config = {}) {
56
58
  setFormSubmission: vitest_1.vi.fn(),
57
59
  clearFormSubmission: vitest_1.vi.fn(),
58
60
  setSelectedPaymentOption: vitest_1.vi.fn(),
61
+ setDepositSelected: vitest_1.vi.fn(),
59
62
  setAllDay: vitest_1.vi.fn(),
60
63
  clearAll: vitest_1.vi.fn(),
61
64
  book: vitest_1.vi.fn(),
@@ -158,6 +158,19 @@ export declare function createLargeStaffMemberList(count: number): StaffMember[]
158
158
  * Mock service with deposit configured
159
159
  */
160
160
  export declare const mockServiceWithDeposit: Service;
161
+ /** Deposit set as a fixed amount under `depositDetails`, the newer field. */
162
+ export declare const mockServiceDepositDetailsAmount: Service;
163
+ /** Percentage deposit: `options.deposit` set, no amount anywhere on the entity. */
164
+ export declare const mockServicePercentageDeposit: Service;
165
+ /**
166
+ * Mock service with a deposit that may also be paid in full upfront — the only
167
+ * configuration where `booking.depositSelected` may be `false`.
168
+ */
169
+ export declare const mockServiceWithDepositFullUpfront: Service;
170
+ /**
171
+ * Mock VARIED-rate service with a deposit (both under `payment.varied`).
172
+ */
173
+ export declare const mockServiceVariedWithDeposit: Service;
161
174
  /**
162
175
  * Mock service with in-person payment option
163
176
  */
@@ -3,7 +3,7 @@
3
3
  * Shared mock data for booking service tests
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.mockServiceInPerson = exports.mockServiceWithDeposit = exports.mockSingleStaffMemberListConfig = exports.mockEmptyStaffMemberListConfig = exports.mockStaffMemberListConfig = exports.mockStaffMembers = exports.mockStaffMemberNoName = exports.mockStaffMemberMinimal = exports.mockStaffMember2 = exports.mockStaffMember = exports.mockAvailabilityFilter = exports.mockAvailabilityPagingMetadataLast = exports.mockAvailabilityPagingMetadata = exports.mockServicesWithAvailability = exports.mockServiceListConfigWithCategories = exports.mockCategories = exports.mockServiceConfig = exports.mockEmptyServiceListConfig = exports.mockServiceListConfigWithMore = exports.mockServiceListConfig = exports.mockPagingMetadataEmpty = exports.mockPagingMetadataWithMore = exports.mockPagingMetadata = exports.mockServices = exports.mockServiceDayRange = exports.mockServiceHourRange = exports.mockServiceFixedDuration = exports.mockServiceMinimal = exports.mockServiceCourse = exports.mockServiceClass = exports.mockServiceNoFee = exports.mockServiceCustomPrice = exports.mockServiceVariedPrice = exports.mockService = void 0;
6
+ exports.mockServiceInPerson = exports.mockServiceVariedWithDeposit = exports.mockServiceWithDepositFullUpfront = exports.mockServicePercentageDeposit = exports.mockServiceDepositDetailsAmount = exports.mockServiceWithDeposit = exports.mockSingleStaffMemberListConfig = exports.mockEmptyStaffMemberListConfig = exports.mockStaffMemberListConfig = exports.mockStaffMembers = exports.mockStaffMemberNoName = exports.mockStaffMemberMinimal = exports.mockStaffMember2 = exports.mockStaffMember = exports.mockAvailabilityFilter = exports.mockAvailabilityPagingMetadataLast = exports.mockAvailabilityPagingMetadata = exports.mockServicesWithAvailability = exports.mockServiceListConfigWithCategories = exports.mockCategories = exports.mockServiceConfig = exports.mockEmptyServiceListConfig = exports.mockServiceListConfigWithMore = exports.mockServiceListConfig = exports.mockPagingMetadataEmpty = exports.mockPagingMetadataWithMore = exports.mockPagingMetadata = exports.mockServices = exports.mockServiceDayRange = exports.mockServiceHourRange = exports.mockServiceFixedDuration = exports.mockServiceMinimal = exports.mockServiceCourse = exports.mockServiceClass = exports.mockServiceNoFee = exports.mockServiceCustomPrice = exports.mockServiceVariedPrice = exports.mockService = void 0;
7
7
  exports.createLargeServiceList = createLargeServiceList;
8
8
  exports.createLargeStaffMemberList = createLargeStaffMemberList;
9
9
  const auto_sdk_bookings_services_1 = require("@wix/auto_sdk_bookings_services");
@@ -540,6 +540,97 @@ exports.mockServiceWithDeposit = {
540
540
  },
541
541
  },
542
542
  };
543
+ /** Deposit set as a fixed amount under `depositDetails`, the newer field. */
544
+ exports.mockServiceDepositDetailsAmount = {
545
+ ...exports.mockService,
546
+ _id: 'test-service-deposit-details-amount',
547
+ name: 'Deposit Details Amount Service',
548
+ payment: {
549
+ rateType: auto_sdk_bookings_services_1.RateType.FIXED,
550
+ fixed: {
551
+ price: {
552
+ value: '100.00',
553
+ currency: 'USD',
554
+ },
555
+ depositDetails: {
556
+ amount: {
557
+ value: '25.00',
558
+ currency: 'USD',
559
+ },
560
+ },
561
+ },
562
+ options: {
563
+ online: true,
564
+ deposit: true,
565
+ },
566
+ },
567
+ };
568
+ /** Percentage deposit: `options.deposit` set, no amount anywhere on the entity. */
569
+ exports.mockServicePercentageDeposit = {
570
+ ...exports.mockService,
571
+ _id: 'test-service-percentage-deposit',
572
+ name: 'Percentage Deposit Service',
573
+ payment: {
574
+ rateType: auto_sdk_bookings_services_1.RateType.FIXED,
575
+ fixed: {
576
+ price: {
577
+ value: '10',
578
+ currency: 'USD',
579
+ },
580
+ fullUpfrontPaymentAllowed: true,
581
+ },
582
+ options: {
583
+ online: true,
584
+ inPerson: false,
585
+ deposit: true,
586
+ },
587
+ },
588
+ };
589
+ /**
590
+ * Mock service with a deposit that may also be paid in full upfront — the only
591
+ * configuration where `booking.depositSelected` may be `false`.
592
+ */
593
+ exports.mockServiceWithDepositFullUpfront = {
594
+ ...exports.mockService,
595
+ _id: 'test-service-deposit-full-upfront',
596
+ name: 'Service with Deposit and Full Upfront Allowed',
597
+ payment: {
598
+ rateType: auto_sdk_bookings_services_1.RateType.FIXED,
599
+ fixed: {
600
+ price: {
601
+ value: '100.00',
602
+ currency: 'USD',
603
+ },
604
+ deposit: {
605
+ value: '30.00',
606
+ currency: 'USD',
607
+ },
608
+ fullUpfrontPaymentAllowed: true,
609
+ },
610
+ },
611
+ };
612
+ /**
613
+ * Mock VARIED-rate service with a deposit (both under `payment.varied`).
614
+ */
615
+ exports.mockServiceVariedWithDeposit = {
616
+ ...exports.mockService,
617
+ _id: 'test-service-varied-deposit',
618
+ name: 'Varied Service with Deposit',
619
+ payment: {
620
+ rateType: auto_sdk_bookings_services_1.RateType.VARIED,
621
+ varied: {
622
+ defaultPrice: {
623
+ value: '80.00',
624
+ currency: 'USD',
625
+ },
626
+ deposit: {
627
+ value: '20.00',
628
+ currency: 'USD',
629
+ },
630
+ fullUpfrontPaymentAllowed: true,
631
+ },
632
+ },
633
+ };
543
634
  /**
544
635
  * Mock service with in-person payment option
545
636
  */
@@ -45,6 +45,12 @@ export interface BookingData {
45
45
  * heuristic applies. Set via `actions.setSelectedPaymentOption`.
46
46
  */
47
47
  selectedPaymentOption: SelectedPaymentOption | undefined;
48
+ /**
49
+ * Deposit-vs-full-upfront choice. `true` pays only the deposit now, `false`
50
+ * pays the full price now, `undefined` means not chosen. Set via
51
+ * `actions.setDepositSelected`.
52
+ */
53
+ depositSelected: boolean | undefined;
48
54
  actions: BookingServiceActions;
49
55
  }
50
56
  /**
@@ -118,6 +118,7 @@ function Data(props) {
118
118
  const timezone = bookingService.timezone.get();
119
119
  const formSubmission = bookingService.formSubmission.get();
120
120
  const selectedPaymentOption = bookingService.selectedPaymentOption.get();
121
+ const depositSelected = bookingService.depositSelected.get();
121
122
  const { actions } = bookingService;
122
123
  const location = resolveLocation(bookingLocation, serviceSelections);
123
124
  return children({
@@ -126,6 +127,7 @@ function Data(props) {
126
127
  timezone,
127
128
  formSubmission,
128
129
  selectedPaymentOption,
130
+ depositSelected,
129
131
  actions,
130
132
  });
131
133
  }
@@ -172,7 +172,8 @@ export interface ServiceOptionsProps {
172
172
  }
173
173
  /**
174
174
  * Core component that exposes service payment options via render props.
175
- * Reads isOnline, isInPerson, isDeposit from the payment service signal.
175
+ * Reads isOnline, isInPerson, isDeposit, canPayFullUpfront from the payment
176
+ * service signal.
176
177
  *
177
178
  * @component
178
179
  */
@@ -93,10 +93,12 @@ function Data(props) {
93
93
  const additionalInfoList = paymentDetails?.additionalInfo?.lineItems || [];
94
94
  const firstLineItemInfo = additionalInfoList[0];
95
95
  const customPrice = firstLineItemInfo?.customPrice || null;
96
- // Computed flags
97
- const hasTax = tax !== null && tax.amount > 0;
98
- const hasDiscount = discount !== null && discount.amount > 0;
99
- const isDeposit = payNow !== null && total !== null && payNow.amount < total.amount;
96
+ // Computed flags (amounts may be strings — compare numerically)
97
+ const hasTax = tax !== null && Number(tax.amount) > 0;
98
+ const hasDiscount = discount !== null && Number(discount.amount) > 0;
99
+ const isDeposit = payNow !== null &&
100
+ total !== null &&
101
+ Number(payNow.amount) < Number(total.amount);
100
102
  return props.children({
101
103
  subtotal,
102
104
  total,
@@ -202,7 +204,8 @@ function LineItemProvider(props) {
202
204
  }
203
205
  /**
204
206
  * Core component that exposes service payment options via render props.
205
- * Reads isOnline, isInPerson, isDeposit from the payment service signal.
207
+ * Reads isOnline, isInPerson, isDeposit, canPayFullUpfront from the payment
208
+ * service signal.
206
209
  *
207
210
  * @component
208
211
  */
@@ -229,9 +232,9 @@ function ServiceOptions(props) {
229
232
  */
230
233
  function LineItemInfo(props) {
231
234
  const { lineItem, currency } = useLineItemContext();
232
- // Computed flags
233
- const hasDiscount = lineItem.discount !== null && lineItem.discount.amount > 0;
234
- const hasDeposit = lineItem.deposit !== null && lineItem.deposit.amount > 0;
235
+ // Computed flags (amounts may be strings — compare numerically)
236
+ const hasDiscount = lineItem.discount !== null && Number(lineItem.discount.amount) > 0;
237
+ const hasDeposit = lineItem.deposit !== null && Number(lineItem.deposit.amount) > 0;
235
238
  return props.children({
236
239
  lineItem,
237
240
  currency,
@@ -28,8 +28,10 @@ function RootContent({ children, }) {
28
28
  const selected = serviceService.selectedSignal.get();
29
29
  const bookable = serviceService.bookableSignal.get();
30
30
  // In "from booking signal" mode, render null when no service is selected
31
- // The signal will trigger a re-render when a service is selected
32
- if (!service || !service._id) {
31
+ // The signal will trigger a re-render when a service is selected.
32
+ // While a slug/id-based fetch is in flight, keep rendering the subtree so
33
+ // consumers' loading skeletons (<Service.Raw>) can show instead of a blank screen.
34
+ if ((!service || !service._id) && !serviceService.isLoadingSignal.get()) {
33
35
  return null;
34
36
  }
35
37
  if (typeof children === 'function') {
@@ -210,7 +210,8 @@ export interface ServiceOptionsProps {
210
210
  *
211
211
  * Payment scenarios:
212
212
  * - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
213
- * - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
213
+ * - `isOnline=true, isInPerson=false, isDeposit=true, canPayFullUpfront=false` → Pay now + security deposit
214
+ * - `isOnline=true, isInPerson=false, isDeposit=true, canPayFullUpfront=true` → Radio: Pay deposit now / Pay in full now
214
215
  * - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
215
216
  * - `isOnline=false, isInPerson=true` → Pay on arrival only
216
217
  *
@@ -223,7 +224,7 @@ export interface ServiceOptionsProps {
223
224
  * @example
224
225
  * ```tsx
225
226
  * <Payment.ServiceOptions asChild>
226
- * {({ isOnline, isInPerson, isDeposit }) => (
227
+ * {({ isOnline, isInPerson, isDeposit, canPayFullUpfront }) => (
227
228
  * // render the correct payment scenario
228
229
  * )}
229
230
  * </Payment.ServiceOptions>
@@ -183,7 +183,7 @@ exports.Total.displayName = 'Payment.Total';
183
183
  */
184
184
  exports.PayNow = react_1.default.forwardRef((props, ref) => {
185
185
  const { asChild, children, className } = props;
186
- return ((0, jsx_runtime_1.jsx)(CorePayment.Data, { children: ({ payNow, isDeposit }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, customElementProps: { payNow, isDeposit }, "data-testid": exports.TestIds.paymentPayNow, "data-has-deposit": isDeposit, children: isDeposit && payNow && payNow.amount > 0 && ((0, jsx_runtime_1.jsx)(react_3.Money, { money: payNow })) })) }));
186
+ return ((0, jsx_runtime_1.jsx)(CorePayment.Data, { children: ({ payNow, isDeposit }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, customElementProps: { payNow, isDeposit }, "data-testid": exports.TestIds.paymentPayNow, "data-has-deposit": isDeposit, children: isDeposit && payNow && Number(payNow.amount) > 0 && ((0, jsx_runtime_1.jsx)(react_3.Money, { money: payNow })) })) }));
187
187
  });
188
188
  exports.PayNow.displayName = 'Payment.PayNow';
189
189
  /**
@@ -198,7 +198,7 @@ exports.PayNow.displayName = 'Payment.PayNow';
198
198
  */
199
199
  exports.PayLater = react_1.default.forwardRef((props, ref) => {
200
200
  const { asChild, children, className } = props;
201
- return ((0, jsx_runtime_1.jsx)(CorePayment.Data, { children: ({ payLater, isDeposit }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, customElementProps: { payLater, isDeposit }, "data-testid": exports.TestIds.paymentPayLater, "data-has-deposit": isDeposit, children: isDeposit && payLater && payLater.amount > 0 && ((0, jsx_runtime_1.jsx)(react_3.Money, { money: payLater })) })) }));
201
+ return ((0, jsx_runtime_1.jsx)(CorePayment.Data, { children: ({ payLater, isDeposit }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, customElementProps: { payLater, isDeposit }, "data-testid": exports.TestIds.paymentPayLater, "data-has-deposit": isDeposit, children: isDeposit && payLater && Number(payLater.amount) > 0 && ((0, jsx_runtime_1.jsx)(react_3.Money, { money: payLater })) })) }));
202
202
  });
203
203
  exports.PayLater.displayName = 'Payment.PayLater';
204
204
  /**
@@ -207,7 +207,8 @@ exports.PayLater.displayName = 'Payment.PayLater';
207
207
  *
208
208
  * Payment scenarios:
209
209
  * - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
210
- * - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
210
+ * - `isOnline=true, isInPerson=false, isDeposit=true, canPayFullUpfront=false` → Pay now + security deposit
211
+ * - `isOnline=true, isInPerson=false, isDeposit=true, canPayFullUpfront=true` → Radio: Pay deposit now / Pay in full now
211
212
  * - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
212
213
  * - `isOnline=false, isInPerson=true` → Pay on arrival only
213
214
  *
@@ -220,7 +221,7 @@ exports.PayLater.displayName = 'Payment.PayLater';
220
221
  * @example
221
222
  * ```tsx
222
223
  * <Payment.ServiceOptions asChild>
223
- * {({ isOnline, isInPerson, isDeposit }) => (
224
+ * {({ isOnline, isInPerson, isDeposit, canPayFullUpfront }) => (
224
225
  * // render the correct payment scenario
225
226
  * )}
226
227
  * </Payment.ServiceOptions>
@@ -228,7 +229,12 @@ exports.PayLater.displayName = 'Payment.PayLater';
228
229
  */
229
230
  exports.ServiceOptions = react_1.default.forwardRef((props, ref) => {
230
231
  const { asChild, children } = props;
231
- return ((0, jsx_runtime_1.jsx)(CorePayment.ServiceOptions, { children: ({ isOnline, isInPerson, isDeposit }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, customElement: children, customElementProps: { isOnline, isInPerson, isDeposit }, "data-testid": exports.TestIds.paymentServiceOptions, "data-is-online": isOnline, "data-is-in-person": isInPerson, "data-is-deposit": isDeposit, children: null })) }));
232
+ return ((0, jsx_runtime_1.jsx)(CorePayment.ServiceOptions, { children: ({ isOnline, isInPerson, isDeposit, canPayFullUpfront }) => ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, customElement: children, customElementProps: {
233
+ isOnline,
234
+ isInPerson,
235
+ isDeposit,
236
+ canPayFullUpfront,
237
+ }, "data-testid": exports.TestIds.paymentServiceOptions, "data-is-online": isOnline, "data-is-in-person": isInPerson, "data-is-deposit": isDeposit, "data-can-pay-full-upfront": canPayFullUpfront, children: null })) }));
232
238
  });
233
239
  exports.ServiceOptions.displayName = 'Payment.ServiceOptions';
234
240
  /**
@@ -7,6 +7,7 @@ exports.buildBookingRequest = buildBookingRequest;
7
7
  const auto_sdk_bookings_availability_time_slots_1 = require("@wix/auto_sdk_bookings_availability-time-slots");
8
8
  const auto_sdk_bookings_bookings_1 = require("@wix/auto_sdk_bookings_bookings");
9
9
  const constants_js_1 = require("../../constants.js");
10
+ const servicePayment_js_1 = require("../../servicePayment.js");
10
11
  /**
11
12
  * Derives the selected payment option from service payment settings.
12
13
  * Maps service.payment.options to booking API's selectedPaymentOption.
@@ -56,12 +57,20 @@ function buildBookingRequest(params) {
56
57
  const resource = item.timeSlot.resources?.[0] ?? item.resources?.[0];
57
58
  // Get location: prefer timeSlot.location, fallback to params.location
58
59
  const slotLocation = item.timeSlot.location ?? location;
60
+ // Consumer override (if set) wins over the service-config heuristic.
61
+ const selectedPaymentOption = params.selectedPaymentOption ?? deriveSelectedPaymentOption(item.service);
62
+ // The only state where the schema allows `false`. Outside it, omitting the key
63
+ // leaves the server default in place.
64
+ const depositSelectedApplies = params.depositSelected !== undefined &&
65
+ selectedPaymentOption === 'ONLINE' &&
66
+ (0, servicePayment_js_1.requiresDeposit)(item.service) &&
67
+ (0, servicePayment_js_1.isFullUpfrontPaymentAllowed)(item.service);
59
68
  return {
60
69
  booking: {
61
- // Payment option: consumer override (if set) wins over the
62
- // service-config heuristic.
63
- selectedPaymentOption: params.selectedPaymentOption ??
64
- deriveSelectedPaymentOption(item.service),
70
+ selectedPaymentOption,
71
+ ...(depositSelectedApplies && {
72
+ depositSelected: params.depositSelected,
73
+ }),
65
74
  // Participants
66
75
  totalParticipants: item.totalParticipants || 1,
67
76
  // App that created the booking (used to filter bookings by source app).
@@ -59,6 +59,12 @@ export interface BookActionParams {
59
59
  * service. When omitted, the existing service-config heuristic applies.
60
60
  */
61
61
  selectedPaymentOption?: SelectedPaymentOption;
62
+ /**
63
+ * Optional consumer choice: `true` pays only the deposit now, `false` pays the
64
+ * full price now. Dropped unless the booking is ONLINE and the service has a
65
+ * deposit with `fullUpfrontPaymentAllowed`. Omit to keep the server default.
66
+ */
67
+ depositSelected?: boolean;
62
68
  /**
63
69
  * DAY-range only. When `true`, the picked per-day slots are collapsed into a
64
70
  * single Booking spanning the whole range (`firstSlot.startDate ->
@@ -89,6 +89,12 @@ export interface BookingServiceActions {
89
89
  * is cleared and the heuristic applies again.
90
90
  */
91
91
  setSelectedPaymentOption(value: SelectedPaymentOption | null): void;
92
+ /**
93
+ * Set (or clear with `undefined`) the deposit-vs-full-upfront choice. `true`
94
+ * charges only the deposit now, `false` charges the full price now. Gate the UI
95
+ * on `Payment.ServiceOptions`' `isDeposit && canPayFullUpfront`.
96
+ */
97
+ setDepositSelected(value: boolean | undefined): void;
92
98
  /**
93
99
  * Toggle the DAY-range `allDay` flag. When `true`, `book()` collapses the
94
100
  * picked per-day slots into one range-spanning Booking created via
@@ -117,6 +123,12 @@ export interface BookingService {
117
123
  * service-config heuristic. Set to `'ONLINE'` or `'OFFLINE'` to override.
118
124
  */
119
125
  selectedPaymentOption: Signal<SelectedPaymentOption | undefined>;
126
+ /**
127
+ * Signal containing the deposit-vs-full-upfront choice. `true` pays only the
128
+ * deposit now, `false` pays the full price now. `undefined` means "not chosen"
129
+ * — `book()` omits the field and the preview keeps its deposit default.
130
+ */
131
+ depositSelected: Signal<boolean | undefined>;
120
132
  /** Actions for modifying booking state */
121
133
  actions: BookingServiceActions;
122
134
  }
@@ -146,6 +158,11 @@ export interface BookingServiceConfig {
146
158
  * service-config heuristic; set to `'ONLINE'` or `'OFFLINE'` to override.
147
159
  */
148
160
  selectedPaymentOption?: SelectedPaymentOption;
161
+ /**
162
+ * Optional initial deposit-vs-full-upfront choice. Omit to keep the server's
163
+ * default.
164
+ */
165
+ depositSelected?: boolean;
149
166
  /** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
150
167
  appId?: string;
151
168
  /**
@@ -74,6 +74,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
74
74
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
75
75
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
76
76
  const selectedPaymentOptionSignal = signalsService.signal(config.selectedPaymentOption);
77
+ const depositSelectedSignal = signalsService.signal(config.depositSelected);
77
78
  // Internal-only signal: set via `setAllDay`, read by `book()`. Not
78
79
  // exposed on the service surface — consumers derive and push the value
79
80
  // (e.g. from the picker's selection mode), so there's no read-back.
@@ -176,6 +177,10 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
176
177
  setSelectedPaymentOption(value) {
177
178
  selectedPaymentOptionSignal.set(value ?? undefined);
178
179
  },
180
+ // Deposit vs full upfront action
181
+ setDepositSelected(value) {
182
+ depositSelectedSignal.set(value);
183
+ },
179
184
  // All-day (DAY-range) action
180
185
  setAllDay(value) {
181
186
  allDaySignal.set(value);
@@ -195,6 +200,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
195
200
  const location = locationSignal.get();
196
201
  const timezone = timezoneSignal.get();
197
202
  const selectedPaymentOption = selectedPaymentOptionSignal.get();
203
+ const depositSelected = depositSelectedSignal.get();
198
204
  const allDay = allDaySignal.get();
199
205
  // Validation happens inside executeBookAction
200
206
  return (0, bookAction_js_1.executeBookAction)({
@@ -205,6 +211,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
205
211
  appId: config.appId,
206
212
  sourceAppId: config.sourceAppId,
207
213
  selectedPaymentOption,
214
+ depositSelected,
208
215
  allDay,
209
216
  });
210
217
  },
@@ -215,6 +222,7 @@ exports.BookingService = services_definitions_1.implementService.withConfig()(ex
215
222
  timezone: timezoneSignal,
216
223
  formSubmission: formSubmissionSignal,
217
224
  selectedPaymentOption: selectedPaymentOptionSignal,
225
+ depositSelected: depositSelectedSignal,
218
226
  actions,
219
227
  };
220
228
  });
@@ -28,9 +28,12 @@ export type ErrorPaymentConfigResult = {
28
28
  * @param params.totalParticipants - Number of participants (default: 1)
29
29
  * @param params.selectedPaymentOption - Optional consumer-selected payment option;
30
30
  * overrides the service-config heuristic on each line item's `paymentOption`.
31
+ * @param params.depositSelected - Optional deposit-vs-full-upfront choice; `false`
32
+ * resolves a deposit service to full payment. Omit to keep the deposit default.
31
33
  */
32
34
  export declare function loadPaymentConfig(params: {
33
35
  pricingServiceSelections: PricingServiceSelection[];
34
36
  totalParticipants?: number;
35
37
  selectedPaymentOption?: SelectedPaymentOption;
38
+ depositSelected?: boolean;
36
39
  }): Promise<SuccessPaymentConfigResult | ErrorPaymentConfigResult>;
@@ -16,8 +16,17 @@ export interface ServicePaymentOptions {
16
16
  isOnline: boolean;
17
17
  /** Customer can pay in person (on arrival) */
18
18
  isInPerson: boolean;
19
- /** A deposit is required at booking time */
19
+ /**
20
+ * A deposit is required at booking time — including percentage deposits, which
21
+ * set no amount. For the amount use `Payment.PayNow` / `Payment.PayLater`.
22
+ */
20
23
  isDeposit: boolean;
24
+ /**
25
+ * Customers may pay the full price upfront instead of only the deposit.
26
+ * Meaningful only with `isDeposit`; together they gate the
27
+ * deposit-vs-pay-in-full toggle.
28
+ */
29
+ canPayFullUpfront: boolean;
21
30
  }
22
31
  /**
23
32
  * Payment option for line items
@@ -83,6 +92,11 @@ export interface PaymentServiceInternalConfig extends PaymentServiceConfig {
83
92
  * set, overrides the service-config heuristic in line item `paymentOption`.
84
93
  */
85
94
  selectedPaymentOption?: SelectedPaymentOption;
95
+ /**
96
+ * Deposit-vs-full-upfront choice for lazy loading. `false` resolves a deposit
97
+ * service to full payment instead of `DEPOSIT_ONLINE`.
98
+ */
99
+ depositSelected?: boolean;
86
100
  }
87
101
  export interface PaymentServiceAPI {
88
102
  paymentDetailsSignal: Signal<PaymentDetails | null>;