@wix/headless-bookings 0.0.47 → 0.0.49
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/api/create-order/index.d.ts +20 -0
- package/cjs/dist/api/create-order/index.js +21 -0
- package/cjs/dist/api/index.d.ts +1 -0
- package/cjs/dist/api/index.js +1 -0
- package/cjs/dist/react/booking/Book.d.ts +18 -5
- package/cjs/dist/react/booking/Book.js +13 -5
- package/cjs/dist/react/core/booking/Book.d.ts +11 -3
- package/cjs/dist/react/core/booking/Book.js +12 -3
- package/cjs/dist/services/booking/book-action/bookAction.d.ts +8 -5
- package/cjs/dist/services/booking/book-action/bookAction.js +28 -8
- package/cjs/dist/services/booking/book-action/buildBookingRequest.js +21 -1
- package/cjs/dist/services/booking/book-action/index.d.ts +2 -1
- package/cjs/dist/services/booking/book-action/index.js +2 -0
- package/cjs/dist/services/booking/book-action/isCheckoutRequired.d.ts +20 -0
- package/cjs/dist/services/booking/book-action/isCheckoutRequired.js +32 -0
- package/cjs/dist/services/booking/book-action/types.d.ts +17 -1
- package/cjs/dist/services/booking/book-action/types.js +8 -1
- package/dist/api/create-order/index.d.ts +20 -0
- package/dist/api/create-order/index.js +21 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/react/booking/Book.d.ts +18 -5
- package/dist/react/booking/Book.js +13 -5
- package/dist/react/core/booking/Book.d.ts +11 -3
- package/dist/react/core/booking/Book.js +12 -3
- package/dist/services/booking/book-action/bookAction.d.ts +8 -5
- package/dist/services/booking/book-action/bookAction.js +28 -8
- package/dist/services/booking/book-action/buildBookingRequest.js +21 -1
- package/dist/services/booking/book-action/index.d.ts +2 -1
- package/dist/services/booking/book-action/index.js +2 -0
- package/dist/services/booking/book-action/isCheckoutRequired.d.ts +20 -0
- package/dist/services/booking/book-action/isCheckoutRequired.js +32 -0
- package/dist/services/booking/book-action/types.d.ts +17 -1
- package/dist/services/booking/book-action/types.js +8 -1
- package/package.json +2 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Order API
|
|
3
|
+
* Wrapper for the Wix eCommerce createOrder SDK function
|
|
4
|
+
*/
|
|
5
|
+
import { type CreateOrderOptions, type CreateOrderResponse } from '@wix/auto_sdk_ecom_checkout';
|
|
6
|
+
/**
|
|
7
|
+
* Creates an order from a checkout session
|
|
8
|
+
*
|
|
9
|
+
* @param checkoutId - The checkout session ID
|
|
10
|
+
* @param options - Optional additional order creation options
|
|
11
|
+
* @returns Promise resolving to the created order response
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const response = await createOrder(checkoutId);
|
|
16
|
+
* const orderId = response.orderId;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function createOrder(checkoutId: string, options?: CreateOrderOptions): Promise<CreateOrderResponse>;
|
|
20
|
+
export type { CreateOrderOptions, CreateOrderResponse };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Order API
|
|
3
|
+
* Wrapper for the Wix eCommerce createOrder SDK function
|
|
4
|
+
*/
|
|
5
|
+
import { createOrder as createOrderSdk, } from '@wix/auto_sdk_ecom_checkout';
|
|
6
|
+
/**
|
|
7
|
+
* Creates an order from a checkout session
|
|
8
|
+
*
|
|
9
|
+
* @param checkoutId - The checkout session ID
|
|
10
|
+
* @param options - Optional additional order creation options
|
|
11
|
+
* @returns Promise resolving to the created order response
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const response = await createOrder(checkoutId);
|
|
16
|
+
* const orderId = response.orderId;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function createOrder(checkoutId, options) {
|
|
20
|
+
return createOrderSdk(checkoutId, options);
|
|
21
|
+
}
|
package/cjs/dist/api/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { getServiceById, getServiceBySlug, queryServices, type PagingMetadata, t
|
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export { createBooking, type CreateBookingOptions, type Booking, type CreateBookingRequest, type CreateBookingResponse, type ParticipantNotification, type ContactDetails, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, type CreateCheckoutRequest, type CreateCheckoutResponse, } from './create-checkout/index.js';
|
|
11
|
+
export { createOrder, type CreateOrderOptions, type CreateOrderResponse, } from './create-order/index.js';
|
|
11
12
|
export { queryLocations, type QueryLocationsResult, type LocationData, } from './query-locations/index.js';
|
|
12
13
|
export { queryCategories, type QueryCategoriesResult, type Category, } from './query-categories/index.js';
|
|
13
14
|
/**
|
package/cjs/dist/api/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { getServiceById, getServiceBySlug, queryServices, } from './query-servic
|
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export { createBooking, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, } from './create-checkout/index.js';
|
|
11
|
+
export { createOrder, } from './create-order/index.js';
|
|
11
12
|
export { queryLocations, } from './query-locations/index.js';
|
|
12
13
|
export { queryCategories, } from './query-categories/index.js';
|
|
13
14
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Wraps core Book with AsChildSlot pattern for customization
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import type { BookChildProps,
|
|
6
|
+
import type { BookChildProps, BookingError } from '../../services/booking/book-action/types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Props for the high-level Book component
|
|
9
9
|
*/
|
|
@@ -14,7 +14,12 @@ export interface BookProps {
|
|
|
14
14
|
label?: string;
|
|
15
15
|
loadingState?: string;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
-
|
|
17
|
+
onCheckout?: (result: {
|
|
18
|
+
checkoutId: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
onComplete?: (result: {
|
|
21
|
+
orderId: string;
|
|
22
|
+
}) => void;
|
|
18
23
|
onError?: (error: BookingError) => void;
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
@@ -27,12 +32,16 @@ export interface BookProps {
|
|
|
27
32
|
*
|
|
28
33
|
* // With callbacks
|
|
29
34
|
* <Booking.Actions.Book
|
|
30
|
-
*
|
|
35
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
36
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
31
37
|
* onError={(error) => console.error(error.message)}
|
|
32
38
|
* />
|
|
33
39
|
*
|
|
34
40
|
* // With render function
|
|
35
|
-
* <Booking.Actions.Book
|
|
41
|
+
* <Booking.Actions.Book
|
|
42
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
43
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
44
|
+
* >
|
|
36
45
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
37
46
|
* <button onClick={onClick} disabled={disabled}>
|
|
38
47
|
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
@@ -41,7 +50,11 @@ export interface BookProps {
|
|
|
41
50
|
* </Booking.Actions.Book>
|
|
42
51
|
*
|
|
43
52
|
* // With asChild
|
|
44
|
-
* <Booking.Actions.Book
|
|
53
|
+
* <Booking.Actions.Book
|
|
54
|
+
* asChild
|
|
55
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
56
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
57
|
+
* >
|
|
45
58
|
* <CustomBookButton />
|
|
46
59
|
* </Booking.Actions.Book>
|
|
47
60
|
* ```
|
|
@@ -20,12 +20,16 @@ var TestIds;
|
|
|
20
20
|
*
|
|
21
21
|
* // With callbacks
|
|
22
22
|
* <Booking.Actions.Book
|
|
23
|
-
*
|
|
23
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
24
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
24
25
|
* onError={(error) => console.error(error.message)}
|
|
25
26
|
* />
|
|
26
27
|
*
|
|
27
28
|
* // With render function
|
|
28
|
-
* <Booking.Actions.Book
|
|
29
|
+
* <Booking.Actions.Book
|
|
30
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
31
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
32
|
+
* >
|
|
29
33
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
30
34
|
* <button onClick={onClick} disabled={disabled}>
|
|
31
35
|
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
@@ -34,13 +38,17 @@ var TestIds;
|
|
|
34
38
|
* </Booking.Actions.Book>
|
|
35
39
|
*
|
|
36
40
|
* // With asChild
|
|
37
|
-
* <Booking.Actions.Book
|
|
41
|
+
* <Booking.Actions.Book
|
|
42
|
+
* asChild
|
|
43
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
44
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
45
|
+
* >
|
|
38
46
|
* <CustomBookButton />
|
|
39
47
|
* </Booking.Actions.Book>
|
|
40
48
|
* ```
|
|
41
49
|
*/
|
|
42
50
|
export const Book = React.forwardRef((props, ref) => {
|
|
43
|
-
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled,
|
|
44
|
-
return (_jsx(CoreBook.Book, {
|
|
51
|
+
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled, onCheckout, onComplete, onError, } = props;
|
|
52
|
+
return (_jsx(CoreBook.Book, { onCheckout: onCheckout, onComplete: onComplete, onError: onError, disabled: disabled, children: (childProps) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingActionBook, "data-in-progress": childProps.isLoading, customElement: children, customElementProps: childProps, children: _jsx("button", { onClick: childProps.onClick, disabled: childProps.disabled, children: childProps.isLoading ? loadingState : label }) })) }));
|
|
45
53
|
});
|
|
46
54
|
Book.displayName = 'Booking.Actions.Book';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* NO AsChildSlot, NO asChild - that belongs in high-level component
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import
|
|
6
|
+
import { type BookingError } from '../../../services/booking/book-action/types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Data exposed by Book component via render props
|
|
9
9
|
*/
|
|
@@ -19,7 +19,12 @@ export interface BookData {
|
|
|
19
19
|
*/
|
|
20
20
|
export interface BookProps {
|
|
21
21
|
children: (data: BookData) => React.ReactNode;
|
|
22
|
-
|
|
22
|
+
onCheckout?: (result: {
|
|
23
|
+
checkoutId: string;
|
|
24
|
+
}) => void;
|
|
25
|
+
onComplete?: (result: {
|
|
26
|
+
orderId: string;
|
|
27
|
+
}) => void;
|
|
23
28
|
onError?: (error: BookingError) => void;
|
|
24
29
|
disabled?: boolean;
|
|
25
30
|
}
|
|
@@ -29,7 +34,10 @@ export interface BookProps {
|
|
|
29
34
|
*
|
|
30
35
|
* @example
|
|
31
36
|
* ```tsx
|
|
32
|
-
* <Book
|
|
37
|
+
* <Book
|
|
38
|
+
* onCheckout={({ checkoutId }) => navigate(`/checkout?id=${checkoutId}`)}
|
|
39
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
40
|
+
* >
|
|
33
41
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
34
42
|
* <button onClick={onClick} disabled={disabled}>
|
|
35
43
|
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
@@ -6,13 +6,17 @@ import { useState } from 'react';
|
|
|
6
6
|
import { useService } from '@wix/services-manager-react';
|
|
7
7
|
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
8
8
|
import { canBook } from '../../../services/booking/book-action/canBook.js';
|
|
9
|
+
import { BookResultType, } from '../../../services/booking/book-action/types.js';
|
|
9
10
|
/**
|
|
10
11
|
* Core Book component - provides book action data via render props.
|
|
11
12
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```tsx
|
|
15
|
-
* <Book
|
|
16
|
+
* <Book
|
|
17
|
+
* onCheckout={({ checkoutId }) => navigate(`/checkout?id=${checkoutId}`)}
|
|
18
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
19
|
+
* >
|
|
16
20
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
17
21
|
* <button onClick={onClick} disabled={disabled}>
|
|
18
22
|
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
@@ -22,7 +26,7 @@ import { canBook } from '../../../services/booking/book-action/canBook.js';
|
|
|
22
26
|
* ```
|
|
23
27
|
*/
|
|
24
28
|
export function Book(props) {
|
|
25
|
-
const { children,
|
|
29
|
+
const { children, onCheckout, onComplete, onError, disabled } = props;
|
|
26
30
|
const [isLoading, setIsLoading] = useState(false);
|
|
27
31
|
const [error, setError] = useState(null);
|
|
28
32
|
const bookingService = useService(BookingServiceDefinition);
|
|
@@ -34,7 +38,12 @@ export function Book(props) {
|
|
|
34
38
|
setError(null);
|
|
35
39
|
try {
|
|
36
40
|
const result = await bookingService.actions.book();
|
|
37
|
-
|
|
41
|
+
if (result.type === BookResultType.CheckoutRequired) {
|
|
42
|
+
onCheckout?.({ checkoutId: result.checkoutId });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
onComplete?.({ orderId: result.orderId });
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
catch (e) {
|
|
40
49
|
const bookingError = {
|
|
@@ -4,18 +4,21 @@
|
|
|
4
4
|
* 1. Validates booking data with canBook()
|
|
5
5
|
* 2. Creates a booking via createBooking() from API layer
|
|
6
6
|
* 3. Creates a checkout via createCheckout() from API layer
|
|
7
|
-
* 4.
|
|
7
|
+
* 4. Determines if checkout is required or can be skipped
|
|
8
|
+
* 5. Returns appropriate result for navigation
|
|
8
9
|
*/
|
|
9
|
-
import type
|
|
10
|
+
import { type BookActionParams, type BookResult } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* Executes the complete booking flow:
|
|
12
13
|
* 1. Validates that all required data is present
|
|
13
14
|
* 2. Creates a booking with the selected service, time slot, and form data
|
|
14
15
|
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
15
|
-
* 4.
|
|
16
|
+
* 4. Determines if checkout page is required or can be skipped
|
|
17
|
+
* 5. If checkout can be skipped (free/offline), creates order directly
|
|
18
|
+
* 6. Returns appropriate result for navigation
|
|
16
19
|
*
|
|
17
20
|
* @param params - The booking action parameters
|
|
18
|
-
* @returns BookResult with
|
|
19
|
-
* @throws Error if validation fails, or booking/checkout creation fails
|
|
21
|
+
* @returns BookResult with either checkoutId or orderId
|
|
22
|
+
* @throws Error if validation fails, or booking/checkout/order creation fails
|
|
20
23
|
*/
|
|
21
24
|
export declare function executeBookAction(params: BookActionParams): Promise<BookResult>;
|
|
@@ -4,23 +4,29 @@
|
|
|
4
4
|
* 1. Validates booking data with canBook()
|
|
5
5
|
* 2. Creates a booking via createBooking() from API layer
|
|
6
6
|
* 3. Creates a checkout via createCheckout() from API layer
|
|
7
|
-
* 4.
|
|
7
|
+
* 4. Determines if checkout is required or can be skipped
|
|
8
|
+
* 5. Returns appropriate result for navigation
|
|
8
9
|
*/
|
|
9
10
|
import { createBooking } from '../../../api/create-booking/index.js';
|
|
10
11
|
import { createCheckout } from '../../../api/create-checkout/index.js';
|
|
12
|
+
import { createOrder } from '../../../api/create-order/index.js';
|
|
11
13
|
import { buildBookingRequest } from './buildBookingRequest.js';
|
|
12
14
|
import { buildCheckoutRequest } from './buildCheckoutRequest.js';
|
|
13
15
|
import { canBook } from './canBook.js';
|
|
16
|
+
import { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
17
|
+
import { BookResultType, } from './types.js';
|
|
14
18
|
/**
|
|
15
19
|
* Executes the complete booking flow:
|
|
16
20
|
* 1. Validates that all required data is present
|
|
17
21
|
* 2. Creates a booking with the selected service, time slot, and form data
|
|
18
22
|
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
19
|
-
* 4.
|
|
23
|
+
* 4. Determines if checkout page is required or can be skipped
|
|
24
|
+
* 5. If checkout can be skipped (free/offline), creates order directly
|
|
25
|
+
* 6. Returns appropriate result for navigation
|
|
20
26
|
*
|
|
21
27
|
* @param params - The booking action parameters
|
|
22
|
-
* @returns BookResult with
|
|
23
|
-
* @throws Error if validation fails, or booking/checkout creation fails
|
|
28
|
+
* @returns BookResult with either checkoutId or orderId
|
|
29
|
+
* @throws Error if validation fails, or booking/checkout/order creation fails
|
|
24
30
|
*/
|
|
25
31
|
export async function executeBookAction(params) {
|
|
26
32
|
// Step 0: Validate
|
|
@@ -45,16 +51,30 @@ export async function executeBookAction(params) {
|
|
|
45
51
|
// Step 2: Create checkout
|
|
46
52
|
// businessLocationId: prefer timeSlot.location, fallback to global location
|
|
47
53
|
const firstSelection = params.serviceSelections[0];
|
|
48
|
-
|
|
54
|
+
if (!firstSelection) {
|
|
55
|
+
throw new Error('No service selection found');
|
|
56
|
+
}
|
|
57
|
+
const businessLocationId = firstSelection.timeSlot?.location?._id ?? undefined;
|
|
49
58
|
const checkoutRequest = buildCheckoutRequest({
|
|
50
59
|
bookingId,
|
|
51
60
|
contactDetails,
|
|
52
61
|
businessLocationId,
|
|
53
62
|
});
|
|
54
63
|
const checkoutResponse = await createCheckout(checkoutRequest);
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
64
|
+
const checkout = checkoutResponse.checkout;
|
|
65
|
+
if (!checkout?._id) {
|
|
57
66
|
throw new Error('Failed to create checkout');
|
|
58
67
|
}
|
|
59
|
-
|
|
68
|
+
// Step 3: Determine if checkout is required or can be skipped
|
|
69
|
+
const service = firstSelection.service;
|
|
70
|
+
if (isCheckoutRequired(checkout, service)) {
|
|
71
|
+
return { type: BookResultType.CheckoutRequired, checkoutId: checkout._id };
|
|
72
|
+
}
|
|
73
|
+
// Step 4: Skip checkout - create order directly
|
|
74
|
+
const orderResponse = await createOrder(checkout._id);
|
|
75
|
+
const orderId = orderResponse.orderId;
|
|
76
|
+
if (!orderId) {
|
|
77
|
+
throw new Error('Failed to create order');
|
|
78
|
+
}
|
|
79
|
+
return { type: BookResultType.CheckoutSkipped, orderId };
|
|
60
80
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the createBooking request from BookActionParams
|
|
3
3
|
*/
|
|
4
|
+
import { LocationType as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
5
|
+
import { LocationType as BookingLocationType } from '@wix/auto_sdk_bookings_bookings';
|
|
4
6
|
/**
|
|
5
7
|
* Derives the selected payment option from service payment settings.
|
|
6
8
|
* Maps service.payment.options to booking API's selectedPaymentOption.
|
|
@@ -69,7 +71,11 @@ export function buildBookingRequest(params) {
|
|
|
69
71
|
? { _id: resource._id, name: resource.name }
|
|
70
72
|
: undefined,
|
|
71
73
|
location: slotLocation
|
|
72
|
-
? {
|
|
74
|
+
? {
|
|
75
|
+
_id: slotLocation._id,
|
|
76
|
+
name: slotLocation.name,
|
|
77
|
+
locationType: mapTimeSlotLocationTypeToBookingLocationType(slotLocation.locationType),
|
|
78
|
+
}
|
|
73
79
|
: undefined,
|
|
74
80
|
},
|
|
75
81
|
},
|
|
@@ -81,3 +87,17 @@ export function buildBookingRequest(params) {
|
|
|
81
87
|
formSubmission: formSubmission ?? undefined,
|
|
82
88
|
};
|
|
83
89
|
}
|
|
90
|
+
function mapTimeSlotLocationTypeToBookingLocationType(slotLocationType) {
|
|
91
|
+
switch (slotLocationType) {
|
|
92
|
+
case TimeSlotLocationType.BUSINESS:
|
|
93
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
94
|
+
case TimeSlotLocationType.CUSTOMER:
|
|
95
|
+
return BookingLocationType.CUSTOM;
|
|
96
|
+
case TimeSlotLocationType.CUSTOM:
|
|
97
|
+
return BookingLocationType.OWNER_CUSTOM;
|
|
98
|
+
case TimeSlotLocationType.UNKNOWN_LOCATION_TYPE:
|
|
99
|
+
return BookingLocationType.UNDEFINED;
|
|
100
|
+
default:
|
|
101
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -5,4 +5,5 @@ export { executeBookAction } from './bookAction.js';
|
|
|
5
5
|
export { canBook, type CanBookParams } from './canBook.js';
|
|
6
6
|
export { buildBookingRequest } from './buildBookingRequest.js';
|
|
7
7
|
export { buildCheckoutRequest, type BuildCheckoutParams, } from './buildCheckoutRequest.js';
|
|
8
|
-
export
|
|
8
|
+
export { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
9
|
+
export { BookResultType, type BookProps, type BookResult, type BookingError, type BookActionParams, type BookChildProps, } from './types.js';
|
|
@@ -5,3 +5,5 @@ export { executeBookAction } from './bookAction.js';
|
|
|
5
5
|
export { canBook } from './canBook.js';
|
|
6
6
|
export { buildBookingRequest } from './buildBookingRequest.js';
|
|
7
7
|
export { buildCheckoutRequest, } from './buildCheckoutRequest.js';
|
|
8
|
+
export { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
9
|
+
export { BookResultType, } from './types.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if checkout page is required based on payment amounts
|
|
3
|
+
*/
|
|
4
|
+
import type { Checkout } from '@wix/auto_sdk_ecom_checkout';
|
|
5
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
6
|
+
/**
|
|
7
|
+
* Determines if checkout page is required based on payment amounts.
|
|
8
|
+
*
|
|
9
|
+
* Skip checkout when:
|
|
10
|
+
* - Free booking: payNow = 0 AND payLater = 0
|
|
11
|
+
* - Offline payment: payNow = 0
|
|
12
|
+
*
|
|
13
|
+
* Force checkout when:
|
|
14
|
+
* - Service has cancellation fee
|
|
15
|
+
*
|
|
16
|
+
* @param checkout - The checkout response from createCheckout
|
|
17
|
+
* @param service - The service being booked
|
|
18
|
+
* @returns true if checkout page is required, false if can skip to order
|
|
19
|
+
*/
|
|
20
|
+
export declare function isCheckoutRequired(checkout: Checkout, service: Service): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if checkout page is required based on payment amounts
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Determines if checkout page is required based on payment amounts.
|
|
6
|
+
*
|
|
7
|
+
* Skip checkout when:
|
|
8
|
+
* - Free booking: payNow = 0 AND payLater = 0
|
|
9
|
+
* - Offline payment: payNow = 0
|
|
10
|
+
*
|
|
11
|
+
* Force checkout when:
|
|
12
|
+
* - Service has cancellation fee
|
|
13
|
+
*
|
|
14
|
+
* @param checkout - The checkout response from createCheckout
|
|
15
|
+
* @param service - The service being booked
|
|
16
|
+
* @returns true if checkout page is required, false if can skip to order
|
|
17
|
+
*/
|
|
18
|
+
export function isCheckoutRequired(checkout, service) {
|
|
19
|
+
// Force checkout for cancellation fee
|
|
20
|
+
const hasCancellationFee = service.bookingPolicy?.cancellationPolicy?.enabled;
|
|
21
|
+
if (hasCancellationFee) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const payNowAmount = Number(checkout.payNow?.total?.amount ?? 0);
|
|
25
|
+
const payLaterAmount = Number(checkout.payLater?.total?.amount ?? 0);
|
|
26
|
+
// Free or price plan: both amounts are 0
|
|
27
|
+
const isFreeOrPricePlan = payNowAmount === 0 && payLaterAmount === 0;
|
|
28
|
+
// Offline payment: payNow is 0
|
|
29
|
+
const isOfflinePayment = payNowAmount === 0;
|
|
30
|
+
// Skip checkout if free/price plan OR offline payment
|
|
31
|
+
return !(isFreeOrPricePlan || isOfflinePayment);
|
|
32
|
+
}
|
|
@@ -4,11 +4,22 @@
|
|
|
4
4
|
import type { ServiceSelection } from '../booking.js';
|
|
5
5
|
import type { FormValues } from '@wix/form-public';
|
|
6
6
|
import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
7
|
+
/**
|
|
8
|
+
* Enum for book result types
|
|
9
|
+
*/
|
|
10
|
+
export declare enum BookResultType {
|
|
11
|
+
CheckoutRequired = "checkout_required",
|
|
12
|
+
CheckoutSkipped = "checkout_skipped"
|
|
13
|
+
}
|
|
7
14
|
/**
|
|
8
15
|
* Result returned from the book action
|
|
9
16
|
*/
|
|
10
17
|
export type BookResult = {
|
|
18
|
+
type: BookResultType.CheckoutRequired;
|
|
11
19
|
checkoutId: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: BookResultType.CheckoutSkipped;
|
|
22
|
+
orderId: string;
|
|
12
23
|
};
|
|
13
24
|
/**
|
|
14
25
|
* Error object for booking errors
|
|
@@ -46,6 +57,11 @@ export interface BookProps {
|
|
|
46
57
|
label?: string;
|
|
47
58
|
loadingState?: string;
|
|
48
59
|
disabled?: boolean;
|
|
49
|
-
|
|
60
|
+
onCheckout?: (result: {
|
|
61
|
+
checkoutId: string;
|
|
62
|
+
}) => void;
|
|
63
|
+
onComplete?: (result: {
|
|
64
|
+
orderId: string;
|
|
65
|
+
}) => void;
|
|
50
66
|
onError?: (error: BookingError) => void;
|
|
51
67
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the Book action
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Enum for book result types
|
|
6
|
+
*/
|
|
7
|
+
export var BookResultType;
|
|
8
|
+
(function (BookResultType) {
|
|
9
|
+
BookResultType["CheckoutRequired"] = "checkout_required";
|
|
10
|
+
BookResultType["CheckoutSkipped"] = "checkout_skipped";
|
|
11
|
+
})(BookResultType || (BookResultType = {}));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Order API
|
|
3
|
+
* Wrapper for the Wix eCommerce createOrder SDK function
|
|
4
|
+
*/
|
|
5
|
+
import { type CreateOrderOptions, type CreateOrderResponse } from '@wix/auto_sdk_ecom_checkout';
|
|
6
|
+
/**
|
|
7
|
+
* Creates an order from a checkout session
|
|
8
|
+
*
|
|
9
|
+
* @param checkoutId - The checkout session ID
|
|
10
|
+
* @param options - Optional additional order creation options
|
|
11
|
+
* @returns Promise resolving to the created order response
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const response = await createOrder(checkoutId);
|
|
16
|
+
* const orderId = response.orderId;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function createOrder(checkoutId: string, options?: CreateOrderOptions): Promise<CreateOrderResponse>;
|
|
20
|
+
export type { CreateOrderOptions, CreateOrderResponse };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Order API
|
|
3
|
+
* Wrapper for the Wix eCommerce createOrder SDK function
|
|
4
|
+
*/
|
|
5
|
+
import { createOrder as createOrderSdk, } from '@wix/auto_sdk_ecom_checkout';
|
|
6
|
+
/**
|
|
7
|
+
* Creates an order from a checkout session
|
|
8
|
+
*
|
|
9
|
+
* @param checkoutId - The checkout session ID
|
|
10
|
+
* @param options - Optional additional order creation options
|
|
11
|
+
* @returns Promise resolving to the created order response
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const response = await createOrder(checkoutId);
|
|
16
|
+
* const orderId = response.orderId;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function createOrder(checkoutId, options) {
|
|
20
|
+
return createOrderSdk(checkoutId, options);
|
|
21
|
+
}
|
package/dist/api/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { getServiceById, getServiceBySlug, queryServices, type PagingMetadata, t
|
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export { createBooking, type CreateBookingOptions, type Booking, type CreateBookingRequest, type CreateBookingResponse, type ParticipantNotification, type ContactDetails, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, type CreateCheckoutRequest, type CreateCheckoutResponse, } from './create-checkout/index.js';
|
|
11
|
+
export { createOrder, type CreateOrderOptions, type CreateOrderResponse, } from './create-order/index.js';
|
|
11
12
|
export { queryLocations, type QueryLocationsResult, type LocationData, } from './query-locations/index.js';
|
|
12
13
|
export { queryCategories, type QueryCategoriesResult, type Category, } from './query-categories/index.js';
|
|
13
14
|
/**
|
package/dist/api/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { getServiceById, getServiceBySlug, queryServices, } from './query-servic
|
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export { createBooking, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, } from './create-checkout/index.js';
|
|
11
|
+
export { createOrder, } from './create-order/index.js';
|
|
11
12
|
export { queryLocations, } from './query-locations/index.js';
|
|
12
13
|
export { queryCategories, } from './query-categories/index.js';
|
|
13
14
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Wraps core Book with AsChildSlot pattern for customization
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import type { BookChildProps,
|
|
6
|
+
import type { BookChildProps, BookingError } from '../../services/booking/book-action/types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Props for the high-level Book component
|
|
9
9
|
*/
|
|
@@ -14,7 +14,12 @@ export interface BookProps {
|
|
|
14
14
|
label?: string;
|
|
15
15
|
loadingState?: string;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
-
|
|
17
|
+
onCheckout?: (result: {
|
|
18
|
+
checkoutId: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
onComplete?: (result: {
|
|
21
|
+
orderId: string;
|
|
22
|
+
}) => void;
|
|
18
23
|
onError?: (error: BookingError) => void;
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
@@ -27,12 +32,16 @@ export interface BookProps {
|
|
|
27
32
|
*
|
|
28
33
|
* // With callbacks
|
|
29
34
|
* <Booking.Actions.Book
|
|
30
|
-
*
|
|
35
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
36
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
31
37
|
* onError={(error) => console.error(error.message)}
|
|
32
38
|
* />
|
|
33
39
|
*
|
|
34
40
|
* // With render function
|
|
35
|
-
* <Booking.Actions.Book
|
|
41
|
+
* <Booking.Actions.Book
|
|
42
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
43
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
44
|
+
* >
|
|
36
45
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
37
46
|
* <button onClick={onClick} disabled={disabled}>
|
|
38
47
|
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
@@ -41,7 +50,11 @@ export interface BookProps {
|
|
|
41
50
|
* </Booking.Actions.Book>
|
|
42
51
|
*
|
|
43
52
|
* // With asChild
|
|
44
|
-
* <Booking.Actions.Book
|
|
53
|
+
* <Booking.Actions.Book
|
|
54
|
+
* asChild
|
|
55
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
56
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
57
|
+
* >
|
|
45
58
|
* <CustomBookButton />
|
|
46
59
|
* </Booking.Actions.Book>
|
|
47
60
|
* ```
|
|
@@ -20,12 +20,16 @@ var TestIds;
|
|
|
20
20
|
*
|
|
21
21
|
* // With callbacks
|
|
22
22
|
* <Booking.Actions.Book
|
|
23
|
-
*
|
|
23
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
24
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
24
25
|
* onError={(error) => console.error(error.message)}
|
|
25
26
|
* />
|
|
26
27
|
*
|
|
27
28
|
* // With render function
|
|
28
|
-
* <Booking.Actions.Book
|
|
29
|
+
* <Booking.Actions.Book
|
|
30
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
31
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
32
|
+
* >
|
|
29
33
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
30
34
|
* <button onClick={onClick} disabled={disabled}>
|
|
31
35
|
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
@@ -34,13 +38,17 @@ var TestIds;
|
|
|
34
38
|
* </Booking.Actions.Book>
|
|
35
39
|
*
|
|
36
40
|
* // With asChild
|
|
37
|
-
* <Booking.Actions.Book
|
|
41
|
+
* <Booking.Actions.Book
|
|
42
|
+
* asChild
|
|
43
|
+
* onCheckout={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
44
|
+
* onComplete={({ orderId }) => router.push(`/thank-you?orderId=${orderId}`)}
|
|
45
|
+
* >
|
|
38
46
|
* <CustomBookButton />
|
|
39
47
|
* </Booking.Actions.Book>
|
|
40
48
|
* ```
|
|
41
49
|
*/
|
|
42
50
|
export const Book = React.forwardRef((props, ref) => {
|
|
43
|
-
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled,
|
|
44
|
-
return (_jsx(CoreBook.Book, {
|
|
51
|
+
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled, onCheckout, onComplete, onError, } = props;
|
|
52
|
+
return (_jsx(CoreBook.Book, { onCheckout: onCheckout, onComplete: onComplete, onError: onError, disabled: disabled, children: (childProps) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingActionBook, "data-in-progress": childProps.isLoading, customElement: children, customElementProps: childProps, children: _jsx("button", { onClick: childProps.onClick, disabled: childProps.disabled, children: childProps.isLoading ? loadingState : label }) })) }));
|
|
45
53
|
});
|
|
46
54
|
Book.displayName = 'Booking.Actions.Book';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* NO AsChildSlot, NO asChild - that belongs in high-level component
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import
|
|
6
|
+
import { type BookingError } from '../../../services/booking/book-action/types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Data exposed by Book component via render props
|
|
9
9
|
*/
|
|
@@ -19,7 +19,12 @@ export interface BookData {
|
|
|
19
19
|
*/
|
|
20
20
|
export interface BookProps {
|
|
21
21
|
children: (data: BookData) => React.ReactNode;
|
|
22
|
-
|
|
22
|
+
onCheckout?: (result: {
|
|
23
|
+
checkoutId: string;
|
|
24
|
+
}) => void;
|
|
25
|
+
onComplete?: (result: {
|
|
26
|
+
orderId: string;
|
|
27
|
+
}) => void;
|
|
23
28
|
onError?: (error: BookingError) => void;
|
|
24
29
|
disabled?: boolean;
|
|
25
30
|
}
|
|
@@ -29,7 +34,10 @@ export interface BookProps {
|
|
|
29
34
|
*
|
|
30
35
|
* @example
|
|
31
36
|
* ```tsx
|
|
32
|
-
* <Book
|
|
37
|
+
* <Book
|
|
38
|
+
* onCheckout={({ checkoutId }) => navigate(`/checkout?id=${checkoutId}`)}
|
|
39
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
40
|
+
* >
|
|
33
41
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
34
42
|
* <button onClick={onClick} disabled={disabled}>
|
|
35
43
|
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
@@ -6,13 +6,17 @@ import { useState } from 'react';
|
|
|
6
6
|
import { useService } from '@wix/services-manager-react';
|
|
7
7
|
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
8
8
|
import { canBook } from '../../../services/booking/book-action/canBook.js';
|
|
9
|
+
import { BookResultType, } from '../../../services/booking/book-action/types.js';
|
|
9
10
|
/**
|
|
10
11
|
* Core Book component - provides book action data via render props.
|
|
11
12
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```tsx
|
|
15
|
-
* <Book
|
|
16
|
+
* <Book
|
|
17
|
+
* onCheckout={({ checkoutId }) => navigate(`/checkout?id=${checkoutId}`)}
|
|
18
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
19
|
+
* >
|
|
16
20
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
17
21
|
* <button onClick={onClick} disabled={disabled}>
|
|
18
22
|
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
@@ -22,7 +26,7 @@ import { canBook } from '../../../services/booking/book-action/canBook.js';
|
|
|
22
26
|
* ```
|
|
23
27
|
*/
|
|
24
28
|
export function Book(props) {
|
|
25
|
-
const { children,
|
|
29
|
+
const { children, onCheckout, onComplete, onError, disabled } = props;
|
|
26
30
|
const [isLoading, setIsLoading] = useState(false);
|
|
27
31
|
const [error, setError] = useState(null);
|
|
28
32
|
const bookingService = useService(BookingServiceDefinition);
|
|
@@ -34,7 +38,12 @@ export function Book(props) {
|
|
|
34
38
|
setError(null);
|
|
35
39
|
try {
|
|
36
40
|
const result = await bookingService.actions.book();
|
|
37
|
-
|
|
41
|
+
if (result.type === BookResultType.CheckoutRequired) {
|
|
42
|
+
onCheckout?.({ checkoutId: result.checkoutId });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
onComplete?.({ orderId: result.orderId });
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
catch (e) {
|
|
40
49
|
const bookingError = {
|
|
@@ -4,18 +4,21 @@
|
|
|
4
4
|
* 1. Validates booking data with canBook()
|
|
5
5
|
* 2. Creates a booking via createBooking() from API layer
|
|
6
6
|
* 3. Creates a checkout via createCheckout() from API layer
|
|
7
|
-
* 4.
|
|
7
|
+
* 4. Determines if checkout is required or can be skipped
|
|
8
|
+
* 5. Returns appropriate result for navigation
|
|
8
9
|
*/
|
|
9
|
-
import type
|
|
10
|
+
import { type BookActionParams, type BookResult } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* Executes the complete booking flow:
|
|
12
13
|
* 1. Validates that all required data is present
|
|
13
14
|
* 2. Creates a booking with the selected service, time slot, and form data
|
|
14
15
|
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
15
|
-
* 4.
|
|
16
|
+
* 4. Determines if checkout page is required or can be skipped
|
|
17
|
+
* 5. If checkout can be skipped (free/offline), creates order directly
|
|
18
|
+
* 6. Returns appropriate result for navigation
|
|
16
19
|
*
|
|
17
20
|
* @param params - The booking action parameters
|
|
18
|
-
* @returns BookResult with
|
|
19
|
-
* @throws Error if validation fails, or booking/checkout creation fails
|
|
21
|
+
* @returns BookResult with either checkoutId or orderId
|
|
22
|
+
* @throws Error if validation fails, or booking/checkout/order creation fails
|
|
20
23
|
*/
|
|
21
24
|
export declare function executeBookAction(params: BookActionParams): Promise<BookResult>;
|
|
@@ -4,23 +4,29 @@
|
|
|
4
4
|
* 1. Validates booking data with canBook()
|
|
5
5
|
* 2. Creates a booking via createBooking() from API layer
|
|
6
6
|
* 3. Creates a checkout via createCheckout() from API layer
|
|
7
|
-
* 4.
|
|
7
|
+
* 4. Determines if checkout is required or can be skipped
|
|
8
|
+
* 5. Returns appropriate result for navigation
|
|
8
9
|
*/
|
|
9
10
|
import { createBooking } from '../../../api/create-booking/index.js';
|
|
10
11
|
import { createCheckout } from '../../../api/create-checkout/index.js';
|
|
12
|
+
import { createOrder } from '../../../api/create-order/index.js';
|
|
11
13
|
import { buildBookingRequest } from './buildBookingRequest.js';
|
|
12
14
|
import { buildCheckoutRequest } from './buildCheckoutRequest.js';
|
|
13
15
|
import { canBook } from './canBook.js';
|
|
16
|
+
import { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
17
|
+
import { BookResultType, } from './types.js';
|
|
14
18
|
/**
|
|
15
19
|
* Executes the complete booking flow:
|
|
16
20
|
* 1. Validates that all required data is present
|
|
17
21
|
* 2. Creates a booking with the selected service, time slot, and form data
|
|
18
22
|
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
19
|
-
* 4.
|
|
23
|
+
* 4. Determines if checkout page is required or can be skipped
|
|
24
|
+
* 5. If checkout can be skipped (free/offline), creates order directly
|
|
25
|
+
* 6. Returns appropriate result for navigation
|
|
20
26
|
*
|
|
21
27
|
* @param params - The booking action parameters
|
|
22
|
-
* @returns BookResult with
|
|
23
|
-
* @throws Error if validation fails, or booking/checkout creation fails
|
|
28
|
+
* @returns BookResult with either checkoutId or orderId
|
|
29
|
+
* @throws Error if validation fails, or booking/checkout/order creation fails
|
|
24
30
|
*/
|
|
25
31
|
export async function executeBookAction(params) {
|
|
26
32
|
// Step 0: Validate
|
|
@@ -45,16 +51,30 @@ export async function executeBookAction(params) {
|
|
|
45
51
|
// Step 2: Create checkout
|
|
46
52
|
// businessLocationId: prefer timeSlot.location, fallback to global location
|
|
47
53
|
const firstSelection = params.serviceSelections[0];
|
|
48
|
-
|
|
54
|
+
if (!firstSelection) {
|
|
55
|
+
throw new Error('No service selection found');
|
|
56
|
+
}
|
|
57
|
+
const businessLocationId = firstSelection.timeSlot?.location?._id ?? undefined;
|
|
49
58
|
const checkoutRequest = buildCheckoutRequest({
|
|
50
59
|
bookingId,
|
|
51
60
|
contactDetails,
|
|
52
61
|
businessLocationId,
|
|
53
62
|
});
|
|
54
63
|
const checkoutResponse = await createCheckout(checkoutRequest);
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
64
|
+
const checkout = checkoutResponse.checkout;
|
|
65
|
+
if (!checkout?._id) {
|
|
57
66
|
throw new Error('Failed to create checkout');
|
|
58
67
|
}
|
|
59
|
-
|
|
68
|
+
// Step 3: Determine if checkout is required or can be skipped
|
|
69
|
+
const service = firstSelection.service;
|
|
70
|
+
if (isCheckoutRequired(checkout, service)) {
|
|
71
|
+
return { type: BookResultType.CheckoutRequired, checkoutId: checkout._id };
|
|
72
|
+
}
|
|
73
|
+
// Step 4: Skip checkout - create order directly
|
|
74
|
+
const orderResponse = await createOrder(checkout._id);
|
|
75
|
+
const orderId = orderResponse.orderId;
|
|
76
|
+
if (!orderId) {
|
|
77
|
+
throw new Error('Failed to create order');
|
|
78
|
+
}
|
|
79
|
+
return { type: BookResultType.CheckoutSkipped, orderId };
|
|
60
80
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the createBooking request from BookActionParams
|
|
3
3
|
*/
|
|
4
|
+
import { LocationType as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
5
|
+
import { LocationType as BookingLocationType } from '@wix/auto_sdk_bookings_bookings';
|
|
4
6
|
/**
|
|
5
7
|
* Derives the selected payment option from service payment settings.
|
|
6
8
|
* Maps service.payment.options to booking API's selectedPaymentOption.
|
|
@@ -69,7 +71,11 @@ export function buildBookingRequest(params) {
|
|
|
69
71
|
? { _id: resource._id, name: resource.name }
|
|
70
72
|
: undefined,
|
|
71
73
|
location: slotLocation
|
|
72
|
-
? {
|
|
74
|
+
? {
|
|
75
|
+
_id: slotLocation._id,
|
|
76
|
+
name: slotLocation.name,
|
|
77
|
+
locationType: mapTimeSlotLocationTypeToBookingLocationType(slotLocation.locationType),
|
|
78
|
+
}
|
|
73
79
|
: undefined,
|
|
74
80
|
},
|
|
75
81
|
},
|
|
@@ -81,3 +87,17 @@ export function buildBookingRequest(params) {
|
|
|
81
87
|
formSubmission: formSubmission ?? undefined,
|
|
82
88
|
};
|
|
83
89
|
}
|
|
90
|
+
function mapTimeSlotLocationTypeToBookingLocationType(slotLocationType) {
|
|
91
|
+
switch (slotLocationType) {
|
|
92
|
+
case TimeSlotLocationType.BUSINESS:
|
|
93
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
94
|
+
case TimeSlotLocationType.CUSTOMER:
|
|
95
|
+
return BookingLocationType.CUSTOM;
|
|
96
|
+
case TimeSlotLocationType.CUSTOM:
|
|
97
|
+
return BookingLocationType.OWNER_CUSTOM;
|
|
98
|
+
case TimeSlotLocationType.UNKNOWN_LOCATION_TYPE:
|
|
99
|
+
return BookingLocationType.UNDEFINED;
|
|
100
|
+
default:
|
|
101
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -5,4 +5,5 @@ export { executeBookAction } from './bookAction.js';
|
|
|
5
5
|
export { canBook, type CanBookParams } from './canBook.js';
|
|
6
6
|
export { buildBookingRequest } from './buildBookingRequest.js';
|
|
7
7
|
export { buildCheckoutRequest, type BuildCheckoutParams, } from './buildCheckoutRequest.js';
|
|
8
|
-
export
|
|
8
|
+
export { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
9
|
+
export { BookResultType, type BookProps, type BookResult, type BookingError, type BookActionParams, type BookChildProps, } from './types.js';
|
|
@@ -5,3 +5,5 @@ export { executeBookAction } from './bookAction.js';
|
|
|
5
5
|
export { canBook } from './canBook.js';
|
|
6
6
|
export { buildBookingRequest } from './buildBookingRequest.js';
|
|
7
7
|
export { buildCheckoutRequest, } from './buildCheckoutRequest.js';
|
|
8
|
+
export { isCheckoutRequired } from './isCheckoutRequired.js';
|
|
9
|
+
export { BookResultType, } from './types.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if checkout page is required based on payment amounts
|
|
3
|
+
*/
|
|
4
|
+
import type { Checkout } from '@wix/auto_sdk_ecom_checkout';
|
|
5
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
6
|
+
/**
|
|
7
|
+
* Determines if checkout page is required based on payment amounts.
|
|
8
|
+
*
|
|
9
|
+
* Skip checkout when:
|
|
10
|
+
* - Free booking: payNow = 0 AND payLater = 0
|
|
11
|
+
* - Offline payment: payNow = 0
|
|
12
|
+
*
|
|
13
|
+
* Force checkout when:
|
|
14
|
+
* - Service has cancellation fee
|
|
15
|
+
*
|
|
16
|
+
* @param checkout - The checkout response from createCheckout
|
|
17
|
+
* @param service - The service being booked
|
|
18
|
+
* @returns true if checkout page is required, false if can skip to order
|
|
19
|
+
*/
|
|
20
|
+
export declare function isCheckoutRequired(checkout: Checkout, service: Service): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if checkout page is required based on payment amounts
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Determines if checkout page is required based on payment amounts.
|
|
6
|
+
*
|
|
7
|
+
* Skip checkout when:
|
|
8
|
+
* - Free booking: payNow = 0 AND payLater = 0
|
|
9
|
+
* - Offline payment: payNow = 0
|
|
10
|
+
*
|
|
11
|
+
* Force checkout when:
|
|
12
|
+
* - Service has cancellation fee
|
|
13
|
+
*
|
|
14
|
+
* @param checkout - The checkout response from createCheckout
|
|
15
|
+
* @param service - The service being booked
|
|
16
|
+
* @returns true if checkout page is required, false if can skip to order
|
|
17
|
+
*/
|
|
18
|
+
export function isCheckoutRequired(checkout, service) {
|
|
19
|
+
// Force checkout for cancellation fee
|
|
20
|
+
const hasCancellationFee = service.bookingPolicy?.cancellationPolicy?.enabled;
|
|
21
|
+
if (hasCancellationFee) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const payNowAmount = Number(checkout.payNow?.total?.amount ?? 0);
|
|
25
|
+
const payLaterAmount = Number(checkout.payLater?.total?.amount ?? 0);
|
|
26
|
+
// Free or price plan: both amounts are 0
|
|
27
|
+
const isFreeOrPricePlan = payNowAmount === 0 && payLaterAmount === 0;
|
|
28
|
+
// Offline payment: payNow is 0
|
|
29
|
+
const isOfflinePayment = payNowAmount === 0;
|
|
30
|
+
// Skip checkout if free/price plan OR offline payment
|
|
31
|
+
return !(isFreeOrPricePlan || isOfflinePayment);
|
|
32
|
+
}
|
|
@@ -4,11 +4,22 @@
|
|
|
4
4
|
import type { ServiceSelection } from '../booking.js';
|
|
5
5
|
import type { FormValues } from '@wix/form-public';
|
|
6
6
|
import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
7
|
+
/**
|
|
8
|
+
* Enum for book result types
|
|
9
|
+
*/
|
|
10
|
+
export declare enum BookResultType {
|
|
11
|
+
CheckoutRequired = "checkout_required",
|
|
12
|
+
CheckoutSkipped = "checkout_skipped"
|
|
13
|
+
}
|
|
7
14
|
/**
|
|
8
15
|
* Result returned from the book action
|
|
9
16
|
*/
|
|
10
17
|
export type BookResult = {
|
|
18
|
+
type: BookResultType.CheckoutRequired;
|
|
11
19
|
checkoutId: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: BookResultType.CheckoutSkipped;
|
|
22
|
+
orderId: string;
|
|
12
23
|
};
|
|
13
24
|
/**
|
|
14
25
|
* Error object for booking errors
|
|
@@ -46,6 +57,11 @@ export interface BookProps {
|
|
|
46
57
|
label?: string;
|
|
47
58
|
loadingState?: string;
|
|
48
59
|
disabled?: boolean;
|
|
49
|
-
|
|
60
|
+
onCheckout?: (result: {
|
|
61
|
+
checkoutId: string;
|
|
62
|
+
}) => void;
|
|
63
|
+
onComplete?: (result: {
|
|
64
|
+
orderId: string;
|
|
65
|
+
}) => void;
|
|
50
66
|
onError?: (error: BookingError) => void;
|
|
51
67
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the Book action
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Enum for book result types
|
|
6
|
+
*/
|
|
7
|
+
export var BookResultType;
|
|
8
|
+
(function (BookResultType) {
|
|
9
|
+
BookResultType["CheckoutRequired"] = "checkout_required";
|
|
10
|
+
BookResultType["CheckoutSkipped"] = "checkout_skipped";
|
|
11
|
+
})(BookResultType || (BookResultType = {}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.49",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"groupId": "com.wixpress.headless-components"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
-
"falconPackageHash": "
|
|
75
|
+
"falconPackageHash": "ce9857f84a2dc23726f81fe1b20c314c9c652cbe712b90742c92d448"
|
|
76
76
|
}
|