@wix/headless-bookings 0.0.66 → 0.0.68
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/Book.d.ts +17 -11
- package/cjs/dist/react/booking/Book.js +13 -16
- package/cjs/dist/react/booking/Booking.d.ts +4 -3
- package/cjs/dist/react/booking/Booking.js +4 -3
- package/cjs/dist/react/core/booking/Book.d.ts +12 -2
- package/cjs/dist/react/core/booking/Book.js +12 -2
- package/cjs/dist/react/core/payment/Payment.d.ts +6 -12
- package/cjs/dist/react/core/payment/Payment.js +7 -16
- package/cjs/dist/react/payment/Payment.d.ts +12 -26
- package/cjs/dist/react/payment/Payment.js +18 -18
- package/cjs/dist/services/booking/book-action/types.d.ts +10 -2
- package/cjs/dist/services/payment/payment.def.d.ts +2 -4
- package/cjs/dist/services/payment/payment.js +2 -3
- package/cjs/dist/utils/navigation.js +2 -1
- package/dist/react/booking/Book.d.ts +17 -11
- package/dist/react/booking/Book.js +13 -16
- package/dist/react/booking/Booking.d.ts +4 -3
- package/dist/react/booking/Booking.js +4 -3
- package/dist/react/core/booking/Book.d.ts +12 -2
- package/dist/react/core/booking/Book.js +12 -2
- package/dist/react/core/payment/Payment.d.ts +6 -12
- package/dist/react/core/payment/Payment.js +7 -16
- package/dist/react/payment/Payment.d.ts +12 -26
- package/dist/react/payment/Payment.js +18 -18
- package/dist/services/booking/book-action/types.d.ts +10 -2
- package/dist/services/payment/payment.def.d.ts +2 -4
- package/dist/services/payment/payment.js +2 -3
- package/dist/utils/navigation.js +2 -1
- package/package.json +2 -2
|
@@ -14,13 +14,19 @@ export interface BookProps {
|
|
|
14
14
|
label?: string;
|
|
15
15
|
loadingState?: string;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Called when checkout is required for payment.
|
|
19
|
+
* The navigateToCheckout function already has the checkoutId captured - just call it to navigate.
|
|
20
|
+
*/
|
|
17
21
|
onCheckout?: (result: {
|
|
18
|
-
|
|
19
|
-
navigateToCheckout: (checkoutId: string) => void;
|
|
22
|
+
navigateToCheckout: () => void;
|
|
20
23
|
}) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Called when booking completes without requiring checkout (free/offline services).
|
|
26
|
+
* Receives the orderId which can be used to display confirmation or navigate to a thank you page.
|
|
27
|
+
*/
|
|
21
28
|
onComplete?: (result: {
|
|
22
29
|
orderId: string;
|
|
23
|
-
navigateToThankYou: (orderId: string) => void;
|
|
24
30
|
}) => void;
|
|
25
31
|
onError?: (error: BookingError) => void;
|
|
26
32
|
}
|
|
@@ -29,20 +35,20 @@ export interface BookProps {
|
|
|
29
35
|
*
|
|
30
36
|
* @example
|
|
31
37
|
* ```tsx
|
|
32
|
-
* // Simple usage
|
|
38
|
+
* // Simple usage - uses default navigation
|
|
33
39
|
* <Booking.Actions.Book />
|
|
34
40
|
*
|
|
35
|
-
* // With callbacks
|
|
41
|
+
* // With callbacks - navigateToCheckout has checkoutId captured
|
|
36
42
|
* <Booking.Actions.Book
|
|
37
|
-
* onCheckout={({
|
|
38
|
-
* onComplete={({ orderId }) =>
|
|
43
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
44
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
39
45
|
* onError={(error) => console.error(error.message)}
|
|
40
46
|
* />
|
|
41
47
|
*
|
|
42
48
|
* // With render function
|
|
43
49
|
* <Booking.Actions.Book
|
|
44
|
-
* onCheckout={({
|
|
45
|
-
* onComplete={({ orderId }) =>
|
|
50
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
51
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
46
52
|
* >
|
|
47
53
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
48
54
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -54,8 +60,8 @@ export interface BookProps {
|
|
|
54
60
|
* // With asChild
|
|
55
61
|
* <Booking.Actions.Book
|
|
56
62
|
* asChild
|
|
57
|
-
* onCheckout={({
|
|
58
|
-
* onComplete={({ orderId }) =>
|
|
63
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
64
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
59
65
|
* >
|
|
60
66
|
* <CustomBookButton />
|
|
61
67
|
* </Booking.Actions.Book>
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
8
|
import * as CoreBook from '../core/booking/Book.js';
|
|
9
|
-
import { navigateToCheckout } from '../../utils/navigation.js';
|
|
9
|
+
import { navigateToCheckout as navigateToCheckoutUtil } from '../../utils/navigation.js';
|
|
10
10
|
var TestIds;
|
|
11
11
|
(function (TestIds) {
|
|
12
12
|
TestIds["bookingActionBook"] = "booking-action-book";
|
|
@@ -16,20 +16,20 @@ var TestIds;
|
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```tsx
|
|
19
|
-
* // Simple usage
|
|
19
|
+
* // Simple usage - uses default navigation
|
|
20
20
|
* <Booking.Actions.Book />
|
|
21
21
|
*
|
|
22
|
-
* // With callbacks
|
|
22
|
+
* // With callbacks - navigateToCheckout has checkoutId captured
|
|
23
23
|
* <Booking.Actions.Book
|
|
24
|
-
* onCheckout={({
|
|
25
|
-
* onComplete={({ orderId }) =>
|
|
24
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
25
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
26
26
|
* onError={(error) => console.error(error.message)}
|
|
27
27
|
* />
|
|
28
28
|
*
|
|
29
29
|
* // With render function
|
|
30
30
|
* <Booking.Actions.Book
|
|
31
|
-
* onCheckout={({
|
|
32
|
-
* onComplete={({ orderId }) =>
|
|
31
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
32
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
33
33
|
* >
|
|
34
34
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
35
35
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -41,8 +41,8 @@ var TestIds;
|
|
|
41
41
|
* // With asChild
|
|
42
42
|
* <Booking.Actions.Book
|
|
43
43
|
* asChild
|
|
44
|
-
* onCheckout={({
|
|
45
|
-
* onComplete={({ orderId }) =>
|
|
44
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
45
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
46
46
|
* >
|
|
47
47
|
* <CustomBookButton />
|
|
48
48
|
* </Booking.Actions.Book>
|
|
@@ -51,14 +51,11 @@ var TestIds;
|
|
|
51
51
|
export const Book = React.forwardRef((props, ref) => {
|
|
52
52
|
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled, onCheckout, onComplete, onError, } = props;
|
|
53
53
|
return (_jsx(CoreBook.Book, { onCheckout: (result) => {
|
|
54
|
-
onCheckout?.({
|
|
55
|
-
|
|
56
|
-
onComplete?.({
|
|
57
|
-
...result,
|
|
58
|
-
navigateToThankYou: (orderId) => {
|
|
59
|
-
console.log('navigateToThankYou', orderId);
|
|
60
|
-
},
|
|
54
|
+
onCheckout?.({
|
|
55
|
+
navigateToCheckout: () => navigateToCheckoutUtil(result.checkoutId),
|
|
61
56
|
});
|
|
57
|
+
}, onComplete: (result) => {
|
|
58
|
+
onComplete?.({ orderId: result.orderId });
|
|
62
59
|
}, 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 }) })) }));
|
|
63
60
|
});
|
|
64
61
|
Book.displayName = 'Booking.Actions.Book';
|
|
@@ -44,10 +44,11 @@ export declare const Root: {
|
|
|
44
44
|
* // Simple usage
|
|
45
45
|
* <Booking.Actions.Book />
|
|
46
46
|
*
|
|
47
|
-
* // With callbacks
|
|
47
|
+
* // With callbacks - navigateToCheckout has checkoutId already captured
|
|
48
48
|
* <Booking.Actions.Book
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
50
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
51
|
+
* onError={(error) => toast.error(error.message)}
|
|
51
52
|
* />
|
|
52
53
|
* ```
|
|
53
54
|
*/
|
|
@@ -60,10 +60,11 @@ Root.displayName = 'Booking.Root';
|
|
|
60
60
|
* // Simple usage
|
|
61
61
|
* <Booking.Actions.Book />
|
|
62
62
|
*
|
|
63
|
-
* // With callbacks
|
|
63
|
+
* // With callbacks - navigateToCheckout has checkoutId already captured
|
|
64
64
|
* <Booking.Actions.Book
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
66
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
67
|
+
* onError={(error) => toast.error(error.message)}
|
|
67
68
|
* />
|
|
68
69
|
* ```
|
|
69
70
|
*/
|
|
@@ -32,11 +32,21 @@ export interface BookProps {
|
|
|
32
32
|
* Core Book component - provides book action data via render props.
|
|
33
33
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
34
34
|
*
|
|
35
|
+
* The onCheckout callback receives the checkoutId for internal use.
|
|
36
|
+
* The high-level component wraps this to provide a simpler API with
|
|
37
|
+
* navigateToCheckout() that has the checkoutId already captured.
|
|
38
|
+
*
|
|
35
39
|
* @example
|
|
36
40
|
* ```tsx
|
|
37
41
|
* <Book
|
|
38
|
-
* onCheckout={({ checkoutId }) =>
|
|
39
|
-
*
|
|
42
|
+
* onCheckout={({ checkoutId }) => {
|
|
43
|
+
* // Use checkoutId to navigate to checkout
|
|
44
|
+
* navigateToCheckout(checkoutId);
|
|
45
|
+
* }}
|
|
46
|
+
* onComplete={({ orderId }) => {
|
|
47
|
+
* // Use orderId to navigate to thank you page
|
|
48
|
+
* navigate(`/thank-you?orderId=${orderId}`);
|
|
49
|
+
* }}
|
|
40
50
|
* >
|
|
41
51
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
42
52
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -11,11 +11,21 @@ import { BookResultType, } from '../../../services/booking/book-action/types.js'
|
|
|
11
11
|
* Core Book component - provides book action data via render props.
|
|
12
12
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
13
13
|
*
|
|
14
|
+
* The onCheckout callback receives the checkoutId for internal use.
|
|
15
|
+
* The high-level component wraps this to provide a simpler API with
|
|
16
|
+
* navigateToCheckout() that has the checkoutId already captured.
|
|
17
|
+
*
|
|
14
18
|
* @example
|
|
15
19
|
* ```tsx
|
|
16
20
|
* <Book
|
|
17
|
-
* onCheckout={({ checkoutId }) =>
|
|
18
|
-
*
|
|
21
|
+
* onCheckout={({ checkoutId }) => {
|
|
22
|
+
* // Use checkoutId to navigate to checkout
|
|
23
|
+
* navigateToCheckout(checkoutId);
|
|
24
|
+
* }}
|
|
25
|
+
* onComplete={({ orderId }) => {
|
|
26
|
+
* // Use orderId to navigate to thank you page
|
|
27
|
+
* navigate(`/thank-you?orderId=${orderId}`);
|
|
28
|
+
* }}
|
|
19
29
|
* >
|
|
20
30
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
21
31
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -59,9 +59,7 @@ export interface DataProps {
|
|
|
59
59
|
discount: MoneyData | null;
|
|
60
60
|
payNow: MoneyData | null;
|
|
61
61
|
payLater: MoneyData | null;
|
|
62
|
-
|
|
63
|
-
hasPriceText: boolean;
|
|
64
|
-
isFree: boolean;
|
|
62
|
+
customPrice: string | null;
|
|
65
63
|
hasTax: boolean;
|
|
66
64
|
hasDiscount: boolean;
|
|
67
65
|
isDeposit: boolean;
|
|
@@ -93,10 +91,8 @@ export interface LineItem {
|
|
|
93
91
|
discount: MoneyData | null;
|
|
94
92
|
/** Deposit amount */
|
|
95
93
|
deposit: MoneyData | null;
|
|
96
|
-
/** Custom price text (for CUSTOM rate type) */
|
|
97
|
-
|
|
98
|
-
/** Whether this is a free service (NO_FEE rate type) */
|
|
99
|
-
isFree: boolean;
|
|
94
|
+
/** Custom price text (for CUSTOM rate type services) */
|
|
95
|
+
customPrice: string | null;
|
|
100
96
|
}
|
|
101
97
|
/**
|
|
102
98
|
* Props for LineItems render prop component
|
|
@@ -180,9 +176,7 @@ export interface LineItemInfoProps {
|
|
|
180
176
|
tax: MoneyData | null;
|
|
181
177
|
discount: MoneyData | null;
|
|
182
178
|
deposit: MoneyData | null;
|
|
183
|
-
|
|
184
|
-
hasPriceText: boolean;
|
|
185
|
-
isFree: boolean;
|
|
179
|
+
customPrice: string | null;
|
|
186
180
|
hasDiscount: boolean;
|
|
187
181
|
hasDeposit: boolean;
|
|
188
182
|
}) => React.ReactNode;
|
|
@@ -195,9 +189,9 @@ export interface LineItemInfoProps {
|
|
|
195
189
|
* @example
|
|
196
190
|
* ```tsx
|
|
197
191
|
* <LineItemInfo>
|
|
198
|
-
* {({ totalPrice,
|
|
192
|
+
* {({ totalPrice, customPrice, lineItem }) => (
|
|
199
193
|
* <div>
|
|
200
|
-
* {
|
|
194
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={totalPrice} />}
|
|
201
195
|
* </div>
|
|
202
196
|
* )}
|
|
203
197
|
* </LineItemInfo>
|
|
@@ -77,13 +77,10 @@ export function Data(props) {
|
|
|
77
77
|
const discount = convertMultiCurrencyPriceToMoneyData(priceSummary?.discount, currency);
|
|
78
78
|
const payNow = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payNow?.total, currency);
|
|
79
79
|
const payLater = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payLater?.total, currency);
|
|
80
|
-
// Get
|
|
80
|
+
// Get customPrice from first line item (single service mode)
|
|
81
81
|
const additionalInfoList = paymentDetails?.additionalInfo?.lineItems || [];
|
|
82
82
|
const firstLineItemInfo = additionalInfoList[0];
|
|
83
|
-
const
|
|
84
|
-
const hasPriceText = !!priceText;
|
|
85
|
-
// Use isFree from service (NO_FEE rate type) if available, otherwise fallback to amount check
|
|
86
|
-
const isFree = firstLineItemInfo?.isFree ?? false;
|
|
83
|
+
const customPrice = firstLineItemInfo?.customPrice || null;
|
|
87
84
|
// Computed flags
|
|
88
85
|
const hasTax = tax !== null && tax.amount > 0;
|
|
89
86
|
const hasDiscount = discount !== null && discount.amount > 0;
|
|
@@ -95,9 +92,7 @@ export function Data(props) {
|
|
|
95
92
|
discount,
|
|
96
93
|
payNow,
|
|
97
94
|
payLater,
|
|
98
|
-
|
|
99
|
-
hasPriceText,
|
|
100
|
-
isFree,
|
|
95
|
+
customPrice,
|
|
101
96
|
hasTax,
|
|
102
97
|
hasDiscount,
|
|
103
98
|
isDeposit,
|
|
@@ -145,8 +140,7 @@ export function LineItems(props) {
|
|
|
145
140
|
tax: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.taxDetails?.totalTax, currency),
|
|
146
141
|
discount: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.totalDiscount, currency),
|
|
147
142
|
deposit: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.depositAmount, currency),
|
|
148
|
-
|
|
149
|
-
isFree: additionalInfo?.isFree ?? false,
|
|
143
|
+
customPrice: additionalInfo?.customPrice || null,
|
|
150
144
|
};
|
|
151
145
|
});
|
|
152
146
|
const hasLineItems = lineItems.length > 0;
|
|
@@ -202,9 +196,9 @@ export function LineItemProvider(props) {
|
|
|
202
196
|
* @example
|
|
203
197
|
* ```tsx
|
|
204
198
|
* <LineItemInfo>
|
|
205
|
-
* {({ totalPrice,
|
|
199
|
+
* {({ totalPrice, customPrice, lineItem }) => (
|
|
206
200
|
* <div>
|
|
207
|
-
* {
|
|
201
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={totalPrice} />}
|
|
208
202
|
* </div>
|
|
209
203
|
* )}
|
|
210
204
|
* </LineItemInfo>
|
|
@@ -213,7 +207,6 @@ export function LineItemProvider(props) {
|
|
|
213
207
|
export function LineItemInfo(props) {
|
|
214
208
|
const { lineItem, currency } = useLineItemContext();
|
|
215
209
|
// Computed flags
|
|
216
|
-
const hasPriceText = !!lineItem.priceText;
|
|
217
210
|
const hasDiscount = lineItem.discount !== null && lineItem.discount.amount > 0;
|
|
218
211
|
const hasDeposit = lineItem.deposit !== null && lineItem.deposit.amount > 0;
|
|
219
212
|
return props.children({
|
|
@@ -224,9 +217,7 @@ export function LineItemInfo(props) {
|
|
|
224
217
|
tax: lineItem.tax,
|
|
225
218
|
discount: lineItem.discount,
|
|
226
219
|
deposit: lineItem.deposit,
|
|
227
|
-
|
|
228
|
-
hasPriceText,
|
|
229
|
-
isFree: lineItem.isFree,
|
|
220
|
+
customPrice: lineItem.customPrice,
|
|
230
221
|
hasDiscount,
|
|
231
222
|
hasDeposit,
|
|
232
223
|
});
|
|
@@ -63,15 +63,13 @@ export interface SubtotalProps {
|
|
|
63
63
|
asChild?: boolean;
|
|
64
64
|
children?: AsChildChildren<{
|
|
65
65
|
subtotal: MoneyData | null;
|
|
66
|
-
|
|
67
|
-
hasPriceText: boolean;
|
|
68
|
-
isFree: boolean;
|
|
66
|
+
customPrice: string | null;
|
|
69
67
|
}>;
|
|
70
68
|
className?: string;
|
|
71
69
|
}
|
|
72
70
|
/**
|
|
73
71
|
* Displays the payment subtotal.
|
|
74
|
-
* Always displays the monetary value
|
|
72
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
75
73
|
*
|
|
76
74
|
* @component
|
|
77
75
|
* @example
|
|
@@ -81,10 +79,9 @@ export interface SubtotalProps {
|
|
|
81
79
|
* @example
|
|
82
80
|
* ```tsx
|
|
83
81
|
* <Payment.Subtotal asChild>
|
|
84
|
-
* {({ subtotal,
|
|
82
|
+
* {({ subtotal, customPrice }) => (
|
|
85
83
|
* <div>
|
|
86
|
-
* <Money money={subtotal} />
|
|
87
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
84
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={subtotal} />}
|
|
88
85
|
* </div>
|
|
89
86
|
* )}
|
|
90
87
|
* </Payment.Subtotal>
|
|
@@ -140,15 +137,13 @@ export interface TotalProps {
|
|
|
140
137
|
asChild?: boolean;
|
|
141
138
|
children?: AsChildChildren<{
|
|
142
139
|
total: MoneyData | null;
|
|
143
|
-
|
|
144
|
-
hasPriceText: boolean;
|
|
145
|
-
isFree: boolean;
|
|
140
|
+
customPrice: string | null;
|
|
146
141
|
}>;
|
|
147
142
|
className?: string;
|
|
148
143
|
}
|
|
149
144
|
/**
|
|
150
145
|
* Displays the payment total.
|
|
151
|
-
* Always displays the monetary value
|
|
146
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
152
147
|
*
|
|
153
148
|
* @component
|
|
154
149
|
* @example
|
|
@@ -158,14 +153,9 @@ export interface TotalProps {
|
|
|
158
153
|
* @example
|
|
159
154
|
* ```tsx
|
|
160
155
|
* <Payment.Total asChild>
|
|
161
|
-
* {({ total,
|
|
156
|
+
* {({ total, customPrice }) => (
|
|
162
157
|
* <div>
|
|
163
|
-
* {
|
|
164
|
-
* <span className="text-green-500">Free</span>
|
|
165
|
-
* ) : (
|
|
166
|
-
* <Money money={total} />
|
|
167
|
-
* )}
|
|
168
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
158
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={total} />}
|
|
169
159
|
* </div>
|
|
170
160
|
* )}
|
|
171
161
|
* </Payment.Total>
|
|
@@ -302,15 +292,13 @@ export interface LineItemTotalProps {
|
|
|
302
292
|
asChild?: boolean;
|
|
303
293
|
children?: AsChildChildren<{
|
|
304
294
|
totalPrice: MoneyData | null;
|
|
305
|
-
|
|
306
|
-
hasPriceText: boolean;
|
|
307
|
-
isFree: boolean;
|
|
295
|
+
customPrice: string | null;
|
|
308
296
|
}>;
|
|
309
297
|
className?: string;
|
|
310
298
|
}
|
|
311
299
|
/**
|
|
312
300
|
* Displays the line item total (total price after tax).
|
|
313
|
-
* Shows
|
|
301
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows totalPrice.
|
|
314
302
|
*
|
|
315
303
|
* @component
|
|
316
304
|
* @example
|
|
@@ -326,15 +314,13 @@ export interface LineItemSubtotalProps {
|
|
|
326
314
|
asChild?: boolean;
|
|
327
315
|
children?: AsChildChildren<{
|
|
328
316
|
subtotal: MoneyData | null;
|
|
329
|
-
|
|
330
|
-
hasPriceText: boolean;
|
|
331
|
-
isFree: boolean;
|
|
317
|
+
customPrice: string | null;
|
|
332
318
|
}>;
|
|
333
319
|
className?: string;
|
|
334
320
|
}
|
|
335
321
|
/**
|
|
336
322
|
* Displays the line item subtotal (full price before discounts).
|
|
337
|
-
* Shows
|
|
323
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows subtotal.
|
|
338
324
|
*
|
|
339
325
|
* @component
|
|
340
326
|
* @example
|
|
@@ -65,7 +65,7 @@ export const Root = React.forwardRef((props, ref) => {
|
|
|
65
65
|
Root.displayName = 'Payment.Root';
|
|
66
66
|
/**
|
|
67
67
|
* Displays the payment subtotal.
|
|
68
|
-
* Always displays the monetary value
|
|
68
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
69
69
|
*
|
|
70
70
|
* @component
|
|
71
71
|
* @example
|
|
@@ -75,10 +75,9 @@ Root.displayName = 'Payment.Root';
|
|
|
75
75
|
* @example
|
|
76
76
|
* ```tsx
|
|
77
77
|
* <Payment.Subtotal asChild>
|
|
78
|
-
* {({ subtotal,
|
|
78
|
+
* {({ subtotal, customPrice }) => (
|
|
79
79
|
* <div>
|
|
80
|
-
* <Money money={subtotal} />
|
|
81
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
80
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={subtotal} />}
|
|
82
81
|
* </div>
|
|
83
82
|
* )}
|
|
84
83
|
* </Payment.Subtotal>
|
|
@@ -86,7 +85,10 @@ Root.displayName = 'Payment.Root';
|
|
|
86
85
|
*/
|
|
87
86
|
export const Subtotal = React.forwardRef((props, ref) => {
|
|
88
87
|
const { asChild, children, className } = props;
|
|
89
|
-
return (_jsx(CorePayment.Data, { children: ({ subtotal,
|
|
88
|
+
return (_jsx(CorePayment.Data, { children: ({ subtotal, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentSubtotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: {
|
|
89
|
+
subtotal,
|
|
90
|
+
customPrice,
|
|
91
|
+
}, children: subtotal && _jsx(Money, { money: subtotal }) })) }));
|
|
90
92
|
});
|
|
91
93
|
Subtotal.displayName = 'Payment.Subtotal';
|
|
92
94
|
/**
|
|
@@ -121,7 +123,7 @@ export const Discount = React.forwardRef((props, ref) => {
|
|
|
121
123
|
Discount.displayName = 'Payment.Discount';
|
|
122
124
|
/**
|
|
123
125
|
* Displays the payment total.
|
|
124
|
-
* Always displays the monetary value
|
|
126
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
125
127
|
*
|
|
126
128
|
* @component
|
|
127
129
|
* @example
|
|
@@ -131,14 +133,9 @@ Discount.displayName = 'Payment.Discount';
|
|
|
131
133
|
* @example
|
|
132
134
|
* ```tsx
|
|
133
135
|
* <Payment.Total asChild>
|
|
134
|
-
* {({ total,
|
|
136
|
+
* {({ total, customPrice }) => (
|
|
135
137
|
* <div>
|
|
136
|
-
* {
|
|
137
|
-
* <span className="text-green-500">Free</span>
|
|
138
|
-
* ) : (
|
|
139
|
-
* <Money money={total} />
|
|
140
|
-
* )}
|
|
141
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
138
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={total} />}
|
|
142
139
|
* </div>
|
|
143
140
|
* )}
|
|
144
141
|
* </Payment.Total>
|
|
@@ -146,7 +143,10 @@ Discount.displayName = 'Payment.Discount';
|
|
|
146
143
|
*/
|
|
147
144
|
export const Total = React.forwardRef((props, ref) => {
|
|
148
145
|
const { asChild, children, className } = props;
|
|
149
|
-
return (_jsx(CorePayment.Data, { children: ({ total,
|
|
146
|
+
return (_jsx(CorePayment.Data, { children: ({ total, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: {
|
|
147
|
+
total,
|
|
148
|
+
customPrice,
|
|
149
|
+
}, children: total && _jsx(Money, { money: total }) })) }));
|
|
150
150
|
});
|
|
151
151
|
Total.displayName = 'Payment.Total';
|
|
152
152
|
/**
|
|
@@ -261,7 +261,7 @@ export const LineItemRepeater = React.forwardRef((props, ref) => {
|
|
|
261
261
|
LineItemRepeater.displayName = 'Payment.LineItemRepeater';
|
|
262
262
|
/**
|
|
263
263
|
* Displays the line item total (total price after tax).
|
|
264
|
-
* Shows
|
|
264
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows totalPrice.
|
|
265
265
|
*
|
|
266
266
|
* @component
|
|
267
267
|
* @example
|
|
@@ -271,12 +271,12 @@ LineItemRepeater.displayName = 'Payment.LineItemRepeater';
|
|
|
271
271
|
*/
|
|
272
272
|
export const LineItemTotal = React.forwardRef((props, ref) => {
|
|
273
273
|
const { asChild, children, className } = props;
|
|
274
|
-
return (_jsx(CorePayment.LineItemInfo, { children: ({ totalPrice,
|
|
274
|
+
return (_jsx(CorePayment.LineItemInfo, { children: ({ totalPrice, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemTotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: { totalPrice, customPrice }, children: customPrice ? (_jsx("span", { children: customPrice })) : (totalPrice && _jsx(Money, { money: totalPrice })) })) }));
|
|
275
275
|
});
|
|
276
276
|
LineItemTotal.displayName = 'Payment.LineItemTotal';
|
|
277
277
|
/**
|
|
278
278
|
* Displays the line item subtotal (full price before discounts).
|
|
279
|
-
* Shows
|
|
279
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows subtotal.
|
|
280
280
|
*
|
|
281
281
|
* @component
|
|
282
282
|
* @example
|
|
@@ -286,7 +286,7 @@ LineItemTotal.displayName = 'Payment.LineItemTotal';
|
|
|
286
286
|
*/
|
|
287
287
|
export const LineItemSubtotal = React.forwardRef((props, ref) => {
|
|
288
288
|
const { asChild, children, className } = props;
|
|
289
|
-
return (_jsx(CorePayment.LineItemInfo, { children: ({ subtotal,
|
|
289
|
+
return (_jsx(CorePayment.LineItemInfo, { children: ({ subtotal, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemSubtotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: { subtotal, customPrice }, children: customPrice ? (_jsx("span", { children: customPrice })) : (subtotal && _jsx(Money, { money: subtotal })) })) }));
|
|
290
290
|
});
|
|
291
291
|
LineItemSubtotal.displayName = 'Payment.LineItemSubtotal';
|
|
292
292
|
/**
|
|
@@ -44,7 +44,7 @@ export interface BookChildProps {
|
|
|
44
44
|
isLoading: boolean;
|
|
45
45
|
error: BookingError | null;
|
|
46
46
|
canBook: boolean;
|
|
47
|
-
onClick: () => void
|
|
47
|
+
onClick: () => Promise<void>;
|
|
48
48
|
disabled: boolean;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -57,9 +57,17 @@ export interface BookProps {
|
|
|
57
57
|
label?: string;
|
|
58
58
|
loadingState?: string;
|
|
59
59
|
disabled?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Called when checkout is required for payment.
|
|
62
|
+
* The navigateToCheckout function has the checkoutId captured - just call it.
|
|
63
|
+
*/
|
|
60
64
|
onCheckout?: (result: {
|
|
61
|
-
|
|
65
|
+
navigateToCheckout: () => void;
|
|
62
66
|
}) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Called when booking completes without requiring checkout (free/offline services).
|
|
69
|
+
* Receives the orderId which can be used to display confirmation or navigate to a thank you page.
|
|
70
|
+
*/
|
|
63
71
|
onComplete?: (result: {
|
|
64
72
|
orderId: string;
|
|
65
73
|
}) => void;
|
|
@@ -15,10 +15,8 @@ export type PaymentOption = 'FULL_PAYMENT_ONLINE' | 'FULL_PAYMENT_OFFLINE' | 'DE
|
|
|
15
15
|
export interface LineItemAdditionalInfo {
|
|
16
16
|
/** Line item ID to match with SDK's calculatedLineItems */
|
|
17
17
|
lineItemId: string;
|
|
18
|
-
/** Custom price
|
|
19
|
-
|
|
20
|
-
/** Whether this is a free service (NO_FEE rate type) */
|
|
21
|
-
isFree?: boolean;
|
|
18
|
+
/** Custom price (for CUSTOM rate type services) */
|
|
19
|
+
customPrice?: string;
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
22
|
* Payment calculation result with SDK response and additional computed info
|
|
@@ -20,11 +20,10 @@ const BOOKINGS_APP_DEF_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
function getAdditionalInfo(service, lineItemId) {
|
|
22
22
|
const payment = service.payment;
|
|
23
|
-
const
|
|
23
|
+
const customPrice = payment?.rateType === RateType.CUSTOM && payment.custom?.description
|
|
24
24
|
? payment.custom.description
|
|
25
25
|
: undefined;
|
|
26
|
-
|
|
27
|
-
return { lineItemId, priceText, isFree };
|
|
26
|
+
return { lineItemId, customPrice };
|
|
28
27
|
}
|
|
29
28
|
function getPaymentOption(service, hasDeposit) {
|
|
30
29
|
const options = service.payment?.options;
|
|
@@ -5,7 +5,8 @@ export async function navigateToCheckout(checkoutId) {
|
|
|
5
5
|
const { redirectSession } = await redirects.createRedirectSession({
|
|
6
6
|
ecomCheckout: { checkoutId },
|
|
7
7
|
callbacks: {
|
|
8
|
-
postFlowUrl: window.location.
|
|
8
|
+
postFlowUrl: `${window.location.host}/booking/services`,
|
|
9
|
+
bookingsServiceListUrl: `${window.location.host}/booking/services`,
|
|
9
10
|
},
|
|
10
11
|
});
|
|
11
12
|
if (redirectSession?.fullUrl) {
|
|
@@ -14,13 +14,19 @@ export interface BookProps {
|
|
|
14
14
|
label?: string;
|
|
15
15
|
loadingState?: string;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Called when checkout is required for payment.
|
|
19
|
+
* The navigateToCheckout function already has the checkoutId captured - just call it to navigate.
|
|
20
|
+
*/
|
|
17
21
|
onCheckout?: (result: {
|
|
18
|
-
|
|
19
|
-
navigateToCheckout: (checkoutId: string) => void;
|
|
22
|
+
navigateToCheckout: () => void;
|
|
20
23
|
}) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Called when booking completes without requiring checkout (free/offline services).
|
|
26
|
+
* Receives the orderId which can be used to display confirmation or navigate to a thank you page.
|
|
27
|
+
*/
|
|
21
28
|
onComplete?: (result: {
|
|
22
29
|
orderId: string;
|
|
23
|
-
navigateToThankYou: (orderId: string) => void;
|
|
24
30
|
}) => void;
|
|
25
31
|
onError?: (error: BookingError) => void;
|
|
26
32
|
}
|
|
@@ -29,20 +35,20 @@ export interface BookProps {
|
|
|
29
35
|
*
|
|
30
36
|
* @example
|
|
31
37
|
* ```tsx
|
|
32
|
-
* // Simple usage
|
|
38
|
+
* // Simple usage - uses default navigation
|
|
33
39
|
* <Booking.Actions.Book />
|
|
34
40
|
*
|
|
35
|
-
* // With callbacks
|
|
41
|
+
* // With callbacks - navigateToCheckout has checkoutId captured
|
|
36
42
|
* <Booking.Actions.Book
|
|
37
|
-
* onCheckout={({
|
|
38
|
-
* onComplete={({ orderId }) =>
|
|
43
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
44
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
39
45
|
* onError={(error) => console.error(error.message)}
|
|
40
46
|
* />
|
|
41
47
|
*
|
|
42
48
|
* // With render function
|
|
43
49
|
* <Booking.Actions.Book
|
|
44
|
-
* onCheckout={({
|
|
45
|
-
* onComplete={({ orderId }) =>
|
|
50
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
51
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
46
52
|
* >
|
|
47
53
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
48
54
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -54,8 +60,8 @@ export interface BookProps {
|
|
|
54
60
|
* // With asChild
|
|
55
61
|
* <Booking.Actions.Book
|
|
56
62
|
* asChild
|
|
57
|
-
* onCheckout={({
|
|
58
|
-
* onComplete={({ orderId }) =>
|
|
63
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
64
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
59
65
|
* >
|
|
60
66
|
* <CustomBookButton />
|
|
61
67
|
* </Booking.Actions.Book>
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
8
|
import * as CoreBook from '../core/booking/Book.js';
|
|
9
|
-
import { navigateToCheckout } from '../../utils/navigation.js';
|
|
9
|
+
import { navigateToCheckout as navigateToCheckoutUtil } from '../../utils/navigation.js';
|
|
10
10
|
var TestIds;
|
|
11
11
|
(function (TestIds) {
|
|
12
12
|
TestIds["bookingActionBook"] = "booking-action-book";
|
|
@@ -16,20 +16,20 @@ var TestIds;
|
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```tsx
|
|
19
|
-
* // Simple usage
|
|
19
|
+
* // Simple usage - uses default navigation
|
|
20
20
|
* <Booking.Actions.Book />
|
|
21
21
|
*
|
|
22
|
-
* // With callbacks
|
|
22
|
+
* // With callbacks - navigateToCheckout has checkoutId captured
|
|
23
23
|
* <Booking.Actions.Book
|
|
24
|
-
* onCheckout={({
|
|
25
|
-
* onComplete={({ orderId }) =>
|
|
24
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
25
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
26
26
|
* onError={(error) => console.error(error.message)}
|
|
27
27
|
* />
|
|
28
28
|
*
|
|
29
29
|
* // With render function
|
|
30
30
|
* <Booking.Actions.Book
|
|
31
|
-
* onCheckout={({
|
|
32
|
-
* onComplete={({ orderId }) =>
|
|
31
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
32
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
33
33
|
* >
|
|
34
34
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
35
35
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -41,8 +41,8 @@ var TestIds;
|
|
|
41
41
|
* // With asChild
|
|
42
42
|
* <Booking.Actions.Book
|
|
43
43
|
* asChild
|
|
44
|
-
* onCheckout={({
|
|
45
|
-
* onComplete={({ orderId }) =>
|
|
44
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
45
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
46
46
|
* >
|
|
47
47
|
* <CustomBookButton />
|
|
48
48
|
* </Booking.Actions.Book>
|
|
@@ -51,14 +51,11 @@ var TestIds;
|
|
|
51
51
|
export const Book = React.forwardRef((props, ref) => {
|
|
52
52
|
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled, onCheckout, onComplete, onError, } = props;
|
|
53
53
|
return (_jsx(CoreBook.Book, { onCheckout: (result) => {
|
|
54
|
-
onCheckout?.({
|
|
55
|
-
|
|
56
|
-
onComplete?.({
|
|
57
|
-
...result,
|
|
58
|
-
navigateToThankYou: (orderId) => {
|
|
59
|
-
console.log('navigateToThankYou', orderId);
|
|
60
|
-
},
|
|
54
|
+
onCheckout?.({
|
|
55
|
+
navigateToCheckout: () => navigateToCheckoutUtil(result.checkoutId),
|
|
61
56
|
});
|
|
57
|
+
}, onComplete: (result) => {
|
|
58
|
+
onComplete?.({ orderId: result.orderId });
|
|
62
59
|
}, 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 }) })) }));
|
|
63
60
|
});
|
|
64
61
|
Book.displayName = 'Booking.Actions.Book';
|
|
@@ -44,10 +44,11 @@ export declare const Root: {
|
|
|
44
44
|
* // Simple usage
|
|
45
45
|
* <Booking.Actions.Book />
|
|
46
46
|
*
|
|
47
|
-
* // With callbacks
|
|
47
|
+
* // With callbacks - navigateToCheckout has checkoutId already captured
|
|
48
48
|
* <Booking.Actions.Book
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
50
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
51
|
+
* onError={(error) => toast.error(error.message)}
|
|
51
52
|
* />
|
|
52
53
|
* ```
|
|
53
54
|
*/
|
|
@@ -60,10 +60,11 @@ Root.displayName = 'Booking.Root';
|
|
|
60
60
|
* // Simple usage
|
|
61
61
|
* <Booking.Actions.Book />
|
|
62
62
|
*
|
|
63
|
-
* // With callbacks
|
|
63
|
+
* // With callbacks - navigateToCheckout has checkoutId already captured
|
|
64
64
|
* <Booking.Actions.Book
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* onCheckout={({ navigateToCheckout }) => navigateToCheckout()}
|
|
66
|
+
* onComplete={({ orderId }) => navigate(`/thank-you?orderId=${orderId}`)}
|
|
67
|
+
* onError={(error) => toast.error(error.message)}
|
|
67
68
|
* />
|
|
68
69
|
* ```
|
|
69
70
|
*/
|
|
@@ -32,11 +32,21 @@ export interface BookProps {
|
|
|
32
32
|
* Core Book component - provides book action data via render props.
|
|
33
33
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
34
34
|
*
|
|
35
|
+
* The onCheckout callback receives the checkoutId for internal use.
|
|
36
|
+
* The high-level component wraps this to provide a simpler API with
|
|
37
|
+
* navigateToCheckout() that has the checkoutId already captured.
|
|
38
|
+
*
|
|
35
39
|
* @example
|
|
36
40
|
* ```tsx
|
|
37
41
|
* <Book
|
|
38
|
-
* onCheckout={({ checkoutId }) =>
|
|
39
|
-
*
|
|
42
|
+
* onCheckout={({ checkoutId }) => {
|
|
43
|
+
* // Use checkoutId to navigate to checkout
|
|
44
|
+
* navigateToCheckout(checkoutId);
|
|
45
|
+
* }}
|
|
46
|
+
* onComplete={({ orderId }) => {
|
|
47
|
+
* // Use orderId to navigate to thank you page
|
|
48
|
+
* navigate(`/thank-you?orderId=${orderId}`);
|
|
49
|
+
* }}
|
|
40
50
|
* >
|
|
41
51
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
42
52
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -11,11 +11,21 @@ import { BookResultType, } from '../../../services/booking/book-action/types.js'
|
|
|
11
11
|
* Core Book component - provides book action data via render props.
|
|
12
12
|
* Used internally by higher-level Booking.Actions.Book component.
|
|
13
13
|
*
|
|
14
|
+
* The onCheckout callback receives the checkoutId for internal use.
|
|
15
|
+
* The high-level component wraps this to provide a simpler API with
|
|
16
|
+
* navigateToCheckout() that has the checkoutId already captured.
|
|
17
|
+
*
|
|
14
18
|
* @example
|
|
15
19
|
* ```tsx
|
|
16
20
|
* <Book
|
|
17
|
-
* onCheckout={({ checkoutId }) =>
|
|
18
|
-
*
|
|
21
|
+
* onCheckout={({ checkoutId }) => {
|
|
22
|
+
* // Use checkoutId to navigate to checkout
|
|
23
|
+
* navigateToCheckout(checkoutId);
|
|
24
|
+
* }}
|
|
25
|
+
* onComplete={({ orderId }) => {
|
|
26
|
+
* // Use orderId to navigate to thank you page
|
|
27
|
+
* navigate(`/thank-you?orderId=${orderId}`);
|
|
28
|
+
* }}
|
|
19
29
|
* >
|
|
20
30
|
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
21
31
|
* <button onClick={onClick} disabled={disabled}>
|
|
@@ -59,9 +59,7 @@ export interface DataProps {
|
|
|
59
59
|
discount: MoneyData | null;
|
|
60
60
|
payNow: MoneyData | null;
|
|
61
61
|
payLater: MoneyData | null;
|
|
62
|
-
|
|
63
|
-
hasPriceText: boolean;
|
|
64
|
-
isFree: boolean;
|
|
62
|
+
customPrice: string | null;
|
|
65
63
|
hasTax: boolean;
|
|
66
64
|
hasDiscount: boolean;
|
|
67
65
|
isDeposit: boolean;
|
|
@@ -93,10 +91,8 @@ export interface LineItem {
|
|
|
93
91
|
discount: MoneyData | null;
|
|
94
92
|
/** Deposit amount */
|
|
95
93
|
deposit: MoneyData | null;
|
|
96
|
-
/** Custom price text (for CUSTOM rate type) */
|
|
97
|
-
|
|
98
|
-
/** Whether this is a free service (NO_FEE rate type) */
|
|
99
|
-
isFree: boolean;
|
|
94
|
+
/** Custom price text (for CUSTOM rate type services) */
|
|
95
|
+
customPrice: string | null;
|
|
100
96
|
}
|
|
101
97
|
/**
|
|
102
98
|
* Props for LineItems render prop component
|
|
@@ -180,9 +176,7 @@ export interface LineItemInfoProps {
|
|
|
180
176
|
tax: MoneyData | null;
|
|
181
177
|
discount: MoneyData | null;
|
|
182
178
|
deposit: MoneyData | null;
|
|
183
|
-
|
|
184
|
-
hasPriceText: boolean;
|
|
185
|
-
isFree: boolean;
|
|
179
|
+
customPrice: string | null;
|
|
186
180
|
hasDiscount: boolean;
|
|
187
181
|
hasDeposit: boolean;
|
|
188
182
|
}) => React.ReactNode;
|
|
@@ -195,9 +189,9 @@ export interface LineItemInfoProps {
|
|
|
195
189
|
* @example
|
|
196
190
|
* ```tsx
|
|
197
191
|
* <LineItemInfo>
|
|
198
|
-
* {({ totalPrice,
|
|
192
|
+
* {({ totalPrice, customPrice, lineItem }) => (
|
|
199
193
|
* <div>
|
|
200
|
-
* {
|
|
194
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={totalPrice} />}
|
|
201
195
|
* </div>
|
|
202
196
|
* )}
|
|
203
197
|
* </LineItemInfo>
|
|
@@ -77,13 +77,10 @@ export function Data(props) {
|
|
|
77
77
|
const discount = convertMultiCurrencyPriceToMoneyData(priceSummary?.discount, currency);
|
|
78
78
|
const payNow = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payNow?.total, currency);
|
|
79
79
|
const payLater = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payLater?.total, currency);
|
|
80
|
-
// Get
|
|
80
|
+
// Get customPrice from first line item (single service mode)
|
|
81
81
|
const additionalInfoList = paymentDetails?.additionalInfo?.lineItems || [];
|
|
82
82
|
const firstLineItemInfo = additionalInfoList[0];
|
|
83
|
-
const
|
|
84
|
-
const hasPriceText = !!priceText;
|
|
85
|
-
// Use isFree from service (NO_FEE rate type) if available, otherwise fallback to amount check
|
|
86
|
-
const isFree = firstLineItemInfo?.isFree ?? false;
|
|
83
|
+
const customPrice = firstLineItemInfo?.customPrice || null;
|
|
87
84
|
// Computed flags
|
|
88
85
|
const hasTax = tax !== null && tax.amount > 0;
|
|
89
86
|
const hasDiscount = discount !== null && discount.amount > 0;
|
|
@@ -95,9 +92,7 @@ export function Data(props) {
|
|
|
95
92
|
discount,
|
|
96
93
|
payNow,
|
|
97
94
|
payLater,
|
|
98
|
-
|
|
99
|
-
hasPriceText,
|
|
100
|
-
isFree,
|
|
95
|
+
customPrice,
|
|
101
96
|
hasTax,
|
|
102
97
|
hasDiscount,
|
|
103
98
|
isDeposit,
|
|
@@ -145,8 +140,7 @@ export function LineItems(props) {
|
|
|
145
140
|
tax: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.taxDetails?.totalTax, currency),
|
|
146
141
|
discount: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.totalDiscount, currency),
|
|
147
142
|
deposit: convertMultiCurrencyPriceToMoneyData(pricesBreakdown?.depositAmount, currency),
|
|
148
|
-
|
|
149
|
-
isFree: additionalInfo?.isFree ?? false,
|
|
143
|
+
customPrice: additionalInfo?.customPrice || null,
|
|
150
144
|
};
|
|
151
145
|
});
|
|
152
146
|
const hasLineItems = lineItems.length > 0;
|
|
@@ -202,9 +196,9 @@ export function LineItemProvider(props) {
|
|
|
202
196
|
* @example
|
|
203
197
|
* ```tsx
|
|
204
198
|
* <LineItemInfo>
|
|
205
|
-
* {({ totalPrice,
|
|
199
|
+
* {({ totalPrice, customPrice, lineItem }) => (
|
|
206
200
|
* <div>
|
|
207
|
-
* {
|
|
201
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={totalPrice} />}
|
|
208
202
|
* </div>
|
|
209
203
|
* )}
|
|
210
204
|
* </LineItemInfo>
|
|
@@ -213,7 +207,6 @@ export function LineItemProvider(props) {
|
|
|
213
207
|
export function LineItemInfo(props) {
|
|
214
208
|
const { lineItem, currency } = useLineItemContext();
|
|
215
209
|
// Computed flags
|
|
216
|
-
const hasPriceText = !!lineItem.priceText;
|
|
217
210
|
const hasDiscount = lineItem.discount !== null && lineItem.discount.amount > 0;
|
|
218
211
|
const hasDeposit = lineItem.deposit !== null && lineItem.deposit.amount > 0;
|
|
219
212
|
return props.children({
|
|
@@ -224,9 +217,7 @@ export function LineItemInfo(props) {
|
|
|
224
217
|
tax: lineItem.tax,
|
|
225
218
|
discount: lineItem.discount,
|
|
226
219
|
deposit: lineItem.deposit,
|
|
227
|
-
|
|
228
|
-
hasPriceText,
|
|
229
|
-
isFree: lineItem.isFree,
|
|
220
|
+
customPrice: lineItem.customPrice,
|
|
230
221
|
hasDiscount,
|
|
231
222
|
hasDeposit,
|
|
232
223
|
});
|
|
@@ -63,15 +63,13 @@ export interface SubtotalProps {
|
|
|
63
63
|
asChild?: boolean;
|
|
64
64
|
children?: AsChildChildren<{
|
|
65
65
|
subtotal: MoneyData | null;
|
|
66
|
-
|
|
67
|
-
hasPriceText: boolean;
|
|
68
|
-
isFree: boolean;
|
|
66
|
+
customPrice: string | null;
|
|
69
67
|
}>;
|
|
70
68
|
className?: string;
|
|
71
69
|
}
|
|
72
70
|
/**
|
|
73
71
|
* Displays the payment subtotal.
|
|
74
|
-
* Always displays the monetary value
|
|
72
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
75
73
|
*
|
|
76
74
|
* @component
|
|
77
75
|
* @example
|
|
@@ -81,10 +79,9 @@ export interface SubtotalProps {
|
|
|
81
79
|
* @example
|
|
82
80
|
* ```tsx
|
|
83
81
|
* <Payment.Subtotal asChild>
|
|
84
|
-
* {({ subtotal,
|
|
82
|
+
* {({ subtotal, customPrice }) => (
|
|
85
83
|
* <div>
|
|
86
|
-
* <Money money={subtotal} />
|
|
87
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
84
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={subtotal} />}
|
|
88
85
|
* </div>
|
|
89
86
|
* )}
|
|
90
87
|
* </Payment.Subtotal>
|
|
@@ -140,15 +137,13 @@ export interface TotalProps {
|
|
|
140
137
|
asChild?: boolean;
|
|
141
138
|
children?: AsChildChildren<{
|
|
142
139
|
total: MoneyData | null;
|
|
143
|
-
|
|
144
|
-
hasPriceText: boolean;
|
|
145
|
-
isFree: boolean;
|
|
140
|
+
customPrice: string | null;
|
|
146
141
|
}>;
|
|
147
142
|
className?: string;
|
|
148
143
|
}
|
|
149
144
|
/**
|
|
150
145
|
* Displays the payment total.
|
|
151
|
-
* Always displays the monetary value
|
|
146
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
152
147
|
*
|
|
153
148
|
* @component
|
|
154
149
|
* @example
|
|
@@ -158,14 +153,9 @@ export interface TotalProps {
|
|
|
158
153
|
* @example
|
|
159
154
|
* ```tsx
|
|
160
155
|
* <Payment.Total asChild>
|
|
161
|
-
* {({ total,
|
|
156
|
+
* {({ total, customPrice }) => (
|
|
162
157
|
* <div>
|
|
163
|
-
* {
|
|
164
|
-
* <span className="text-green-500">Free</span>
|
|
165
|
-
* ) : (
|
|
166
|
-
* <Money money={total} />
|
|
167
|
-
* )}
|
|
168
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
158
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={total} />}
|
|
169
159
|
* </div>
|
|
170
160
|
* )}
|
|
171
161
|
* </Payment.Total>
|
|
@@ -302,15 +292,13 @@ export interface LineItemTotalProps {
|
|
|
302
292
|
asChild?: boolean;
|
|
303
293
|
children?: AsChildChildren<{
|
|
304
294
|
totalPrice: MoneyData | null;
|
|
305
|
-
|
|
306
|
-
hasPriceText: boolean;
|
|
307
|
-
isFree: boolean;
|
|
295
|
+
customPrice: string | null;
|
|
308
296
|
}>;
|
|
309
297
|
className?: string;
|
|
310
298
|
}
|
|
311
299
|
/**
|
|
312
300
|
* Displays the line item total (total price after tax).
|
|
313
|
-
* Shows
|
|
301
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows totalPrice.
|
|
314
302
|
*
|
|
315
303
|
* @component
|
|
316
304
|
* @example
|
|
@@ -326,15 +314,13 @@ export interface LineItemSubtotalProps {
|
|
|
326
314
|
asChild?: boolean;
|
|
327
315
|
children?: AsChildChildren<{
|
|
328
316
|
subtotal: MoneyData | null;
|
|
329
|
-
|
|
330
|
-
hasPriceText: boolean;
|
|
331
|
-
isFree: boolean;
|
|
317
|
+
customPrice: string | null;
|
|
332
318
|
}>;
|
|
333
319
|
className?: string;
|
|
334
320
|
}
|
|
335
321
|
/**
|
|
336
322
|
* Displays the line item subtotal (full price before discounts).
|
|
337
|
-
* Shows
|
|
323
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows subtotal.
|
|
338
324
|
*
|
|
339
325
|
* @component
|
|
340
326
|
* @example
|
|
@@ -65,7 +65,7 @@ export const Root = React.forwardRef((props, ref) => {
|
|
|
65
65
|
Root.displayName = 'Payment.Root';
|
|
66
66
|
/**
|
|
67
67
|
* Displays the payment subtotal.
|
|
68
|
-
* Always displays the monetary value
|
|
68
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
69
69
|
*
|
|
70
70
|
* @component
|
|
71
71
|
* @example
|
|
@@ -75,10 +75,9 @@ Root.displayName = 'Payment.Root';
|
|
|
75
75
|
* @example
|
|
76
76
|
* ```tsx
|
|
77
77
|
* <Payment.Subtotal asChild>
|
|
78
|
-
* {({ subtotal,
|
|
78
|
+
* {({ subtotal, customPrice }) => (
|
|
79
79
|
* <div>
|
|
80
|
-
* <Money money={subtotal} />
|
|
81
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
80
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={subtotal} />}
|
|
82
81
|
* </div>
|
|
83
82
|
* )}
|
|
84
83
|
* </Payment.Subtotal>
|
|
@@ -86,7 +85,10 @@ Root.displayName = 'Payment.Root';
|
|
|
86
85
|
*/
|
|
87
86
|
export const Subtotal = React.forwardRef((props, ref) => {
|
|
88
87
|
const { asChild, children, className } = props;
|
|
89
|
-
return (_jsx(CorePayment.Data, { children: ({ subtotal,
|
|
88
|
+
return (_jsx(CorePayment.Data, { children: ({ subtotal, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentSubtotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: {
|
|
89
|
+
subtotal,
|
|
90
|
+
customPrice,
|
|
91
|
+
}, children: subtotal && _jsx(Money, { money: subtotal }) })) }));
|
|
90
92
|
});
|
|
91
93
|
Subtotal.displayName = 'Payment.Subtotal';
|
|
92
94
|
/**
|
|
@@ -121,7 +123,7 @@ export const Discount = React.forwardRef((props, ref) => {
|
|
|
121
123
|
Discount.displayName = 'Payment.Discount';
|
|
122
124
|
/**
|
|
123
125
|
* Displays the payment total.
|
|
124
|
-
* Always displays the monetary value
|
|
126
|
+
* Always displays the monetary value. Exposes customPrice for custom rendering.
|
|
125
127
|
*
|
|
126
128
|
* @component
|
|
127
129
|
* @example
|
|
@@ -131,14 +133,9 @@ Discount.displayName = 'Payment.Discount';
|
|
|
131
133
|
* @example
|
|
132
134
|
* ```tsx
|
|
133
135
|
* <Payment.Total asChild>
|
|
134
|
-
* {({ total,
|
|
136
|
+
* {({ total, customPrice }) => (
|
|
135
137
|
* <div>
|
|
136
|
-
* {
|
|
137
|
-
* <span className="text-green-500">Free</span>
|
|
138
|
-
* ) : (
|
|
139
|
-
* <Money money={total} />
|
|
140
|
-
* )}
|
|
141
|
-
* {hasPriceText && <span className="text-sm">({priceText})</span>}
|
|
138
|
+
* {customPrice ? <span>{customPrice}</span> : <Money money={total} />}
|
|
142
139
|
* </div>
|
|
143
140
|
* )}
|
|
144
141
|
* </Payment.Total>
|
|
@@ -146,7 +143,10 @@ Discount.displayName = 'Payment.Discount';
|
|
|
146
143
|
*/
|
|
147
144
|
export const Total = React.forwardRef((props, ref) => {
|
|
148
145
|
const { asChild, children, className } = props;
|
|
149
|
-
return (_jsx(CorePayment.Data, { children: ({ total,
|
|
146
|
+
return (_jsx(CorePayment.Data, { children: ({ total, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: {
|
|
147
|
+
total,
|
|
148
|
+
customPrice,
|
|
149
|
+
}, children: total && _jsx(Money, { money: total }) })) }));
|
|
150
150
|
});
|
|
151
151
|
Total.displayName = 'Payment.Total';
|
|
152
152
|
/**
|
|
@@ -261,7 +261,7 @@ export const LineItemRepeater = React.forwardRef((props, ref) => {
|
|
|
261
261
|
LineItemRepeater.displayName = 'Payment.LineItemRepeater';
|
|
262
262
|
/**
|
|
263
263
|
* Displays the line item total (total price after tax).
|
|
264
|
-
* Shows
|
|
264
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows totalPrice.
|
|
265
265
|
*
|
|
266
266
|
* @component
|
|
267
267
|
* @example
|
|
@@ -271,12 +271,12 @@ LineItemRepeater.displayName = 'Payment.LineItemRepeater';
|
|
|
271
271
|
*/
|
|
272
272
|
export const LineItemTotal = React.forwardRef((props, ref) => {
|
|
273
273
|
const { asChild, children, className } = props;
|
|
274
|
-
return (_jsx(CorePayment.LineItemInfo, { children: ({ totalPrice,
|
|
274
|
+
return (_jsx(CorePayment.LineItemInfo, { children: ({ totalPrice, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemTotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: { totalPrice, customPrice }, children: customPrice ? (_jsx("span", { children: customPrice })) : (totalPrice && _jsx(Money, { money: totalPrice })) })) }));
|
|
275
275
|
});
|
|
276
276
|
LineItemTotal.displayName = 'Payment.LineItemTotal';
|
|
277
277
|
/**
|
|
278
278
|
* Displays the line item subtotal (full price before discounts).
|
|
279
|
-
* Shows
|
|
279
|
+
* Shows customPrice for CUSTOM rate type services, otherwise shows subtotal.
|
|
280
280
|
*
|
|
281
281
|
* @component
|
|
282
282
|
* @example
|
|
@@ -286,7 +286,7 @@ LineItemTotal.displayName = 'Payment.LineItemTotal';
|
|
|
286
286
|
*/
|
|
287
287
|
export const LineItemSubtotal = React.forwardRef((props, ref) => {
|
|
288
288
|
const { asChild, children, className } = props;
|
|
289
|
-
return (_jsx(CorePayment.LineItemInfo, { children: ({ subtotal,
|
|
289
|
+
return (_jsx(CorePayment.LineItemInfo, { children: ({ subtotal, customPrice }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemSubtotal, "data-has-custom-price": !!customPrice, customElement: children, customElementProps: { subtotal, customPrice }, children: customPrice ? (_jsx("span", { children: customPrice })) : (subtotal && _jsx(Money, { money: subtotal })) })) }));
|
|
290
290
|
});
|
|
291
291
|
LineItemSubtotal.displayName = 'Payment.LineItemSubtotal';
|
|
292
292
|
/**
|
|
@@ -44,7 +44,7 @@ export interface BookChildProps {
|
|
|
44
44
|
isLoading: boolean;
|
|
45
45
|
error: BookingError | null;
|
|
46
46
|
canBook: boolean;
|
|
47
|
-
onClick: () => void
|
|
47
|
+
onClick: () => Promise<void>;
|
|
48
48
|
disabled: boolean;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -57,9 +57,17 @@ export interface BookProps {
|
|
|
57
57
|
label?: string;
|
|
58
58
|
loadingState?: string;
|
|
59
59
|
disabled?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Called when checkout is required for payment.
|
|
62
|
+
* The navigateToCheckout function has the checkoutId captured - just call it.
|
|
63
|
+
*/
|
|
60
64
|
onCheckout?: (result: {
|
|
61
|
-
|
|
65
|
+
navigateToCheckout: () => void;
|
|
62
66
|
}) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Called when booking completes without requiring checkout (free/offline services).
|
|
69
|
+
* Receives the orderId which can be used to display confirmation or navigate to a thank you page.
|
|
70
|
+
*/
|
|
63
71
|
onComplete?: (result: {
|
|
64
72
|
orderId: string;
|
|
65
73
|
}) => void;
|
|
@@ -15,10 +15,8 @@ export type PaymentOption = 'FULL_PAYMENT_ONLINE' | 'FULL_PAYMENT_OFFLINE' | 'DE
|
|
|
15
15
|
export interface LineItemAdditionalInfo {
|
|
16
16
|
/** Line item ID to match with SDK's calculatedLineItems */
|
|
17
17
|
lineItemId: string;
|
|
18
|
-
/** Custom price
|
|
19
|
-
|
|
20
|
-
/** Whether this is a free service (NO_FEE rate type) */
|
|
21
|
-
isFree?: boolean;
|
|
18
|
+
/** Custom price (for CUSTOM rate type services) */
|
|
19
|
+
customPrice?: string;
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
22
|
* Payment calculation result with SDK response and additional computed info
|
|
@@ -20,11 +20,10 @@ const BOOKINGS_APP_DEF_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
function getAdditionalInfo(service, lineItemId) {
|
|
22
22
|
const payment = service.payment;
|
|
23
|
-
const
|
|
23
|
+
const customPrice = payment?.rateType === RateType.CUSTOM && payment.custom?.description
|
|
24
24
|
? payment.custom.description
|
|
25
25
|
: undefined;
|
|
26
|
-
|
|
27
|
-
return { lineItemId, priceText, isFree };
|
|
26
|
+
return { lineItemId, customPrice };
|
|
28
27
|
}
|
|
29
28
|
function getPaymentOption(service, hasDeposit) {
|
|
30
29
|
const options = service.payment?.options;
|
package/dist/utils/navigation.js
CHANGED
|
@@ -5,7 +5,8 @@ export async function navigateToCheckout(checkoutId) {
|
|
|
5
5
|
const { redirectSession } = await redirects.createRedirectSession({
|
|
6
6
|
ecomCheckout: { checkoutId },
|
|
7
7
|
callbacks: {
|
|
8
|
-
postFlowUrl: window.location.
|
|
8
|
+
postFlowUrl: `${window.location.host}/booking/services`,
|
|
9
|
+
bookingsServiceListUrl: `${window.location.host}/booking/services`,
|
|
9
10
|
},
|
|
10
11
|
});
|
|
11
12
|
if (redirectSession?.fullUrl) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.68",
|
|
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": "3b008d24daecb8e73bbbb513fa6c77a3915d21d91e398a5574be74a6"
|
|
76
76
|
}
|