@wix/headless-bookings 0.0.116 → 0.0.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/dist/react/booking-form/BookingForm.d.ts +2 -0
- package/cjs/dist/react/booking-form/BookingForm.js +2 -2
- package/cjs/dist/react/core/booking-form/BookingForm.d.ts +2 -0
- package/cjs/dist/react/core/booking-form/BookingForm.js +3 -3
- package/cjs/dist/react/core/payment/Payment.d.ts +14 -1
- package/cjs/dist/react/core/payment/Payment.js +12 -0
- package/cjs/dist/react/payment/Payment.d.ts +37 -1
- package/cjs/dist/react/payment/Payment.js +32 -1
- package/cjs/dist/services/booking/book-action/bookAction.js +1 -0
- package/cjs/dist/services/booking/book-action/buildCheckoutRequest.d.ts +2 -0
- package/cjs/dist/services/booking/book-action/buildCheckoutRequest.js +2 -2
- package/cjs/dist/services/booking/book-action/types.d.ts +2 -0
- package/cjs/dist/services/booking/booking.d.ts +2 -0
- package/cjs/dist/services/booking/booking.js +1 -0
- package/cjs/dist/services/booking-form/booking-form.d.ts +4 -0
- package/cjs/dist/services/booking-form/booking-form.js +11 -8
- package/cjs/dist/services/index.d.ts +1 -1
- package/cjs/dist/services/payment/payment.d.ts +1 -1
- package/cjs/dist/services/payment/payment.def.d.ts +15 -0
- package/cjs/dist/services/payment/payment.js +51 -3
- package/dist/react/booking-form/BookingForm.d.ts +2 -0
- package/dist/react/booking-form/BookingForm.js +2 -2
- package/dist/react/core/booking-form/BookingForm.d.ts +2 -0
- package/dist/react/core/booking-form/BookingForm.js +3 -3
- package/dist/react/core/payment/Payment.d.ts +14 -1
- package/dist/react/core/payment/Payment.js +11 -0
- package/dist/react/payment/Payment.d.ts +37 -1
- package/dist/react/payment/Payment.js +31 -0
- package/dist/services/booking/book-action/bookAction.js +1 -0
- package/dist/services/booking/book-action/buildCheckoutRequest.d.ts +2 -0
- package/dist/services/booking/book-action/buildCheckoutRequest.js +2 -2
- package/dist/services/booking/book-action/types.d.ts +2 -0
- package/dist/services/booking/booking.d.ts +2 -0
- package/dist/services/booking/booking.js +1 -0
- package/dist/services/booking-form/booking-form.d.ts +4 -0
- package/dist/services/booking-form/booking-form.js +11 -8
- package/dist/services/index.d.ts +1 -1
- package/dist/services/payment/payment.d.ts +1 -1
- package/dist/services/payment/payment.def.d.ts +15 -0
- package/dist/services/payment/payment.js +51 -3
- package/package.json +2 -2
|
@@ -21,6 +21,8 @@ export interface RootProps {
|
|
|
21
21
|
serviceIds?: string[];
|
|
22
22
|
/** Optional additional metadata to pass to FormService */
|
|
23
23
|
additionalMetadata?: Record<string, string | string[]>;
|
|
24
|
+
/** Optional namespace override for the form. Defaults to the bookings namespace. */
|
|
25
|
+
namespace?: string;
|
|
24
26
|
/** Field map to override default booking field components */
|
|
25
27
|
fieldMap: FieldMap;
|
|
26
28
|
/**
|
|
@@ -151,8 +151,8 @@ exports.TestIds = {
|
|
|
151
151
|
* ```
|
|
152
152
|
*/
|
|
153
153
|
exports.Root = react_1.default.forwardRef((props, ref) => {
|
|
154
|
-
const { children, asChild, className, formId, serviceIds, additionalMetadata, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', loadErrorFallback, ...otherProps } = props;
|
|
155
|
-
return ((0, jsx_runtime_1.jsx)(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: (0, jsx_runtime_1.jsx)(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: (renderProps) => {
|
|
154
|
+
const { children, asChild, className, formId, serviceIds, additionalMetadata, namespace, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', loadErrorFallback, ...otherProps } = props;
|
|
155
|
+
return ((0, jsx_runtime_1.jsx)(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: (0, jsx_runtime_1.jsx)(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, namespace: namespace, fieldMap: fieldMap, children: (renderProps) => {
|
|
156
156
|
// If children is a render prop function, call it with the render props
|
|
157
157
|
if (typeof children === 'function') {
|
|
158
158
|
return children(renderProps);
|
|
@@ -44,6 +44,8 @@ interface BookingFormBaseProps {
|
|
|
44
44
|
serviceIds?: string[];
|
|
45
45
|
/** Optional additional metadata to pass to FormService */
|
|
46
46
|
additionalMetadata?: Record<string, string | string[]>;
|
|
47
|
+
/** Optional namespace override for the form. Defaults to the bookings namespace. */
|
|
48
|
+
namespace?: string;
|
|
47
49
|
/** Field map to override default booking field components */
|
|
48
50
|
fieldMap: FieldMap;
|
|
49
51
|
/** Render prop function that receives form data and actions */
|
|
@@ -129,13 +129,13 @@ const signals_1 = require("@wix/services-definitions/core-services/signals");
|
|
|
129
129
|
* ```
|
|
130
130
|
*/
|
|
131
131
|
function Root(props) {
|
|
132
|
-
const { serviceIds, additionalMetadata, fieldMap, children } = props;
|
|
132
|
+
const { serviceIds, additionalMetadata, namespace, fieldMap, children } = props;
|
|
133
133
|
// Create formRef to pass to Form.Root and BookingFormContent
|
|
134
134
|
const formRef = (0, react_1.useRef)(null);
|
|
135
135
|
const bookingFormServiceConfig = props.form
|
|
136
|
-
? { form: props.form }
|
|
136
|
+
? { form: props.form, ...(namespace !== undefined ? { namespace } : {}) }
|
|
137
137
|
: props.formId
|
|
138
|
-
? { formId: props.formId, additionalMetadata, serviceIds }
|
|
138
|
+
? { formId: props.formId, additionalMetadata, serviceIds, namespace }
|
|
139
139
|
: undefined;
|
|
140
140
|
// Use Form.Root to set up FormService, then nest WixServices for BookingFormService
|
|
141
141
|
// This allows BookingFormService to access FormService via getService internally
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Note: Currently supports single service only
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { type PaymentServiceConfig, type PricingServiceSelection } from '../../../services/payment/payment.js';
|
|
7
|
+
import { type PaymentServiceConfig, type PricingServiceSelection, type ServicePaymentOptions } from '../../../services/payment/payment.js';
|
|
8
8
|
import type { MoneyData } from '@wix/headless-components/react';
|
|
9
9
|
/**
|
|
10
10
|
* Props for Payment.Root component
|
|
@@ -164,6 +164,19 @@ export interface LineItemProviderProps {
|
|
|
164
164
|
* ```
|
|
165
165
|
*/
|
|
166
166
|
export declare function LineItemProvider(props: LineItemProviderProps): React.ReactNode;
|
|
167
|
+
/**
|
|
168
|
+
* Props for ServiceOptions render prop component
|
|
169
|
+
*/
|
|
170
|
+
export interface ServiceOptionsProps {
|
|
171
|
+
children: (options: ServicePaymentOptions) => React.ReactNode;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Core component that exposes service payment options via render props.
|
|
175
|
+
* Reads isOnline, isInPerson, isDeposit from the payment service signal.
|
|
176
|
+
*
|
|
177
|
+
* @component
|
|
178
|
+
*/
|
|
179
|
+
export declare function ServiceOptions(props: ServiceOptionsProps): React.ReactNode;
|
|
167
180
|
/**
|
|
168
181
|
* Props for LineItemInfo render prop component
|
|
169
182
|
*/
|
|
@@ -8,6 +8,7 @@ exports.Data = Data;
|
|
|
8
8
|
exports.LineItems = LineItems;
|
|
9
9
|
exports.useLineItemContext = useLineItemContext;
|
|
10
10
|
exports.LineItemProvider = LineItemProvider;
|
|
11
|
+
exports.ServiceOptions = ServiceOptions;
|
|
11
12
|
exports.LineItemInfo = LineItemInfo;
|
|
12
13
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
13
14
|
/**
|
|
@@ -199,6 +200,17 @@ function LineItemProvider(props) {
|
|
|
199
200
|
};
|
|
200
201
|
return ((0, jsx_runtime_1.jsx)(LineItemContext.Provider, { value: contextValue, children: children }));
|
|
201
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Core component that exposes service payment options via render props.
|
|
205
|
+
* Reads isOnline, isInPerson, isDeposit from the payment service signal.
|
|
206
|
+
*
|
|
207
|
+
* @component
|
|
208
|
+
*/
|
|
209
|
+
function ServiceOptions(props) {
|
|
210
|
+
const paymentService = (0, services_manager_react_1.useService)(payment_js_1.PaymentServiceDefinition);
|
|
211
|
+
const serviceOptions = paymentService.serviceOptionsSignal.get();
|
|
212
|
+
return props.children(serviceOptions);
|
|
213
|
+
}
|
|
202
214
|
/**
|
|
203
215
|
* Core component that provides access to line item info via render props.
|
|
204
216
|
* Must be used within LineItemProvider.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { AsChildChildren } from '@wix/headless-utils/react';
|
|
7
7
|
import { MoneyData } from '@wix/headless-components/react';
|
|
8
|
-
import type { PaymentServiceConfig, PricingServiceSelection } from '../../services/payment/payment.js';
|
|
8
|
+
import type { PaymentServiceConfig, PricingServiceSelection, ServicePaymentOptions } from '../../services/payment/payment.js';
|
|
9
9
|
export declare const TestIds: {
|
|
10
10
|
readonly paymentRoot: "payment-root";
|
|
11
11
|
readonly paymentSubtotal: "payment-subtotal";
|
|
@@ -19,6 +19,7 @@ export declare const TestIds: {
|
|
|
19
19
|
readonly paymentLineItemTotal: "payment-line-item-total";
|
|
20
20
|
readonly paymentLineItemSubtotal: "payment-line-item-subtotal";
|
|
21
21
|
readonly paymentLineItemDeposit: "payment-line-item-deposit";
|
|
22
|
+
readonly paymentServiceOptions: "payment-service-options";
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
24
25
|
* Props for Payment.Root component
|
|
@@ -194,6 +195,41 @@ export interface PayLaterProps extends Omit<React.HTMLAttributes<HTMLElement>, '
|
|
|
194
195
|
* ```
|
|
195
196
|
*/
|
|
196
197
|
export declare const PayLater: React.ForwardRefExoticComponent<PayLaterProps & React.RefAttributes<HTMLElement>>;
|
|
198
|
+
/**
|
|
199
|
+
* Props for Payment.ServiceOptions component
|
|
200
|
+
*/
|
|
201
|
+
export interface ServiceOptionsProps {
|
|
202
|
+
asChild?: boolean;
|
|
203
|
+
/** Render prop receiving service payment options. This component renders no
|
|
204
|
+
* visible DOM element by default — it is a flags-only data component. */
|
|
205
|
+
children?: AsChildChildren<ServicePaymentOptions>;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Exposes service payment options via render props.
|
|
209
|
+
* Use this to determine which payment scenario to render without reading the service entity directly.
|
|
210
|
+
*
|
|
211
|
+
* Payment scenarios:
|
|
212
|
+
* - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
|
|
213
|
+
* - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
|
|
214
|
+
* - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
|
|
215
|
+
* - `isOnline=false, isInPerson=true` → Pay on arrival only
|
|
216
|
+
*
|
|
217
|
+
* @component
|
|
218
|
+
* Note: This component renders no visible DOM element. It is a flags-only data
|
|
219
|
+
* component — use `asChild` with a render function to consume the flags.
|
|
220
|
+
* For the actual split amounts (deposit / pay-later), use `Payment.PayNow` and
|
|
221
|
+
* `Payment.PayLater`.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```tsx
|
|
225
|
+
* <Payment.ServiceOptions asChild>
|
|
226
|
+
* {({ isOnline, isInPerson, isDeposit }) => (
|
|
227
|
+
* // render the correct payment scenario
|
|
228
|
+
* )}
|
|
229
|
+
* </Payment.ServiceOptions>
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export declare const ServiceOptions: React.ForwardRefExoticComponent<ServiceOptionsProps & React.RefAttributes<HTMLElement>>;
|
|
197
233
|
/**
|
|
198
234
|
* Props for Payment.LineItems component (Container Level)
|
|
199
235
|
*/
|
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.LineItemDeposit = exports.LineItemSubtotal = exports.LineItemTotal = exports.LineItemRepeater = exports.LineItemsList = exports.LineItems = exports.PayLater = exports.PayNow = exports.Total = exports.Tax = exports.Subtotal = exports.Root = exports.TestIds = void 0;
|
|
39
|
+
exports.LineItemDeposit = exports.LineItemSubtotal = exports.LineItemTotal = exports.LineItemRepeater = exports.LineItemsList = exports.LineItems = exports.ServiceOptions = exports.PayLater = exports.PayNow = exports.Total = exports.Tax = exports.Subtotal = exports.Root = exports.TestIds = void 0;
|
|
40
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
41
41
|
/**
|
|
42
42
|
* Payment - High-level component for displaying payment information
|
|
@@ -59,6 +59,7 @@ exports.TestIds = {
|
|
|
59
59
|
paymentLineItemTotal: 'payment-line-item-total',
|
|
60
60
|
paymentLineItemSubtotal: 'payment-line-item-subtotal',
|
|
61
61
|
paymentLineItemDeposit: 'payment-line-item-deposit',
|
|
62
|
+
paymentServiceOptions: 'payment-service-options',
|
|
62
63
|
};
|
|
63
64
|
/**
|
|
64
65
|
* Root component that provides payment context to the entire app.
|
|
@@ -200,6 +201,36 @@ exports.PayLater = react_1.default.forwardRef((props, ref) => {
|
|
|
200
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
202
|
});
|
|
202
203
|
exports.PayLater.displayName = 'Payment.PayLater';
|
|
204
|
+
/**
|
|
205
|
+
* Exposes service payment options via render props.
|
|
206
|
+
* Use this to determine which payment scenario to render without reading the service entity directly.
|
|
207
|
+
*
|
|
208
|
+
* Payment scenarios:
|
|
209
|
+
* - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
|
|
210
|
+
* - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
|
|
211
|
+
* - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
|
|
212
|
+
* - `isOnline=false, isInPerson=true` → Pay on arrival only
|
|
213
|
+
*
|
|
214
|
+
* @component
|
|
215
|
+
* Note: This component renders no visible DOM element. It is a flags-only data
|
|
216
|
+
* component — use `asChild` with a render function to consume the flags.
|
|
217
|
+
* For the actual split amounts (deposit / pay-later), use `Payment.PayNow` and
|
|
218
|
+
* `Payment.PayLater`.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```tsx
|
|
222
|
+
* <Payment.ServiceOptions asChild>
|
|
223
|
+
* {({ isOnline, isInPerson, isDeposit }) => (
|
|
224
|
+
* // render the correct payment scenario
|
|
225
|
+
* )}
|
|
226
|
+
* </Payment.ServiceOptions>
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
exports.ServiceOptions = react_1.default.forwardRef((props, ref) => {
|
|
230
|
+
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
|
+
});
|
|
233
|
+
exports.ServiceOptions.displayName = 'Payment.ServiceOptions';
|
|
203
234
|
/**
|
|
204
235
|
* Container component for line items.
|
|
205
236
|
* Follows the 3-level List/Options/Repeater pattern.
|
|
@@ -10,6 +10,8 @@ export interface BuildCheckoutParams {
|
|
|
10
10
|
bookingId: string;
|
|
11
11
|
contactDetails?: ContactDetails;
|
|
12
12
|
businessLocationId?: string;
|
|
13
|
+
/** Optional app ID override for the catalog reference. Defaults to BOOKING_APP_ID. */
|
|
14
|
+
appId?: string;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Builds a checkout request from the booking ID and contact details
|
|
@@ -19,7 +19,7 @@ const constants_js_1 = require("../../constants.js");
|
|
|
19
19
|
* @returns CreateCheckoutRequest ready to be sent to the API
|
|
20
20
|
*/
|
|
21
21
|
function buildCheckoutRequest(params) {
|
|
22
|
-
const { bookingId, contactDetails, businessLocationId } = params;
|
|
22
|
+
const { bookingId, contactDetails, businessLocationId, appId } = params;
|
|
23
23
|
// Build checkoutInfo incrementally for readability
|
|
24
24
|
const checkoutInfo = {};
|
|
25
25
|
// businessLocationId for multi-location support
|
|
@@ -56,7 +56,7 @@ function buildCheckoutRequest(params) {
|
|
|
56
56
|
quantity: 1,
|
|
57
57
|
catalogReference: {
|
|
58
58
|
catalogItemId: bookingId,
|
|
59
|
-
appId: constants_js_1.BOOKING_APP_ID,
|
|
59
|
+
appId: appId ?? constants_js_1.BOOKING_APP_ID,
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
],
|
|
@@ -36,6 +36,8 @@ export interface BookActionParams {
|
|
|
36
36
|
location: TimeSlotLocationType | null;
|
|
37
37
|
formSubmission: FormValues | null;
|
|
38
38
|
timezone?: string;
|
|
39
|
+
/** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
|
|
40
|
+
appId?: string;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
43
|
* Props passed to children of Book component
|
|
@@ -110,6 +110,8 @@ export interface BookingServiceConfig {
|
|
|
110
110
|
timezone?: string;
|
|
111
111
|
/** Optional initial form submission */
|
|
112
112
|
formSubmission?: FormValues | null;
|
|
113
|
+
/** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
|
|
114
|
+
appId?: string;
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* Implementation of BookingService
|
|
@@ -55,9 +55,13 @@ export type BookingFormServiceConfig = {
|
|
|
55
55
|
additionalMetadata?: Record<string, string | string[]>;
|
|
56
56
|
/** Optional list of service IDs to pass to FormService */
|
|
57
57
|
serviceIds?: string[];
|
|
58
|
+
/** Optional namespace override. Defaults to BOOKING_FORM_NAMESPACE. */
|
|
59
|
+
namespace?: string;
|
|
58
60
|
} | {
|
|
59
61
|
/** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
|
|
60
62
|
form: forms.Form;
|
|
63
|
+
/** Optional namespace override. Defaults to BOOKING_FORM_NAMESPACE. */
|
|
64
|
+
namespace?: string;
|
|
61
65
|
};
|
|
62
66
|
/**
|
|
63
67
|
* API interface for the BookingForm service.
|
|
@@ -61,15 +61,18 @@ exports.BookingFormServiceDefinition = (0, services_definitions_1.defineService)
|
|
|
61
61
|
// Validation Helpers
|
|
62
62
|
// ============================================================================
|
|
63
63
|
/**
|
|
64
|
-
* Validates that a form belongs to the
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Validates that a form belongs to the expected namespace.
|
|
65
|
+
* When a custom namespace is provided, validates against it; otherwise validates
|
|
66
|
+
* against BOOKING_FORM_NAMESPACE.
|
|
67
|
+
* @param form The form to validate
|
|
68
|
+
* @param expectedNamespace The expected namespace (defaults to BOOKING_FORM_NAMESPACE)
|
|
69
|
+
* @throws Error if the form namespace does not match the expected namespace
|
|
67
70
|
*/
|
|
68
|
-
function validateBookingFormNamespace(form) {
|
|
71
|
+
function validateBookingFormNamespace(form, expectedNamespace = exports.BOOKING_FORM_NAMESPACE) {
|
|
69
72
|
const formNamespace = form.namespace;
|
|
70
|
-
if (formNamespace && formNamespace !==
|
|
73
|
+
if (formNamespace && formNamespace !== expectedNamespace) {
|
|
71
74
|
throw new Error(`BookingFormService: Invalid form namespace. ` +
|
|
72
|
-
`Expected "${
|
|
75
|
+
`Expected "${expectedNamespace}", but received "${formNamespace}". ` +
|
|
73
76
|
`Ensure the form is a booking form.`);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -78,7 +81,7 @@ function validateBookingFormConfig({ config, services, }) {
|
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
80
83
|
if ((0, utils_js_1.hasForm)(config)) {
|
|
81
|
-
validateBookingFormNamespace(config.form);
|
|
84
|
+
validateBookingFormNamespace(config.form, config.namespace);
|
|
82
85
|
const servicesFormId = services.length > 1 ? DEFAULT_FORM_ID : services?.[0]?.form?._id;
|
|
83
86
|
if (servicesFormId !== config.form._id) {
|
|
84
87
|
throw new Error(`BookingFormService: Invalid form ID. ` +
|
|
@@ -236,7 +239,7 @@ exports.BookingFormService = services_definitions_1.implementService.withConfig(
|
|
|
236
239
|
? { form: config.form }
|
|
237
240
|
: {
|
|
238
241
|
formId: formIdValue,
|
|
239
|
-
namespace: exports.BOOKING_FORM_NAMESPACE,
|
|
242
|
+
namespace: config?.namespace ?? exports.BOOKING_FORM_NAMESPACE,
|
|
240
243
|
additionalMetadata: {
|
|
241
244
|
...config?.additionalMetadata,
|
|
242
245
|
serviceIds,
|
|
@@ -9,6 +9,6 @@ export { ServiceListService, ServiceListServiceDefinition, type ServiceListServi
|
|
|
9
9
|
export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from '../api/query-services/index.js';
|
|
10
10
|
export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
|
|
11
11
|
export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, } from './booking-form/booking-form.js';
|
|
12
|
-
export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
|
|
12
|
+
export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, type ServicePaymentOptions, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
|
|
13
13
|
export { LocationListService, LocationListServiceDefinition, LocationType, loadLocationListServiceInitialData, locationListServiceBinding, type LocationListServiceAPI, type LocationListServiceConfig, type LocationListActions, type Location, type LoadLocationListServiceResult, getLocationById, } from './location-list/location-list.js';
|
|
14
14
|
export { StaffMemberListService, StaffMemberListServiceDefinition, loadStaffMemberListServiceInitialData, staffMemberListServiceBinding, type StaffMemberListServiceAPI, type StaffMemberListServiceConfig, type StaffMemberListActions, type StaffMemberData, type LoadStaffMemberListServiceResult, } from './staff-member-list/staff-member-list.js';
|
|
@@ -4,7 +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
|
-
export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, } from './payment.def.js';
|
|
7
|
+
export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, type ServicePaymentOptions, } from './payment.def.js';
|
|
8
8
|
export type { PaymentServiceAPI, PaymentServiceInternalConfig, } from './payment.def.js';
|
|
9
9
|
export declare const PaymentService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
10
10
|
__api: import("./payment.def.js").PaymentServiceAPI;
|
|
@@ -5,6 +5,18 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Service payment options derived from service.payment.options.
|
|
10
|
+
* Describes which payment methods the service supports.
|
|
11
|
+
*/
|
|
12
|
+
export interface ServicePaymentOptions {
|
|
13
|
+
/** Customer can pay online at booking time */
|
|
14
|
+
isOnline: boolean;
|
|
15
|
+
/** Customer can pay in person (on arrival) */
|
|
16
|
+
isInPerson: boolean;
|
|
17
|
+
/** A deposit is required at booking time */
|
|
18
|
+
isDeposit: boolean;
|
|
19
|
+
}
|
|
8
20
|
/**
|
|
9
21
|
* Payment option for line items
|
|
10
22
|
*/
|
|
@@ -44,6 +56,8 @@ export interface PricingServiceSelection {
|
|
|
44
56
|
export interface PaymentServiceConfig {
|
|
45
57
|
/** Pre-calculated payment details for SSR (no API call) */
|
|
46
58
|
paymentDetails?: PaymentDetails;
|
|
59
|
+
/** Service payment options for SSR (derived from service.payment.options) */
|
|
60
|
+
serviceOptions?: ServicePaymentOptions;
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
63
|
* Internal config used by Core component to pass lazy loading data
|
|
@@ -59,6 +73,7 @@ export interface PaymentServiceAPI {
|
|
|
59
73
|
paymentDetailsSignal: Signal<PaymentDetails | null>;
|
|
60
74
|
isLoadingSignal: Signal<boolean>;
|
|
61
75
|
errorSignal: Signal<string | null>;
|
|
76
|
+
serviceOptionsSignal: Signal<ServicePaymentOptions>;
|
|
62
77
|
}
|
|
63
78
|
export declare const PaymentServiceDefinition: string & {
|
|
64
79
|
__api: PaymentServiceAPI;
|
|
@@ -20,6 +20,35 @@ const constants_js_1 = require("../constants.js");
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
// Helper Functions
|
|
22
22
|
// ============================================================================
|
|
23
|
+
const DEFAULT_SERVICE_OPTIONS = {
|
|
24
|
+
isOnline: false,
|
|
25
|
+
isInPerson: false,
|
|
26
|
+
isDeposit: false,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Derives static service payment options from the first service's configuration.
|
|
30
|
+
*
|
|
31
|
+
* Note on `isDeposit`: this reflects whether the service is *configured* with a
|
|
32
|
+
* deposit (i.e. `payment.fixed.deposit.value > 0`), consistent with how
|
|
33
|
+
* `buildLineItem` determines `hasDeposit`. Only `FIXED` rate type is checked;
|
|
34
|
+
* `VARIED` pricing is not currently supported for deposit detection here.
|
|
35
|
+
*
|
|
36
|
+
* This is distinct from the runtime `isDeposit` flag in `CorePayment.Data`, which
|
|
37
|
+
* is computed dynamically from the ecom API result (`payNow.amount < total.amount`).
|
|
38
|
+
* Use `Payment.ServiceOptions` to determine *which scenario to render* (static config),
|
|
39
|
+
* and `Payment.PayNow`/`Payment.PayLater` for the *actual split amounts* (runtime).
|
|
40
|
+
*/
|
|
41
|
+
function deriveServiceOptions(pricingServiceSelections) {
|
|
42
|
+
const service = pricingServiceSelections[0]?.service;
|
|
43
|
+
const options = service?.payment?.options;
|
|
44
|
+
const depositValue = service?.payment?.fixed?.deposit?.value;
|
|
45
|
+
const hasDeposit = !!depositValue && Number(depositValue) > 0;
|
|
46
|
+
return {
|
|
47
|
+
isOnline: !!options?.online,
|
|
48
|
+
isInPerson: !!options?.inPerson,
|
|
49
|
+
isDeposit: hasDeposit,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
23
52
|
function getAdditionalInfo(service, lineItemId) {
|
|
24
53
|
const payment = service.payment;
|
|
25
54
|
const customPrice = payment?.rateType === auto_sdk_bookings_services_1.RateType.CUSTOM && payment.custom?.description
|
|
@@ -97,6 +126,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
97
126
|
const paymentDetailsSignal = signalsService.signal(config.paymentDetails || null);
|
|
98
127
|
const isLoadingSignal = signalsService.signal(false);
|
|
99
128
|
const errorSignal = signalsService.signal(null);
|
|
129
|
+
const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
|
|
100
130
|
// Internal calculate function
|
|
101
131
|
const calculate = async (pricingServiceSelections, totalParticipants) => {
|
|
102
132
|
if (!pricingServiceSelections.length) {
|
|
@@ -122,10 +152,18 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
122
152
|
};
|
|
123
153
|
// Mode 1: SSR - paymentDetails already set in signal initialization
|
|
124
154
|
if (config.paymentDetails) {
|
|
125
|
-
|
|
155
|
+
if (!config.serviceOptions &&
|
|
156
|
+
process.env['NODE_ENV'] !== 'production') {
|
|
157
|
+
console.warn('[PaymentService] SSR mode: `serviceOptions` was not provided in config. ' +
|
|
158
|
+
'Payment.ServiceOptions will return all-false defaults. ' +
|
|
159
|
+
'Use `loadPaymentConfig` to automatically derive serviceOptions, ' +
|
|
160
|
+
'or pass `serviceOptions` explicitly in `paymentServiceConfig`.');
|
|
161
|
+
}
|
|
162
|
+
// serviceOptionsSignal already initialized from config.serviceOptions (or DEFAULT_SERVICE_OPTIONS)
|
|
126
163
|
}
|
|
127
164
|
// Mode 2: Lazy loading - pricingServiceSelections provided via internal config
|
|
128
165
|
else if (config.pricingServiceSelections?.length) {
|
|
166
|
+
serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
|
|
129
167
|
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1);
|
|
130
168
|
}
|
|
131
169
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
@@ -147,6 +185,9 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
147
185
|
service: selection.service,
|
|
148
186
|
lineItemId: selection.instanceId,
|
|
149
187
|
}));
|
|
188
|
+
if (pricingServiceSelections.length) {
|
|
189
|
+
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
|
190
|
+
}
|
|
150
191
|
void calculate(pricingServiceSelections, numParticipants);
|
|
151
192
|
});
|
|
152
193
|
}
|
|
@@ -154,6 +195,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
154
195
|
paymentDetailsSignal,
|
|
155
196
|
isLoadingSignal,
|
|
156
197
|
errorSignal,
|
|
198
|
+
serviceOptionsSignal,
|
|
157
199
|
};
|
|
158
200
|
});
|
|
159
201
|
/**
|
|
@@ -169,13 +211,19 @@ async function loadPaymentConfig(params) {
|
|
|
169
211
|
if (!pricingServiceSelections.length) {
|
|
170
212
|
return {
|
|
171
213
|
type: 'success',
|
|
172
|
-
config: {
|
|
214
|
+
config: {
|
|
215
|
+
paymentDetails: undefined,
|
|
216
|
+
serviceOptions: DEFAULT_SERVICE_OPTIONS,
|
|
217
|
+
},
|
|
173
218
|
};
|
|
174
219
|
}
|
|
175
220
|
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
|
|
176
221
|
return {
|
|
177
222
|
type: 'success',
|
|
178
|
-
config: {
|
|
223
|
+
config: {
|
|
224
|
+
paymentDetails,
|
|
225
|
+
serviceOptions: deriveServiceOptions(pricingServiceSelections),
|
|
226
|
+
},
|
|
179
227
|
};
|
|
180
228
|
}
|
|
181
229
|
catch (error) {
|
|
@@ -21,6 +21,8 @@ export interface RootProps {
|
|
|
21
21
|
serviceIds?: string[];
|
|
22
22
|
/** Optional additional metadata to pass to FormService */
|
|
23
23
|
additionalMetadata?: Record<string, string | string[]>;
|
|
24
|
+
/** Optional namespace override for the form. Defaults to the bookings namespace. */
|
|
25
|
+
namespace?: string;
|
|
24
26
|
/** Field map to override default booking field components */
|
|
25
27
|
fieldMap: FieldMap;
|
|
26
28
|
/**
|
|
@@ -112,8 +112,8 @@ export const TestIds = {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
export const Root = React.forwardRef((props, ref) => {
|
|
115
|
-
const { children, asChild, className, formId, serviceIds, additionalMetadata, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', loadErrorFallback, ...otherProps } = props;
|
|
116
|
-
return (_jsx(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: _jsx(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: (renderProps) => {
|
|
115
|
+
const { children, asChild, className, formId, serviceIds, additionalMetadata, namespace, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', loadErrorFallback, ...otherProps } = props;
|
|
116
|
+
return (_jsx(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: _jsx(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, namespace: namespace, fieldMap: fieldMap, children: (renderProps) => {
|
|
117
117
|
// If children is a render prop function, call it with the render props
|
|
118
118
|
if (typeof children === 'function') {
|
|
119
119
|
return children(renderProps);
|
|
@@ -44,6 +44,8 @@ interface BookingFormBaseProps {
|
|
|
44
44
|
serviceIds?: string[];
|
|
45
45
|
/** Optional additional metadata to pass to FormService */
|
|
46
46
|
additionalMetadata?: Record<string, string | string[]>;
|
|
47
|
+
/** Optional namespace override for the form. Defaults to the bookings namespace. */
|
|
48
|
+
namespace?: string;
|
|
47
49
|
/** Field map to override default booking field components */
|
|
48
50
|
fieldMap: FieldMap;
|
|
49
51
|
/** Render prop function that receives form data and actions */
|
|
@@ -90,13 +90,13 @@ import { SignalsServiceDefinition } from '@wix/services-definitions/core-service
|
|
|
90
90
|
* ```
|
|
91
91
|
*/
|
|
92
92
|
export function Root(props) {
|
|
93
|
-
const { serviceIds, additionalMetadata, fieldMap, children } = props;
|
|
93
|
+
const { serviceIds, additionalMetadata, namespace, fieldMap, children } = props;
|
|
94
94
|
// Create formRef to pass to Form.Root and BookingFormContent
|
|
95
95
|
const formRef = useRef(null);
|
|
96
96
|
const bookingFormServiceConfig = props.form
|
|
97
|
-
? { form: props.form }
|
|
97
|
+
? { form: props.form, ...(namespace !== undefined ? { namespace } : {}) }
|
|
98
98
|
: props.formId
|
|
99
|
-
? { formId: props.formId, additionalMetadata, serviceIds }
|
|
99
|
+
? { formId: props.formId, additionalMetadata, serviceIds, namespace }
|
|
100
100
|
: undefined;
|
|
101
101
|
// Use Form.Root to set up FormService, then nest WixServices for BookingFormService
|
|
102
102
|
// This allows BookingFormService to access FormService via getService internally
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Note: Currently supports single service only
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { type PaymentServiceConfig, type PricingServiceSelection } from '../../../services/payment/payment.js';
|
|
7
|
+
import { type PaymentServiceConfig, type PricingServiceSelection, type ServicePaymentOptions } from '../../../services/payment/payment.js';
|
|
8
8
|
import type { MoneyData } from '@wix/headless-components/react';
|
|
9
9
|
/**
|
|
10
10
|
* Props for Payment.Root component
|
|
@@ -164,6 +164,19 @@ export interface LineItemProviderProps {
|
|
|
164
164
|
* ```
|
|
165
165
|
*/
|
|
166
166
|
export declare function LineItemProvider(props: LineItemProviderProps): React.ReactNode;
|
|
167
|
+
/**
|
|
168
|
+
* Props for ServiceOptions render prop component
|
|
169
|
+
*/
|
|
170
|
+
export interface ServiceOptionsProps {
|
|
171
|
+
children: (options: ServicePaymentOptions) => React.ReactNode;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Core component that exposes service payment options via render props.
|
|
175
|
+
* Reads isOnline, isInPerson, isDeposit from the payment service signal.
|
|
176
|
+
*
|
|
177
|
+
* @component
|
|
178
|
+
*/
|
|
179
|
+
export declare function ServiceOptions(props: ServiceOptionsProps): React.ReactNode;
|
|
167
180
|
/**
|
|
168
181
|
* Props for LineItemInfo render prop component
|
|
169
182
|
*/
|
|
@@ -188,6 +188,17 @@ export function LineItemProvider(props) {
|
|
|
188
188
|
};
|
|
189
189
|
return (_jsx(LineItemContext.Provider, { value: contextValue, children: children }));
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Core component that exposes service payment options via render props.
|
|
193
|
+
* Reads isOnline, isInPerson, isDeposit from the payment service signal.
|
|
194
|
+
*
|
|
195
|
+
* @component
|
|
196
|
+
*/
|
|
197
|
+
export function ServiceOptions(props) {
|
|
198
|
+
const paymentService = useService(PaymentServiceDefinition);
|
|
199
|
+
const serviceOptions = paymentService.serviceOptionsSignal.get();
|
|
200
|
+
return props.children(serviceOptions);
|
|
201
|
+
}
|
|
191
202
|
/**
|
|
192
203
|
* Core component that provides access to line item info via render props.
|
|
193
204
|
* Must be used within LineItemProvider.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { AsChildChildren } from '@wix/headless-utils/react';
|
|
7
7
|
import { MoneyData } from '@wix/headless-components/react';
|
|
8
|
-
import type { PaymentServiceConfig, PricingServiceSelection } from '../../services/payment/payment.js';
|
|
8
|
+
import type { PaymentServiceConfig, PricingServiceSelection, ServicePaymentOptions } from '../../services/payment/payment.js';
|
|
9
9
|
export declare const TestIds: {
|
|
10
10
|
readonly paymentRoot: "payment-root";
|
|
11
11
|
readonly paymentSubtotal: "payment-subtotal";
|
|
@@ -19,6 +19,7 @@ export declare const TestIds: {
|
|
|
19
19
|
readonly paymentLineItemTotal: "payment-line-item-total";
|
|
20
20
|
readonly paymentLineItemSubtotal: "payment-line-item-subtotal";
|
|
21
21
|
readonly paymentLineItemDeposit: "payment-line-item-deposit";
|
|
22
|
+
readonly paymentServiceOptions: "payment-service-options";
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
24
25
|
* Props for Payment.Root component
|
|
@@ -194,6 +195,41 @@ export interface PayLaterProps extends Omit<React.HTMLAttributes<HTMLElement>, '
|
|
|
194
195
|
* ```
|
|
195
196
|
*/
|
|
196
197
|
export declare const PayLater: React.ForwardRefExoticComponent<PayLaterProps & React.RefAttributes<HTMLElement>>;
|
|
198
|
+
/**
|
|
199
|
+
* Props for Payment.ServiceOptions component
|
|
200
|
+
*/
|
|
201
|
+
export interface ServiceOptionsProps {
|
|
202
|
+
asChild?: boolean;
|
|
203
|
+
/** Render prop receiving service payment options. This component renders no
|
|
204
|
+
* visible DOM element by default — it is a flags-only data component. */
|
|
205
|
+
children?: AsChildChildren<ServicePaymentOptions>;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Exposes service payment options via render props.
|
|
209
|
+
* Use this to determine which payment scenario to render without reading the service entity directly.
|
|
210
|
+
*
|
|
211
|
+
* Payment scenarios:
|
|
212
|
+
* - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
|
|
213
|
+
* - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
|
|
214
|
+
* - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
|
|
215
|
+
* - `isOnline=false, isInPerson=true` → Pay on arrival only
|
|
216
|
+
*
|
|
217
|
+
* @component
|
|
218
|
+
* Note: This component renders no visible DOM element. It is a flags-only data
|
|
219
|
+
* component — use `asChild` with a render function to consume the flags.
|
|
220
|
+
* For the actual split amounts (deposit / pay-later), use `Payment.PayNow` and
|
|
221
|
+
* `Payment.PayLater`.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```tsx
|
|
225
|
+
* <Payment.ServiceOptions asChild>
|
|
226
|
+
* {({ isOnline, isInPerson, isDeposit }) => (
|
|
227
|
+
* // render the correct payment scenario
|
|
228
|
+
* )}
|
|
229
|
+
* </Payment.ServiceOptions>
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export declare const ServiceOptions: React.ForwardRefExoticComponent<ServiceOptionsProps & React.RefAttributes<HTMLElement>>;
|
|
197
233
|
/**
|
|
198
234
|
* Props for Payment.LineItems component (Container Level)
|
|
199
235
|
*/
|
|
@@ -20,6 +20,7 @@ export const TestIds = {
|
|
|
20
20
|
paymentLineItemTotal: 'payment-line-item-total',
|
|
21
21
|
paymentLineItemSubtotal: 'payment-line-item-subtotal',
|
|
22
22
|
paymentLineItemDeposit: 'payment-line-item-deposit',
|
|
23
|
+
paymentServiceOptions: 'payment-service-options',
|
|
23
24
|
};
|
|
24
25
|
/**
|
|
25
26
|
* Root component that provides payment context to the entire app.
|
|
@@ -161,6 +162,36 @@ export const PayLater = React.forwardRef((props, ref) => {
|
|
|
161
162
|
return (_jsx(CorePayment.Data, { children: ({ payLater, isDeposit }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, customElementProps: { payLater, isDeposit }, "data-testid": TestIds.paymentPayLater, "data-has-deposit": isDeposit, children: isDeposit && payLater && payLater.amount > 0 && (_jsx(Money, { money: payLater })) })) }));
|
|
162
163
|
});
|
|
163
164
|
PayLater.displayName = 'Payment.PayLater';
|
|
165
|
+
/**
|
|
166
|
+
* Exposes service payment options via render props.
|
|
167
|
+
* Use this to determine which payment scenario to render without reading the service entity directly.
|
|
168
|
+
*
|
|
169
|
+
* Payment scenarios:
|
|
170
|
+
* - `isOnline=true, isInPerson=false, isDeposit=false` → Pay now only (Due now = total)
|
|
171
|
+
* - `isOnline=true, isInPerson=false, isDeposit=true` → Pay now + security deposit
|
|
172
|
+
* - `isOnline=true, isInPerson=true` → Radio: Pay Online / Pay on Arrival
|
|
173
|
+
* - `isOnline=false, isInPerson=true` → Pay on arrival only
|
|
174
|
+
*
|
|
175
|
+
* @component
|
|
176
|
+
* Note: This component renders no visible DOM element. It is a flags-only data
|
|
177
|
+
* component — use `asChild` with a render function to consume the flags.
|
|
178
|
+
* For the actual split amounts (deposit / pay-later), use `Payment.PayNow` and
|
|
179
|
+
* `Payment.PayLater`.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```tsx
|
|
183
|
+
* <Payment.ServiceOptions asChild>
|
|
184
|
+
* {({ isOnline, isInPerson, isDeposit }) => (
|
|
185
|
+
* // render the correct payment scenario
|
|
186
|
+
* )}
|
|
187
|
+
* </Payment.ServiceOptions>
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export const ServiceOptions = React.forwardRef((props, ref) => {
|
|
191
|
+
const { asChild, children } = props;
|
|
192
|
+
return (_jsx(CorePayment.ServiceOptions, { children: ({ isOnline, isInPerson, isDeposit }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, customElement: children, customElementProps: { isOnline, isInPerson, isDeposit }, "data-testid": TestIds.paymentServiceOptions, "data-is-online": isOnline, "data-is-in-person": isInPerson, "data-is-deposit": isDeposit, children: null })) }));
|
|
193
|
+
});
|
|
194
|
+
ServiceOptions.displayName = 'Payment.ServiceOptions';
|
|
164
195
|
/**
|
|
165
196
|
* Container component for line items.
|
|
166
197
|
* Follows the 3-level List/Options/Repeater pattern.
|
|
@@ -10,6 +10,8 @@ export interface BuildCheckoutParams {
|
|
|
10
10
|
bookingId: string;
|
|
11
11
|
contactDetails?: ContactDetails;
|
|
12
12
|
businessLocationId?: string;
|
|
13
|
+
/** Optional app ID override for the catalog reference. Defaults to BOOKING_APP_ID. */
|
|
14
|
+
appId?: string;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Builds a checkout request from the booking ID and contact details
|
|
@@ -16,7 +16,7 @@ import { BOOKING_APP_ID } from '../../constants.js';
|
|
|
16
16
|
* @returns CreateCheckoutRequest ready to be sent to the API
|
|
17
17
|
*/
|
|
18
18
|
export function buildCheckoutRequest(params) {
|
|
19
|
-
const { bookingId, contactDetails, businessLocationId } = params;
|
|
19
|
+
const { bookingId, contactDetails, businessLocationId, appId } = params;
|
|
20
20
|
// Build checkoutInfo incrementally for readability
|
|
21
21
|
const checkoutInfo = {};
|
|
22
22
|
// businessLocationId for multi-location support
|
|
@@ -53,7 +53,7 @@ export function buildCheckoutRequest(params) {
|
|
|
53
53
|
quantity: 1,
|
|
54
54
|
catalogReference: {
|
|
55
55
|
catalogItemId: bookingId,
|
|
56
|
-
appId: BOOKING_APP_ID,
|
|
56
|
+
appId: appId ?? BOOKING_APP_ID,
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
],
|
|
@@ -36,6 +36,8 @@ export interface BookActionParams {
|
|
|
36
36
|
location: TimeSlotLocationType | null;
|
|
37
37
|
formSubmission: FormValues | null;
|
|
38
38
|
timezone?: string;
|
|
39
|
+
/** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
|
|
40
|
+
appId?: string;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
43
|
* Props passed to children of Book component
|
|
@@ -110,6 +110,8 @@ export interface BookingServiceConfig {
|
|
|
110
110
|
timezone?: string;
|
|
111
111
|
/** Optional initial form submission */
|
|
112
112
|
formSubmission?: FormValues | null;
|
|
113
|
+
/** Optional app ID override for the catalog reference. Defaults to the bookings app ID. */
|
|
114
|
+
appId?: string;
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* Implementation of BookingService
|
|
@@ -55,9 +55,13 @@ export type BookingFormServiceConfig = {
|
|
|
55
55
|
additionalMetadata?: Record<string, string | string[]>;
|
|
56
56
|
/** Optional list of service IDs to pass to FormService */
|
|
57
57
|
serviceIds?: string[];
|
|
58
|
+
/** Optional namespace override. Defaults to BOOKING_FORM_NAMESPACE. */
|
|
59
|
+
namespace?: string;
|
|
58
60
|
} | {
|
|
59
61
|
/** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
|
|
60
62
|
form: forms.Form;
|
|
63
|
+
/** Optional namespace override. Defaults to BOOKING_FORM_NAMESPACE. */
|
|
64
|
+
namespace?: string;
|
|
61
65
|
};
|
|
62
66
|
/**
|
|
63
67
|
* API interface for the BookingForm service.
|
|
@@ -53,15 +53,18 @@ export const BookingFormServiceDefinition = defineService('bookingForm');
|
|
|
53
53
|
// Validation Helpers
|
|
54
54
|
// ============================================================================
|
|
55
55
|
/**
|
|
56
|
-
* Validates that a form belongs to the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* Validates that a form belongs to the expected namespace.
|
|
57
|
+
* When a custom namespace is provided, validates against it; otherwise validates
|
|
58
|
+
* against BOOKING_FORM_NAMESPACE.
|
|
59
|
+
* @param form The form to validate
|
|
60
|
+
* @param expectedNamespace The expected namespace (defaults to BOOKING_FORM_NAMESPACE)
|
|
61
|
+
* @throws Error if the form namespace does not match the expected namespace
|
|
59
62
|
*/
|
|
60
|
-
function validateBookingFormNamespace(form) {
|
|
63
|
+
function validateBookingFormNamespace(form, expectedNamespace = BOOKING_FORM_NAMESPACE) {
|
|
61
64
|
const formNamespace = form.namespace;
|
|
62
|
-
if (formNamespace && formNamespace !==
|
|
65
|
+
if (formNamespace && formNamespace !== expectedNamespace) {
|
|
63
66
|
throw new Error(`BookingFormService: Invalid form namespace. ` +
|
|
64
|
-
`Expected "${
|
|
67
|
+
`Expected "${expectedNamespace}", but received "${formNamespace}". ` +
|
|
65
68
|
`Ensure the form is a booking form.`);
|
|
66
69
|
}
|
|
67
70
|
}
|
|
@@ -70,7 +73,7 @@ function validateBookingFormConfig({ config, services, }) {
|
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
72
75
|
if (hasForm(config)) {
|
|
73
|
-
validateBookingFormNamespace(config.form);
|
|
76
|
+
validateBookingFormNamespace(config.form, config.namespace);
|
|
74
77
|
const servicesFormId = services.length > 1 ? DEFAULT_FORM_ID : services?.[0]?.form?._id;
|
|
75
78
|
if (servicesFormId !== config.form._id) {
|
|
76
79
|
throw new Error(`BookingFormService: Invalid form ID. ` +
|
|
@@ -228,7 +231,7 @@ export const BookingFormService = implementService.withConfig()(BookingFormServi
|
|
|
228
231
|
? { form: config.form }
|
|
229
232
|
: {
|
|
230
233
|
formId: formIdValue,
|
|
231
|
-
namespace: BOOKING_FORM_NAMESPACE,
|
|
234
|
+
namespace: config?.namespace ?? BOOKING_FORM_NAMESPACE,
|
|
232
235
|
additionalMetadata: {
|
|
233
236
|
...config?.additionalMetadata,
|
|
234
237
|
serviceIds,
|
package/dist/services/index.d.ts
CHANGED
|
@@ -9,6 +9,6 @@ export { ServiceListService, ServiceListServiceDefinition, type ServiceListServi
|
|
|
9
9
|
export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from '../api/query-services/index.js';
|
|
10
10
|
export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
|
|
11
11
|
export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, } from './booking-form/booking-form.js';
|
|
12
|
-
export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
|
|
12
|
+
export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, type ServicePaymentOptions, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
|
|
13
13
|
export { LocationListService, LocationListServiceDefinition, LocationType, loadLocationListServiceInitialData, locationListServiceBinding, type LocationListServiceAPI, type LocationListServiceConfig, type LocationListActions, type Location, type LoadLocationListServiceResult, getLocationById, } from './location-list/location-list.js';
|
|
14
14
|
export { StaffMemberListService, StaffMemberListServiceDefinition, loadStaffMemberListServiceInitialData, staffMemberListServiceBinding, type StaffMemberListServiceAPI, type StaffMemberListServiceConfig, type StaffMemberListActions, type StaffMemberData, type LoadStaffMemberListServiceResult, } from './staff-member-list/staff-member-list.js';
|
|
@@ -4,7 +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
|
-
export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, } from './payment.def.js';
|
|
7
|
+
export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, type ServicePaymentOptions, } from './payment.def.js';
|
|
8
8
|
export type { PaymentServiceAPI, PaymentServiceInternalConfig, } from './payment.def.js';
|
|
9
9
|
export declare const PaymentService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
10
10
|
__api: import("./payment.def.js").PaymentServiceAPI;
|
|
@@ -5,6 +5,18 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Service payment options derived from service.payment.options.
|
|
10
|
+
* Describes which payment methods the service supports.
|
|
11
|
+
*/
|
|
12
|
+
export interface ServicePaymentOptions {
|
|
13
|
+
/** Customer can pay online at booking time */
|
|
14
|
+
isOnline: boolean;
|
|
15
|
+
/** Customer can pay in person (on arrival) */
|
|
16
|
+
isInPerson: boolean;
|
|
17
|
+
/** A deposit is required at booking time */
|
|
18
|
+
isDeposit: boolean;
|
|
19
|
+
}
|
|
8
20
|
/**
|
|
9
21
|
* Payment option for line items
|
|
10
22
|
*/
|
|
@@ -44,6 +56,8 @@ export interface PricingServiceSelection {
|
|
|
44
56
|
export interface PaymentServiceConfig {
|
|
45
57
|
/** Pre-calculated payment details for SSR (no API call) */
|
|
46
58
|
paymentDetails?: PaymentDetails;
|
|
59
|
+
/** Service payment options for SSR (derived from service.payment.options) */
|
|
60
|
+
serviceOptions?: ServicePaymentOptions;
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
63
|
* Internal config used by Core component to pass lazy loading data
|
|
@@ -59,6 +73,7 @@ export interface PaymentServiceAPI {
|
|
|
59
73
|
paymentDetailsSignal: Signal<PaymentDetails | null>;
|
|
60
74
|
isLoadingSignal: Signal<boolean>;
|
|
61
75
|
errorSignal: Signal<string | null>;
|
|
76
|
+
serviceOptionsSignal: Signal<ServicePaymentOptions>;
|
|
62
77
|
}
|
|
63
78
|
export declare const PaymentServiceDefinition: string & {
|
|
64
79
|
__api: PaymentServiceAPI;
|
|
@@ -15,6 +15,35 @@ import { BOOKING_APP_ID } from '../constants.js';
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// Helper Functions
|
|
17
17
|
// ============================================================================
|
|
18
|
+
const DEFAULT_SERVICE_OPTIONS = {
|
|
19
|
+
isOnline: false,
|
|
20
|
+
isInPerson: false,
|
|
21
|
+
isDeposit: false,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Derives static service payment options from the first service's configuration.
|
|
25
|
+
*
|
|
26
|
+
* Note on `isDeposit`: this reflects whether the service is *configured* with a
|
|
27
|
+
* deposit (i.e. `payment.fixed.deposit.value > 0`), consistent with how
|
|
28
|
+
* `buildLineItem` determines `hasDeposit`. Only `FIXED` rate type is checked;
|
|
29
|
+
* `VARIED` pricing is not currently supported for deposit detection here.
|
|
30
|
+
*
|
|
31
|
+
* This is distinct from the runtime `isDeposit` flag in `CorePayment.Data`, which
|
|
32
|
+
* is computed dynamically from the ecom API result (`payNow.amount < total.amount`).
|
|
33
|
+
* Use `Payment.ServiceOptions` to determine *which scenario to render* (static config),
|
|
34
|
+
* and `Payment.PayNow`/`Payment.PayLater` for the *actual split amounts* (runtime).
|
|
35
|
+
*/
|
|
36
|
+
function deriveServiceOptions(pricingServiceSelections) {
|
|
37
|
+
const service = pricingServiceSelections[0]?.service;
|
|
38
|
+
const options = service?.payment?.options;
|
|
39
|
+
const depositValue = service?.payment?.fixed?.deposit?.value;
|
|
40
|
+
const hasDeposit = !!depositValue && Number(depositValue) > 0;
|
|
41
|
+
return {
|
|
42
|
+
isOnline: !!options?.online,
|
|
43
|
+
isInPerson: !!options?.inPerson,
|
|
44
|
+
isDeposit: hasDeposit,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
18
47
|
function getAdditionalInfo(service, lineItemId) {
|
|
19
48
|
const payment = service.payment;
|
|
20
49
|
const customPrice = payment?.rateType === RateType.CUSTOM && payment.custom?.description
|
|
@@ -92,6 +121,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
92
121
|
const paymentDetailsSignal = signalsService.signal(config.paymentDetails || null);
|
|
93
122
|
const isLoadingSignal = signalsService.signal(false);
|
|
94
123
|
const errorSignal = signalsService.signal(null);
|
|
124
|
+
const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
|
|
95
125
|
// Internal calculate function
|
|
96
126
|
const calculate = async (pricingServiceSelections, totalParticipants) => {
|
|
97
127
|
if (!pricingServiceSelections.length) {
|
|
@@ -117,10 +147,18 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
117
147
|
};
|
|
118
148
|
// Mode 1: SSR - paymentDetails already set in signal initialization
|
|
119
149
|
if (config.paymentDetails) {
|
|
120
|
-
|
|
150
|
+
if (!config.serviceOptions &&
|
|
151
|
+
process.env['NODE_ENV'] !== 'production') {
|
|
152
|
+
console.warn('[PaymentService] SSR mode: `serviceOptions` was not provided in config. ' +
|
|
153
|
+
'Payment.ServiceOptions will return all-false defaults. ' +
|
|
154
|
+
'Use `loadPaymentConfig` to automatically derive serviceOptions, ' +
|
|
155
|
+
'or pass `serviceOptions` explicitly in `paymentServiceConfig`.');
|
|
156
|
+
}
|
|
157
|
+
// serviceOptionsSignal already initialized from config.serviceOptions (or DEFAULT_SERVICE_OPTIONS)
|
|
121
158
|
}
|
|
122
159
|
// Mode 2: Lazy loading - pricingServiceSelections provided via internal config
|
|
123
160
|
else if (config.pricingServiceSelections?.length) {
|
|
161
|
+
serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
|
|
124
162
|
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1);
|
|
125
163
|
}
|
|
126
164
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
@@ -142,6 +180,9 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
142
180
|
service: selection.service,
|
|
143
181
|
lineItemId: selection.instanceId,
|
|
144
182
|
}));
|
|
183
|
+
if (pricingServiceSelections.length) {
|
|
184
|
+
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
|
185
|
+
}
|
|
145
186
|
void calculate(pricingServiceSelections, numParticipants);
|
|
146
187
|
});
|
|
147
188
|
}
|
|
@@ -149,6 +190,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
149
190
|
paymentDetailsSignal,
|
|
150
191
|
isLoadingSignal,
|
|
151
192
|
errorSignal,
|
|
193
|
+
serviceOptionsSignal,
|
|
152
194
|
};
|
|
153
195
|
});
|
|
154
196
|
/**
|
|
@@ -164,13 +206,19 @@ export async function loadPaymentConfig(params) {
|
|
|
164
206
|
if (!pricingServiceSelections.length) {
|
|
165
207
|
return {
|
|
166
208
|
type: 'success',
|
|
167
|
-
config: {
|
|
209
|
+
config: {
|
|
210
|
+
paymentDetails: undefined,
|
|
211
|
+
serviceOptions: DEFAULT_SERVICE_OPTIONS,
|
|
212
|
+
},
|
|
168
213
|
};
|
|
169
214
|
}
|
|
170
215
|
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants);
|
|
171
216
|
return {
|
|
172
217
|
type: 'success',
|
|
173
|
-
config: {
|
|
218
|
+
config: {
|
|
219
|
+
paymentDetails,
|
|
220
|
+
serviceOptions: deriveServiceOptions(pricingServiceSelections),
|
|
221
|
+
},
|
|
174
222
|
};
|
|
175
223
|
}
|
|
176
224
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.118",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"groupId": "com.wixpress.headless-components"
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
-
"falconPackageHash": "
|
|
76
|
+
"falconPackageHash": "dac1d1f3424ebe62c7911b33c4224023bcfa07880c8ea404481cc7b0"
|
|
77
77
|
}
|