@wix/headless-bookings 1.0.1 → 1.0.3
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/core/booking/Booking.js +4 -0
- package/cjs/dist/services/payment/payment.def.d.ts +6 -0
- package/cjs/dist/services/payment/payment.js +57 -5
- package/dist/react/core/booking/Booking.js +4 -0
- package/dist/services/payment/payment.def.d.ts +6 -0
- package/dist/services/payment/payment.js +58 -6
- package/package.json +3 -2
|
@@ -296,9 +296,13 @@ function PaymentData(props) {
|
|
|
296
296
|
const { children } = props;
|
|
297
297
|
const bookingService = (0, services_manager_react_1.useService)(booking_js_1.BookingServiceDefinition);
|
|
298
298
|
const serviceSelections = bookingService.serviceSelections.get();
|
|
299
|
+
const timezone = bookingService.timezone.get();
|
|
299
300
|
const pricingServiceSelections = serviceSelections.map((selection) => ({
|
|
300
301
|
service: selection.service,
|
|
301
302
|
lineItemId: selection.instanceId,
|
|
303
|
+
localStartDate: selection.timeSlot?.startDate,
|
|
304
|
+
localEndDate: selection.timeSlot?.endDate,
|
|
305
|
+
timeZone: timezone,
|
|
302
306
|
}));
|
|
303
307
|
return children({ pricingServiceSelections });
|
|
304
308
|
}
|
|
@@ -48,6 +48,12 @@ export interface PricingServiceSelection {
|
|
|
48
48
|
service: Service;
|
|
49
49
|
/** Optional line item ID. If not provided, one will be generated. */
|
|
50
50
|
lineItemId?: string;
|
|
51
|
+
/** Local start date of the selected slot in ISO-8601 format (e.g. "2024-01-30T13:30:00"). */
|
|
52
|
+
localStartDate?: string;
|
|
53
|
+
/** Local end date of the selected slot in ISO-8601 format (e.g. "2024-01-30T14:30:00"). */
|
|
54
|
+
localEndDate?: string;
|
|
55
|
+
/** IANA time zone identifier for the selected slot (e.g. "America/New_York"). */
|
|
56
|
+
timeZone?: string;
|
|
51
57
|
}
|
|
52
58
|
/**
|
|
53
59
|
* Public config for PaymentService (SSR mode only):
|
|
@@ -10,6 +10,7 @@ exports.loadPaymentConfig = loadPaymentConfig;
|
|
|
10
10
|
const services_definitions_1 = require("@wix/services-definitions");
|
|
11
11
|
const signals_1 = require("@wix/services-definitions/core-services/signals");
|
|
12
12
|
const auto_sdk_ecom_totals_calculator_1 = require("@wix/auto_sdk_ecom_totals-calculator");
|
|
13
|
+
const auto_sdk_bookings_pricing_1 = require("@wix/auto_sdk_bookings_pricing");
|
|
13
14
|
const booking_js_1 = require("../booking/booking.js");
|
|
14
15
|
const auto_sdk_bookings_services_1 = require("@wix/auto_sdk_bookings_services");
|
|
15
16
|
const payment_def_js_1 = require("./payment.def.js");
|
|
@@ -91,13 +92,57 @@ function buildLineItem(service, totalParticipants, lineItemId) {
|
|
|
91
92
|
paymentOption,
|
|
92
93
|
};
|
|
93
94
|
}
|
|
95
|
+
// durationRange is an @internal field on AvailabilityConstraints (bookings services SDK v1.0.226+).
|
|
96
|
+
// It signals that the service uses flexible duration pricing (hourly/daily rentals),
|
|
97
|
+
// where the correct price must be fetched via previewPrice() rather than the fixed unit price.
|
|
98
|
+
function getDurationRange(service) {
|
|
99
|
+
return service.schedule?.availabilityConstraints?.durationRange;
|
|
100
|
+
}
|
|
101
|
+
async function getPreviewedPrice(service, localStartDate, localEndDate, timeZone, numberOfParticipants) {
|
|
102
|
+
if (!service._id)
|
|
103
|
+
return null;
|
|
104
|
+
const lineItemInput = {
|
|
105
|
+
serviceId: service._id,
|
|
106
|
+
localStartDate,
|
|
107
|
+
localEndDate,
|
|
108
|
+
numberOfParticipants,
|
|
109
|
+
...(timeZone ? { timeZone } : {}),
|
|
110
|
+
};
|
|
111
|
+
const result = await (0, auto_sdk_bookings_pricing_1.previewPrice)([lineItemInput]);
|
|
112
|
+
return result.priceInfo?.calculatedPrice ?? null;
|
|
113
|
+
}
|
|
114
|
+
async function buildLineItemWithPreview(item, totalParticipants) {
|
|
115
|
+
if (item.localStartDate &&
|
|
116
|
+
item.localEndDate &&
|
|
117
|
+
getDurationRange(item.service)) {
|
|
118
|
+
const previewedPrice = await getPreviewedPrice(item.service, item.localStartDate, item.localEndDate, item.timeZone, totalParticipants).catch(() => null);
|
|
119
|
+
if (previewedPrice != null) {
|
|
120
|
+
const depositValue = item.service.payment?.fixed?.deposit?.value;
|
|
121
|
+
const hasDeposit = !!depositValue && Number(depositValue) > 0;
|
|
122
|
+
const deposit = hasDeposit
|
|
123
|
+
? Number(depositValue) * totalParticipants
|
|
124
|
+
: undefined;
|
|
125
|
+
return {
|
|
126
|
+
_id: item.lineItemId,
|
|
127
|
+
catalogReference: {
|
|
128
|
+
catalogItemId: item.service._id || '',
|
|
129
|
+
appId: constants_js_1.BOOKING_APP_ID,
|
|
130
|
+
},
|
|
131
|
+
price: String(previewedPrice),
|
|
132
|
+
...(deposit ? { depositAmount: String(deposit) } : {}),
|
|
133
|
+
quantity: 1,
|
|
134
|
+
paymentOption: getPaymentOption(item.service, hasDeposit),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return buildLineItem(item.service, totalParticipants, item.lineItemId);
|
|
139
|
+
}
|
|
94
140
|
async function calculatePaymentDetails(pricingServiceSelections, totalParticipants) {
|
|
95
|
-
// Ensure all booking services have a lineItemId
|
|
96
141
|
const data = pricingServiceSelections.map((selection) => ({
|
|
97
142
|
...selection,
|
|
98
143
|
lineItemId: selection.lineItemId || (0, guid_js_1.generateLineItemId)(),
|
|
99
144
|
}));
|
|
100
|
-
const lineItems = data.map((item) =>
|
|
145
|
+
const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants)));
|
|
101
146
|
const response = await (0, auto_sdk_ecom_totals_calculator_1.calculateTotals)({
|
|
102
147
|
lineItems,
|
|
103
148
|
});
|
|
@@ -168,22 +213,29 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
168
213
|
}
|
|
169
214
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
170
215
|
else if (typeof window !== 'undefined' && bookingService) {
|
|
171
|
-
// Computed signal that
|
|
216
|
+
// Computed signal that changes when services or selected time slots change (not participants)
|
|
172
217
|
const servicesKey = signalsService.computed(() => {
|
|
173
218
|
const selections = bookingService?.serviceSelections.get() || [];
|
|
174
|
-
|
|
219
|
+
const timezone = bookingService?.timezone.get();
|
|
220
|
+
return (selections
|
|
221
|
+
.map((s) => `${s.service._id}:${s.timeSlot?.startDate ?? ''}:${s.timeSlot?.endDate ?? ''}`)
|
|
222
|
+
.join(',') + `:${timezone ?? ''}`);
|
|
175
223
|
});
|
|
176
224
|
signalsService.effect(() => {
|
|
177
|
-
// Subscribe to servicesKey to trigger effect
|
|
225
|
+
// Subscribe to servicesKey to trigger effect when service or time slot changes
|
|
178
226
|
servicesKey.get();
|
|
179
227
|
// Read current selections without subscribing (peek)
|
|
180
228
|
const selections = bookingService?.serviceSelections.peek() || [];
|
|
229
|
+
const timezone = bookingService?.timezone.peek();
|
|
181
230
|
// For single service mode, take the first selection
|
|
182
231
|
const firstSelection = selections[0];
|
|
183
232
|
const numParticipants = firstSelection?.totalParticipants || 1;
|
|
184
233
|
const pricingServiceSelections = selections.map((selection) => ({
|
|
185
234
|
service: selection.service,
|
|
186
235
|
lineItemId: selection.instanceId,
|
|
236
|
+
localStartDate: selection.timeSlot?.startDate,
|
|
237
|
+
localEndDate: selection.timeSlot?.endDate,
|
|
238
|
+
timeZone: timezone,
|
|
187
239
|
}));
|
|
188
240
|
if (pricingServiceSelections.length) {
|
|
189
241
|
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
|
@@ -284,9 +284,13 @@ export function PaymentData(props) {
|
|
|
284
284
|
const { children } = props;
|
|
285
285
|
const bookingService = useService(BookingServiceDefinition);
|
|
286
286
|
const serviceSelections = bookingService.serviceSelections.get();
|
|
287
|
+
const timezone = bookingService.timezone.get();
|
|
287
288
|
const pricingServiceSelections = serviceSelections.map((selection) => ({
|
|
288
289
|
service: selection.service,
|
|
289
290
|
lineItemId: selection.instanceId,
|
|
291
|
+
localStartDate: selection.timeSlot?.startDate,
|
|
292
|
+
localEndDate: selection.timeSlot?.endDate,
|
|
293
|
+
timeZone: timezone,
|
|
290
294
|
}));
|
|
291
295
|
return children({ pricingServiceSelections });
|
|
292
296
|
}
|
|
@@ -48,6 +48,12 @@ export interface PricingServiceSelection {
|
|
|
48
48
|
service: Service;
|
|
49
49
|
/** Optional line item ID. If not provided, one will be generated. */
|
|
50
50
|
lineItemId?: string;
|
|
51
|
+
/** Local start date of the selected slot in ISO-8601 format (e.g. "2024-01-30T13:30:00"). */
|
|
52
|
+
localStartDate?: string;
|
|
53
|
+
/** Local end date of the selected slot in ISO-8601 format (e.g. "2024-01-30T14:30:00"). */
|
|
54
|
+
localEndDate?: string;
|
|
55
|
+
/** IANA time zone identifier for the selected slot (e.g. "America/New_York"). */
|
|
56
|
+
timeZone?: string;
|
|
51
57
|
}
|
|
52
58
|
/**
|
|
53
59
|
* Public config for PaymentService (SSR mode only):
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import { implementService } from '@wix/services-definitions';
|
|
7
7
|
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
8
8
|
import { calculateTotals, } from '@wix/auto_sdk_ecom_totals-calculator';
|
|
9
|
+
import { previewPrice, } from '@wix/auto_sdk_bookings_pricing';
|
|
9
10
|
import { BookingServiceDefinition } from '../booking/booking.js';
|
|
10
|
-
import { RateType } from '@wix/auto_sdk_bookings_services';
|
|
11
|
+
import { RateType, } from '@wix/auto_sdk_bookings_services';
|
|
11
12
|
import { PaymentServiceDefinition, } from './payment.def.js';
|
|
12
13
|
import { generateLineItemId } from '../../utils/guid.js';
|
|
13
14
|
export { PaymentServiceDefinition, } from './payment.def.js';
|
|
@@ -86,13 +87,57 @@ function buildLineItem(service, totalParticipants, lineItemId) {
|
|
|
86
87
|
paymentOption,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
90
|
+
// durationRange is an @internal field on AvailabilityConstraints (bookings services SDK v1.0.226+).
|
|
91
|
+
// It signals that the service uses flexible duration pricing (hourly/daily rentals),
|
|
92
|
+
// where the correct price must be fetched via previewPrice() rather than the fixed unit price.
|
|
93
|
+
function getDurationRange(service) {
|
|
94
|
+
return service.schedule?.availabilityConstraints?.durationRange;
|
|
95
|
+
}
|
|
96
|
+
async function getPreviewedPrice(service, localStartDate, localEndDate, timeZone, numberOfParticipants) {
|
|
97
|
+
if (!service._id)
|
|
98
|
+
return null;
|
|
99
|
+
const lineItemInput = {
|
|
100
|
+
serviceId: service._id,
|
|
101
|
+
localStartDate,
|
|
102
|
+
localEndDate,
|
|
103
|
+
numberOfParticipants,
|
|
104
|
+
...(timeZone ? { timeZone } : {}),
|
|
105
|
+
};
|
|
106
|
+
const result = await previewPrice([lineItemInput]);
|
|
107
|
+
return result.priceInfo?.calculatedPrice ?? null;
|
|
108
|
+
}
|
|
109
|
+
async function buildLineItemWithPreview(item, totalParticipants) {
|
|
110
|
+
if (item.localStartDate &&
|
|
111
|
+
item.localEndDate &&
|
|
112
|
+
getDurationRange(item.service)) {
|
|
113
|
+
const previewedPrice = await getPreviewedPrice(item.service, item.localStartDate, item.localEndDate, item.timeZone, totalParticipants).catch(() => null);
|
|
114
|
+
if (previewedPrice != null) {
|
|
115
|
+
const depositValue = item.service.payment?.fixed?.deposit?.value;
|
|
116
|
+
const hasDeposit = !!depositValue && Number(depositValue) > 0;
|
|
117
|
+
const deposit = hasDeposit
|
|
118
|
+
? Number(depositValue) * totalParticipants
|
|
119
|
+
: undefined;
|
|
120
|
+
return {
|
|
121
|
+
_id: item.lineItemId,
|
|
122
|
+
catalogReference: {
|
|
123
|
+
catalogItemId: item.service._id || '',
|
|
124
|
+
appId: BOOKING_APP_ID,
|
|
125
|
+
},
|
|
126
|
+
price: String(previewedPrice),
|
|
127
|
+
...(deposit ? { depositAmount: String(deposit) } : {}),
|
|
128
|
+
quantity: 1,
|
|
129
|
+
paymentOption: getPaymentOption(item.service, hasDeposit),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return buildLineItem(item.service, totalParticipants, item.lineItemId);
|
|
134
|
+
}
|
|
89
135
|
async function calculatePaymentDetails(pricingServiceSelections, totalParticipants) {
|
|
90
|
-
// Ensure all booking services have a lineItemId
|
|
91
136
|
const data = pricingServiceSelections.map((selection) => ({
|
|
92
137
|
...selection,
|
|
93
138
|
lineItemId: selection.lineItemId || generateLineItemId(),
|
|
94
139
|
}));
|
|
95
|
-
const lineItems = data.map((item) =>
|
|
140
|
+
const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants)));
|
|
96
141
|
const response = await calculateTotals({
|
|
97
142
|
lineItems,
|
|
98
143
|
});
|
|
@@ -163,22 +208,29 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
163
208
|
}
|
|
164
209
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
165
210
|
else if (typeof window !== 'undefined' && bookingService) {
|
|
166
|
-
// Computed signal that
|
|
211
|
+
// Computed signal that changes when services or selected time slots change (not participants)
|
|
167
212
|
const servicesKey = signalsService.computed(() => {
|
|
168
213
|
const selections = bookingService?.serviceSelections.get() || [];
|
|
169
|
-
|
|
214
|
+
const timezone = bookingService?.timezone.get();
|
|
215
|
+
return (selections
|
|
216
|
+
.map((s) => `${s.service._id}:${s.timeSlot?.startDate ?? ''}:${s.timeSlot?.endDate ?? ''}`)
|
|
217
|
+
.join(',') + `:${timezone ?? ''}`);
|
|
170
218
|
});
|
|
171
219
|
signalsService.effect(() => {
|
|
172
|
-
// Subscribe to servicesKey to trigger effect
|
|
220
|
+
// Subscribe to servicesKey to trigger effect when service or time slot changes
|
|
173
221
|
servicesKey.get();
|
|
174
222
|
// Read current selections without subscribing (peek)
|
|
175
223
|
const selections = bookingService?.serviceSelections.peek() || [];
|
|
224
|
+
const timezone = bookingService?.timezone.peek();
|
|
176
225
|
// For single service mode, take the first selection
|
|
177
226
|
const firstSelection = selections[0];
|
|
178
227
|
const numParticipants = firstSelection?.totalParticipants || 1;
|
|
179
228
|
const pricingServiceSelections = selections.map((selection) => ({
|
|
180
229
|
service: selection.service,
|
|
181
230
|
lineItemId: selection.instanceId,
|
|
231
|
+
localStartDate: selection.timeSlot?.startDate,
|
|
232
|
+
localEndDate: selection.timeSlot?.endDate,
|
|
233
|
+
timeZone: timezone,
|
|
182
234
|
}));
|
|
183
235
|
if (pricingServiceSelections.length) {
|
|
184
236
|
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@wix/auto_sdk_bookings_bookings": "^1.0.183",
|
|
38
38
|
"@wix/auto_sdk_bookings_event-time-slots": "^1.0.64",
|
|
39
|
+
"@wix/auto_sdk_bookings_pricing": "^1.0.87",
|
|
39
40
|
"@wix/auto_sdk_bookings_services": "^1.0.226",
|
|
40
41
|
"@wix/auto_sdk_ecom_cart-v-2": "^1.0.86",
|
|
41
42
|
"@wix/forms": "^1.0.384",
|
|
@@ -74,5 +75,5 @@
|
|
|
74
75
|
"groupId": "com.wixpress.headless-components"
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
|
-
"falconPackageHash": "
|
|
78
|
+
"falconPackageHash": "60d159ffa08e430dd4c999859e909d281cd9262b93a577989e8de015"
|
|
78
79
|
}
|