@wix/headless-bookings 1.0.17 → 1.0.19
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/__mocks__/booking/booking-client-service.js +4 -1
- package/cjs/dist/__mocks__/services/services-mocks.d.ts +13 -0
- package/cjs/dist/__mocks__/services/services-mocks.js +92 -1
- package/cjs/dist/react/core/booking/Booking.d.ts +6 -0
- package/cjs/dist/react/core/booking/Booking.js +2 -0
- package/cjs/dist/react/core/payment/Payment.d.ts +2 -1
- package/cjs/dist/react/core/payment/Payment.js +11 -8
- package/cjs/dist/react/payment/Payment.d.ts +3 -2
- package/cjs/dist/react/payment/Payment.js +11 -5
- package/cjs/dist/services/booking/book-action/buildBookingRequest.js +13 -4
- package/cjs/dist/services/booking/book-action/types.d.ts +6 -0
- package/cjs/dist/services/booking/booking.d.ts +17 -0
- package/cjs/dist/services/booking/booking.js +8 -0
- package/cjs/dist/services/index.d.ts +1 -1
- package/cjs/dist/services/payment/payment.d.ts +4 -1
- package/cjs/dist/services/payment/payment.def.d.ts +49 -1
- package/cjs/dist/services/payment/payment.js +107 -69
- package/cjs/dist/services/servicePayment.d.ts +26 -0
- package/cjs/dist/services/servicePayment.js +46 -0
- package/cjs/dist/utils/money.d.ts +12 -2
- package/cjs/dist/utils/money.js +30 -18
- package/dist/__mocks__/booking/booking-client-service.js +4 -1
- package/dist/__mocks__/services/services-mocks.d.ts +13 -0
- package/dist/__mocks__/services/services-mocks.js +91 -0
- package/dist/react/core/booking/Booking.d.ts +6 -0
- package/dist/react/core/booking/Booking.js +2 -0
- package/dist/react/core/payment/Payment.d.ts +2 -1
- package/dist/react/core/payment/Payment.js +11 -8
- package/dist/react/payment/Payment.d.ts +3 -2
- package/dist/react/payment/Payment.js +11 -5
- package/dist/services/booking/book-action/buildBookingRequest.js +13 -4
- package/dist/services/booking/book-action/types.d.ts +6 -0
- package/dist/services/booking/booking.d.ts +17 -0
- package/dist/services/booking/booking.js +8 -0
- package/dist/services/index.d.ts +1 -1
- package/dist/services/payment/payment.d.ts +4 -1
- package/dist/services/payment/payment.def.d.ts +49 -1
- package/dist/services/payment/payment.js +107 -69
- package/dist/services/servicePayment.d.ts +26 -0
- package/dist/services/servicePayment.js +40 -0
- package/dist/utils/money.d.ts +12 -2
- package/dist/utils/money.js +30 -18
- package/package.json +4 -4
|
@@ -10,6 +10,7 @@ import { previewPrice, } from '@wix/auto_sdk_bookings_pricing';
|
|
|
10
10
|
import { BookingServiceDefinition } from '../booking/booking.js';
|
|
11
11
|
import { RateType, } from '@wix/auto_sdk_bookings_services';
|
|
12
12
|
import { PaymentServiceDefinition, } from './payment.def.js';
|
|
13
|
+
import { getDepositValue, hasDeposit as serviceHasDeposit, isFullUpfrontPaymentAllowed, requiresDeposit, } from '../servicePayment.js';
|
|
13
14
|
import { generateLineItemId } from '../../utils/guid.js';
|
|
14
15
|
export { PaymentServiceDefinition, } from './payment.def.js';
|
|
15
16
|
import { BOOKING_APP_ID } from '../constants.js';
|
|
@@ -20,14 +21,13 @@ const DEFAULT_SERVICE_OPTIONS = {
|
|
|
20
21
|
isOnline: false,
|
|
21
22
|
isInPerson: false,
|
|
22
23
|
isDeposit: false,
|
|
24
|
+
canPayFullUpfront: false,
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* Derives static service payment options from the first service's configuration.
|
|
26
28
|
*
|
|
27
|
-
* Note on `isDeposit`:
|
|
28
|
-
*
|
|
29
|
-
* `buildLineItem` determines `hasDeposit`. Only `FIXED` rate type is checked;
|
|
30
|
-
* `VARIED` pricing is not currently supported for deposit detection here.
|
|
29
|
+
* Note on `isDeposit`: reflects whether the service *takes* a deposit, not
|
|
30
|
+
* whether an amount is configured — see `requiresDeposit`.
|
|
31
31
|
*
|
|
32
32
|
* This is distinct from the runtime `isDeposit` flag in `CorePayment.Data`, which
|
|
33
33
|
* is computed dynamically from the ecom API result (`payNow.amount < total.amount`).
|
|
@@ -37,12 +37,11 @@ const DEFAULT_SERVICE_OPTIONS = {
|
|
|
37
37
|
function deriveServiceOptions(pricingServiceSelections) {
|
|
38
38
|
const service = pricingServiceSelections[0]?.service;
|
|
39
39
|
const options = service?.payment?.options;
|
|
40
|
-
const depositValue = service?.payment?.fixed?.deposit?.value;
|
|
41
|
-
const hasDeposit = !!depositValue && Number(depositValue) > 0;
|
|
42
40
|
return {
|
|
43
41
|
isOnline: !!options?.online,
|
|
44
42
|
isInPerson: !!options?.inPerson,
|
|
45
|
-
isDeposit:
|
|
43
|
+
isDeposit: requiresDeposit(service),
|
|
44
|
+
canPayFullUpfront: isFullUpfrontPaymentAllowed(service),
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
function getAdditionalInfo(service, lineItemId) {
|
|
@@ -55,52 +54,77 @@ function getAdditionalInfo(service, lineItemId) {
|
|
|
55
54
|
/**
|
|
56
55
|
* Resolves the line item's `paymentOption`.
|
|
57
56
|
*
|
|
58
|
-
* Precedence: explicit
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
57
|
+
* Precedence: an explicit `selectedPaymentOption` wins over the service-config
|
|
58
|
+
* heuristic. `depositSelected` then picks deposit-vs-full within the online
|
|
59
|
+
* branches only — the offline branches ignore it.
|
|
60
|
+
*
|
|
61
|
+
* Note: `online && inPerson` resolves OFFLINE here but ONLINE in
|
|
62
|
+
* `buildBookingRequest`'s heuristic. Pre-existing; the two aren't derived from a
|
|
63
|
+
* shared rule.
|
|
62
64
|
*/
|
|
63
|
-
function getPaymentOption(service, hasDeposit, selectedPaymentOption) {
|
|
65
|
+
function getPaymentOption(service, hasDeposit, selectedPaymentOption, depositSelected) {
|
|
66
|
+
// Only an explicit `false` (pay in full) cancels the deposit split. `undefined`
|
|
67
|
+
// means the visitor never chose, and must keep charging the deposit — that is
|
|
68
|
+
// what every deposit service did before this field existed.
|
|
69
|
+
const chargeDeposit = hasDeposit && depositSelected !== false;
|
|
64
70
|
if (selectedPaymentOption === 'OFFLINE') {
|
|
65
71
|
return 'FULL_PAYMENT_OFFLINE';
|
|
66
72
|
}
|
|
67
73
|
if (selectedPaymentOption === 'ONLINE') {
|
|
68
|
-
return
|
|
74
|
+
return chargeDeposit ? 'DEPOSIT_ONLINE' : 'FULL_PAYMENT_ONLINE';
|
|
69
75
|
}
|
|
70
76
|
const options = service.payment?.options;
|
|
71
77
|
if (options?.inPerson) {
|
|
72
78
|
return 'FULL_PAYMENT_OFFLINE';
|
|
73
79
|
}
|
|
74
80
|
// If deposit is configured, use DEPOSIT_ONLINE to get correct payNow/payLater split
|
|
75
|
-
if (
|
|
81
|
+
if (chargeDeposit) {
|
|
76
82
|
return 'DEPOSIT_ONLINE';
|
|
77
83
|
}
|
|
78
84
|
return 'FULL_PAYMENT_ONLINE';
|
|
79
85
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const deposit = hasDeposit
|
|
88
|
-
? Number(depositValue) * totalParticipants
|
|
89
|
-
: undefined;
|
|
90
|
-
// Get payment option from service configuration (pass hasDeposit for correct API behavior)
|
|
91
|
-
const paymentOption = getPaymentOption(service, hasDeposit, selectedPaymentOption);
|
|
86
|
+
/**
|
|
87
|
+
* Assembles the eCom line item. Both pricing paths go through here so the
|
|
88
|
+
* `depositAmount` rule — eCom rejects it unless the option is `DEPOSIT_ONLINE` —
|
|
89
|
+
* lives in one place.
|
|
90
|
+
*/
|
|
91
|
+
function toLineItem(args) {
|
|
92
|
+
const { lineItemId, serviceId, price, deposit, paymentOption } = args;
|
|
92
93
|
return {
|
|
93
94
|
_id: lineItemId,
|
|
94
95
|
catalogReference: {
|
|
95
|
-
catalogItemId:
|
|
96
|
+
catalogItemId: serviceId || '',
|
|
96
97
|
appId: BOOKING_APP_ID,
|
|
97
98
|
},
|
|
98
99
|
price: String(price),
|
|
99
|
-
...(deposit
|
|
100
|
+
...(deposit && paymentOption === 'DEPOSIT_ONLINE'
|
|
101
|
+
? { depositAmount: String(deposit) }
|
|
102
|
+
: {}),
|
|
100
103
|
quantity: 1,
|
|
101
104
|
paymentOption,
|
|
102
105
|
};
|
|
103
106
|
}
|
|
107
|
+
function buildLineItem(service, totalParticipants, lineItemId, selectedPaymentOption, depositSelected) {
|
|
108
|
+
// VARIED-rate services carry price and deposit under `varied`, not `fixed`.
|
|
109
|
+
const servicePrice = service.payment?.fixed?.price?.value ??
|
|
110
|
+
service.payment?.varied?.defaultPrice?.value;
|
|
111
|
+
const price = Number(servicePrice || 0) * totalParticipants;
|
|
112
|
+
// Charge a deposit only if the service takes one and we know the amount.
|
|
113
|
+
const depositValue = getDepositValue(service);
|
|
114
|
+
const hasDeposit = requiresDeposit(service) && serviceHasDeposit(service);
|
|
115
|
+
const deposit = hasDeposit
|
|
116
|
+
? Number(depositValue) * totalParticipants
|
|
117
|
+
: undefined;
|
|
118
|
+
// Get payment option from service configuration (pass hasDeposit for correct API behavior)
|
|
119
|
+
const paymentOption = getPaymentOption(service, hasDeposit, selectedPaymentOption, depositSelected);
|
|
120
|
+
return toLineItem({
|
|
121
|
+
lineItemId,
|
|
122
|
+
serviceId: service._id,
|
|
123
|
+
price,
|
|
124
|
+
deposit,
|
|
125
|
+
paymentOption,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
104
128
|
// durationRange is an @internal field on AvailabilityConstraints (bookings services SDK v1.0.226+).
|
|
105
129
|
// It signals that the service uses flexible duration pricing (hourly/daily rentals),
|
|
106
130
|
// where the correct price must be fetched via previewPrice() rather than the fixed unit price.
|
|
@@ -110,16 +134,11 @@ function getDurationRange(service) {
|
|
|
110
134
|
async function getPreviewedPrice(bookingLineItems) {
|
|
111
135
|
if (!bookingLineItems.length)
|
|
112
136
|
return null;
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
return null;
|
|
117
|
-
return {
|
|
118
|
-
price,
|
|
119
|
-
deposit: result.priceInfo?.deposit ?? null,
|
|
120
|
-
};
|
|
137
|
+
const { priceInfo } = await previewPrice(bookingLineItems);
|
|
138
|
+
const calculatedPrice = priceInfo?.calculatedPrice;
|
|
139
|
+
return calculatedPrice != null ? { ...priceInfo, calculatedPrice } : null;
|
|
121
140
|
}
|
|
122
|
-
async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption) {
|
|
141
|
+
async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption, depositSelected) {
|
|
123
142
|
const slots = item.rangeTimeSlots && item.rangeTimeSlots.length > 0
|
|
124
143
|
? item.rangeTimeSlots.map((s) => ({
|
|
125
144
|
localStartDate: s.startDate,
|
|
@@ -133,7 +152,12 @@ async function buildLineItemWithPreview(item, totalParticipants, selectedPayment
|
|
|
133
152
|
},
|
|
134
153
|
]
|
|
135
154
|
: [];
|
|
136
|
-
|
|
155
|
+
// A deposit with no configured amount is a percentage — only the server knows
|
|
156
|
+
// the figure, and guessing it risks charging something else.
|
|
157
|
+
const needsServerResolvedDeposit = requiresDeposit(item.service) && !serviceHasDeposit(item.service);
|
|
158
|
+
if (item.service._id &&
|
|
159
|
+
slots.length &&
|
|
160
|
+
(getDurationRange(item.service) || needsServerResolvedDeposit)) {
|
|
137
161
|
const bookingLineItems = slots.map((slot) => ({
|
|
138
162
|
serviceId: item.service._id,
|
|
139
163
|
localStartDate: slot.localStartDate,
|
|
@@ -141,43 +165,52 @@ async function buildLineItemWithPreview(item, totalParticipants, selectedPayment
|
|
|
141
165
|
numberOfParticipants: totalParticipants,
|
|
142
166
|
...(item.timeZone ? { timeZone: item.timeZone } : {}),
|
|
143
167
|
}));
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
const depositValue = item.service
|
|
147
|
-
const previewedDeposit =
|
|
148
|
-
const hasDeposit =
|
|
149
|
-
|
|
150
|
-
|
|
168
|
+
const priceInfo = await getPreviewedPrice(bookingLineItems).catch(() => null);
|
|
169
|
+
if (priceInfo != null) {
|
|
170
|
+
const depositValue = getDepositValue(item.service);
|
|
171
|
+
const previewedDeposit = priceInfo.deposit ?? null;
|
|
172
|
+
const hasDeposit = requiresDeposit(item.service) &&
|
|
173
|
+
(previewedDeposit != null
|
|
174
|
+
? previewedDeposit > 0
|
|
175
|
+
: serviceHasDeposit(item.service));
|
|
151
176
|
const deposit = hasDeposit
|
|
152
177
|
? previewedDeposit != null
|
|
153
178
|
? previewedDeposit
|
|
154
179
|
: Number(depositValue) * totalParticipants
|
|
155
180
|
: undefined;
|
|
156
181
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption),
|
|
182
|
+
lineItem: toLineItem({
|
|
183
|
+
lineItemId: item.lineItemId,
|
|
184
|
+
serviceId: item.service._id,
|
|
185
|
+
price: priceInfo.calculatedPrice,
|
|
186
|
+
deposit,
|
|
187
|
+
paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption, depositSelected),
|
|
188
|
+
}),
|
|
189
|
+
priceInfo,
|
|
166
190
|
};
|
|
167
191
|
}
|
|
168
192
|
}
|
|
169
|
-
return
|
|
193
|
+
return {
|
|
194
|
+
lineItem: buildLineItem(item.service, totalParticipants, item.lineItemId, selectedPaymentOption, depositSelected),
|
|
195
|
+
};
|
|
170
196
|
}
|
|
171
|
-
async function calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption) {
|
|
197
|
+
async function calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected) {
|
|
172
198
|
const data = pricingServiceSelections.map((selection) => ({
|
|
173
199
|
...selection,
|
|
174
200
|
lineItemId: selection.lineItemId || generateLineItemId(),
|
|
175
201
|
}));
|
|
176
|
-
const
|
|
202
|
+
const builtLineItems = await Promise.all(data.map(async (item) => {
|
|
203
|
+
const { lineItem, priceInfo } = await buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption, depositSelected);
|
|
204
|
+
const additionalInfo = {
|
|
205
|
+
...getAdditionalInfo(item.service, item.lineItemId),
|
|
206
|
+
...(priceInfo ? { priceInfo } : {}),
|
|
207
|
+
};
|
|
208
|
+
return { lineItem, additionalInfo };
|
|
209
|
+
}));
|
|
177
210
|
const response = await calculateTotals({
|
|
178
|
-
lineItems,
|
|
211
|
+
lineItems: builtLineItems.map(({ lineItem }) => lineItem),
|
|
179
212
|
});
|
|
180
|
-
const additionalInfoList =
|
|
213
|
+
const additionalInfoList = builtLineItems.map(({ additionalInfo }) => additionalInfo);
|
|
181
214
|
return {
|
|
182
215
|
response,
|
|
183
216
|
additionalInfo: {
|
|
@@ -204,7 +237,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
204
237
|
const errorSignal = signalsService.signal(null);
|
|
205
238
|
const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
|
|
206
239
|
// Internal calculate function
|
|
207
|
-
const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption) => {
|
|
240
|
+
const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected) => {
|
|
208
241
|
if (!pricingServiceSelections.length) {
|
|
209
242
|
paymentDetailsSignal.set(null);
|
|
210
243
|
return;
|
|
@@ -212,7 +245,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
212
245
|
isLoadingSignal.set(true);
|
|
213
246
|
errorSignal.set(null);
|
|
214
247
|
try {
|
|
215
|
-
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
|
|
248
|
+
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected);
|
|
216
249
|
paymentDetailsSignal.set(paymentDetails);
|
|
217
250
|
}
|
|
218
251
|
catch (error) {
|
|
@@ -240,16 +273,17 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
240
273
|
// Mode 2: Lazy loading - pricingServiceSelections provided via internal config
|
|
241
274
|
else if (config.pricingServiceSelections?.length) {
|
|
242
275
|
serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
|
|
243
|
-
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption);
|
|
276
|
+
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption, config.depositSelected);
|
|
244
277
|
}
|
|
245
278
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
246
279
|
else if (typeof window !== 'undefined' && bookingService) {
|
|
247
|
-
// Computed signal that changes when services, selected time slots, or
|
|
248
|
-
//
|
|
280
|
+
// Computed signal that changes when services, selected time slots, or the
|
|
281
|
+
// consumer's payment choices change (not participants).
|
|
249
282
|
const servicesKey = signalsService.computed(() => {
|
|
250
283
|
const selections = bookingService?.serviceSelections.get() || [];
|
|
251
284
|
const timezone = bookingService?.timezone.get();
|
|
252
285
|
const selectedPaymentOption = bookingService?.selectedPaymentOption.get();
|
|
286
|
+
const depositSelected = bookingService?.depositSelected.get();
|
|
253
287
|
return JSON.stringify({
|
|
254
288
|
selections: selections.map((s) => ({
|
|
255
289
|
serviceId: s.service._id,
|
|
@@ -259,16 +293,18 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
259
293
|
})),
|
|
260
294
|
timezone,
|
|
261
295
|
selectedPaymentOption,
|
|
296
|
+
depositSelected,
|
|
262
297
|
});
|
|
263
298
|
});
|
|
264
299
|
signalsService.effect(() => {
|
|
265
|
-
// Subscribe to servicesKey to trigger effect when service, time
|
|
266
|
-
//
|
|
300
|
+
// Subscribe to servicesKey to trigger effect when service, time slot,
|
|
301
|
+
// or payment choices change.
|
|
267
302
|
servicesKey.get();
|
|
268
303
|
// Read current selections without subscribing (peek)
|
|
269
304
|
const selections = bookingService?.serviceSelections.peek() || [];
|
|
270
305
|
const timezone = bookingService?.timezone.peek();
|
|
271
306
|
const selectedPaymentOption = bookingService?.selectedPaymentOption.peek();
|
|
307
|
+
const depositSelected = bookingService?.depositSelected.peek();
|
|
272
308
|
// For single service mode, take the first selection
|
|
273
309
|
const firstSelection = selections[0];
|
|
274
310
|
const numParticipants = firstSelection?.totalParticipants || 1;
|
|
@@ -283,7 +319,7 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
283
319
|
if (pricingServiceSelections.length) {
|
|
284
320
|
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
|
285
321
|
}
|
|
286
|
-
void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption);
|
|
322
|
+
void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption, depositSelected);
|
|
287
323
|
});
|
|
288
324
|
}
|
|
289
325
|
return {
|
|
@@ -301,10 +337,12 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
|
|
|
301
337
|
* @param params.totalParticipants - Number of participants (default: 1)
|
|
302
338
|
* @param params.selectedPaymentOption - Optional consumer-selected payment option;
|
|
303
339
|
* overrides the service-config heuristic on each line item's `paymentOption`.
|
|
340
|
+
* @param params.depositSelected - Optional deposit-vs-full-upfront choice; `false`
|
|
341
|
+
* resolves a deposit service to full payment. Omit to keep the deposit default.
|
|
304
342
|
*/
|
|
305
343
|
export async function loadPaymentConfig(params) {
|
|
306
344
|
try {
|
|
307
|
-
const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, } = params;
|
|
345
|
+
const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, depositSelected, } = params;
|
|
308
346
|
if (!pricingServiceSelections.length) {
|
|
309
347
|
return {
|
|
310
348
|
type: 'success',
|
|
@@ -314,7 +352,7 @@ export async function loadPaymentConfig(params) {
|
|
|
314
352
|
},
|
|
315
353
|
};
|
|
316
354
|
}
|
|
317
|
-
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
|
|
355
|
+
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected);
|
|
318
356
|
return {
|
|
319
357
|
type: 'success',
|
|
320
358
|
config: {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for reading payment configuration off a Service entity.
|
|
3
|
+
*
|
|
4
|
+
* `payment.fixed` / `varied` / `custom` are a `@oneof` in the services SDK, so
|
|
5
|
+
* only one is ever populated — reading `fixed` then `varied` covers both FIXED
|
|
6
|
+
* and VARIED rate types.
|
|
7
|
+
*
|
|
8
|
+
* A percentage deposit stores no amount anywhere on the entity; the server
|
|
9
|
+
* resolves it when the customer books. Hence the split between
|
|
10
|
+
* {@link requiresDeposit} ("takes a deposit?") and {@link hasDeposit} ("has an
|
|
11
|
+
* amount?") — a percentage deposit answers `true` and `false` respectively.
|
|
12
|
+
*
|
|
13
|
+
* @module Services/ServicePayment
|
|
14
|
+
*/
|
|
15
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
16
|
+
/**
|
|
17
|
+
* Whether the service takes a deposit. An explicit `options.deposit` wins; when
|
|
18
|
+
* the flag is absent a configured amount still counts, so a service that sets one
|
|
19
|
+
* without the flag keeps reporting a deposit.
|
|
20
|
+
*/
|
|
21
|
+
export declare function requiresDeposit(service?: Service): boolean;
|
|
22
|
+
/** The deposit amount, if one is configured as a fixed amount. */
|
|
23
|
+
export declare function getDepositValue(service?: Service): string | undefined;
|
|
24
|
+
export declare function hasDeposit(service?: Service): boolean;
|
|
25
|
+
/** Meaningful only alongside `hasDeposit`. */
|
|
26
|
+
export declare function isFullUpfrontPaymentAllowed(service?: Service): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for reading payment configuration off a Service entity.
|
|
3
|
+
*
|
|
4
|
+
* `payment.fixed` / `varied` / `custom` are a `@oneof` in the services SDK, so
|
|
5
|
+
* only one is ever populated — reading `fixed` then `varied` covers both FIXED
|
|
6
|
+
* and VARIED rate types.
|
|
7
|
+
*
|
|
8
|
+
* A percentage deposit stores no amount anywhere on the entity; the server
|
|
9
|
+
* resolves it when the customer books. Hence the split between
|
|
10
|
+
* {@link requiresDeposit} ("takes a deposit?") and {@link hasDeposit} ("has an
|
|
11
|
+
* amount?") — a percentage deposit answers `true` and `false` respectively.
|
|
12
|
+
*
|
|
13
|
+
* @module Services/ServicePayment
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Whether the service takes a deposit. An explicit `options.deposit` wins; when
|
|
17
|
+
* the flag is absent a configured amount still counts, so a service that sets one
|
|
18
|
+
* without the flag keeps reporting a deposit.
|
|
19
|
+
*/
|
|
20
|
+
export function requiresDeposit(service) {
|
|
21
|
+
return service?.payment?.options?.deposit ?? hasDeposit(service);
|
|
22
|
+
}
|
|
23
|
+
/** The deposit amount, if one is configured as a fixed amount. */
|
|
24
|
+
export function getDepositValue(service) {
|
|
25
|
+
const payment = service?.payment;
|
|
26
|
+
return (payment?.fixed?.deposit?.value ??
|
|
27
|
+
payment?.varied?.deposit?.value ??
|
|
28
|
+
payment?.fixed?.depositDetails?.amount?.value ??
|
|
29
|
+
payment?.varied?.depositDetails?.amount?.value);
|
|
30
|
+
}
|
|
31
|
+
export function hasDeposit(service) {
|
|
32
|
+
const value = getDepositValue(service);
|
|
33
|
+
return !!value && Number(value) > 0;
|
|
34
|
+
}
|
|
35
|
+
/** Meaningful only alongside `hasDeposit`. */
|
|
36
|
+
export function isFullUpfrontPaymentAllowed(service) {
|
|
37
|
+
const payment = service?.payment;
|
|
38
|
+
return !!(payment?.fixed?.fullUpfrontPaymentAllowed ??
|
|
39
|
+
payment?.varied?.fullUpfrontPaymentAllowed);
|
|
40
|
+
}
|
package/dist/utils/money.d.ts
CHANGED
|
@@ -5,13 +5,19 @@ import type { MoneyData } from '@wix/headless-components/react';
|
|
|
5
5
|
/**
|
|
6
6
|
* Converts SDK Money format to MoneyData format
|
|
7
7
|
*
|
|
8
|
+
* The original SDK value is validated as a whole (values that are not fully
|
|
9
|
+
* numeric and finite, like `"12.34xyz"`, return `null`) but kept as-is, so
|
|
10
|
+
* string amounts reach `Money` with full decimal precision instead of being
|
|
11
|
+
* routed through floating point. Compare amounts numerically with
|
|
12
|
+
* `Number(money.amount)`.
|
|
13
|
+
*
|
|
8
14
|
* @param sdkMoney - The SDK money object with value, currency, and optional formattedValue
|
|
9
15
|
* @returns MoneyData object with amount and currency, or null if conversion fails
|
|
10
16
|
*
|
|
11
17
|
* @example
|
|
12
18
|
* ```ts
|
|
13
19
|
* const money = convertToMoneyData({ value: "25.50", currency: "USD" });
|
|
14
|
-
* // Returns: { amount: 25.50, currency: "USD" }
|
|
20
|
+
* // Returns: { amount: "25.50", currency: "USD" }
|
|
15
21
|
* ```
|
|
16
22
|
*/
|
|
17
23
|
export declare function convertToMoneyData(sdkMoney: {
|
|
@@ -21,7 +27,11 @@ export declare function convertToMoneyData(sdkMoney: {
|
|
|
21
27
|
} | undefined): MoneyData | null;
|
|
22
28
|
/**
|
|
23
29
|
* Converts ecom MultiCurrencyPrice format to MoneyData format
|
|
24
|
-
*
|
|
30
|
+
*
|
|
31
|
+
* The string amount is validated as a whole (values that are not fully
|
|
32
|
+
* numeric and finite, like `"12.34xyz"`, return `null`) but kept as-is, so
|
|
33
|
+
* it reaches `Money` with full decimal precision. Compare amounts
|
|
34
|
+
* numerically with `Number(money.amount)`.
|
|
25
35
|
*
|
|
26
36
|
* @param price - The MultiCurrencyPrice object with amount as string
|
|
27
37
|
* @param currency - The currency code (from response level)
|
package/dist/utils/money.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Money utility functions for converting between SDK Money types and MoneyData
|
|
3
3
|
*/
|
|
4
|
+
// Number() (unlike parseFloat) rejects strings with trailing garbage like
|
|
5
|
+
// "12.34xyz", but treats empty/whitespace-only strings as 0 — reject those
|
|
6
|
+
// separately so invalid values return null instead of reaching the formatter.
|
|
7
|
+
function toNumericAmount(value) {
|
|
8
|
+
if (typeof value === 'string') {
|
|
9
|
+
return value.trim() === '' ? NaN : Number(value);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* Converts SDK Money format to MoneyData format
|
|
6
15
|
*
|
|
16
|
+
* The original SDK value is validated as a whole (values that are not fully
|
|
17
|
+
* numeric and finite, like `"12.34xyz"`, return `null`) but kept as-is, so
|
|
18
|
+
* string amounts reach `Money` with full decimal precision instead of being
|
|
19
|
+
* routed through floating point. Compare amounts numerically with
|
|
20
|
+
* `Number(money.amount)`.
|
|
21
|
+
*
|
|
7
22
|
* @param sdkMoney - The SDK money object with value, currency, and optional formattedValue
|
|
8
23
|
* @returns MoneyData object with amount and currency, or null if conversion fails
|
|
9
24
|
*
|
|
10
25
|
* @example
|
|
11
26
|
* ```ts
|
|
12
27
|
* const money = convertToMoneyData({ value: "25.50", currency: "USD" });
|
|
13
|
-
* // Returns: { amount: 25.50, currency: "USD" }
|
|
28
|
+
* // Returns: { amount: "25.50", currency: "USD" }
|
|
14
29
|
* ```
|
|
15
30
|
*/
|
|
16
31
|
export function convertToMoneyData(sdkMoney) {
|
|
@@ -18,23 +33,20 @@ export function convertToMoneyData(sdkMoney) {
|
|
|
18
33
|
return null;
|
|
19
34
|
const priceValue = sdkMoney.value;
|
|
20
35
|
const currency = sdkMoney.currency || 'USD';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
else if (typeof priceValue === 'number') {
|
|
28
|
-
amount = priceValue;
|
|
29
|
-
}
|
|
30
|
-
if (amount !== undefined && !isNaN(amount) && amount >= 0) {
|
|
31
|
-
return { amount, currency };
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
36
|
+
if (priceValue === undefined)
|
|
37
|
+
return null;
|
|
38
|
+
const parsed = toNumericAmount(priceValue);
|
|
39
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
40
|
+
return null;
|
|
41
|
+
return { amount: priceValue, currency };
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Converts ecom MultiCurrencyPrice format to MoneyData format
|
|
37
|
-
*
|
|
45
|
+
*
|
|
46
|
+
* The string amount is validated as a whole (values that are not fully
|
|
47
|
+
* numeric and finite, like `"12.34xyz"`, return `null`) but kept as-is, so
|
|
48
|
+
* it reaches `Money` with full decimal precision. Compare amounts
|
|
49
|
+
* numerically with `Number(money.amount)`.
|
|
38
50
|
*
|
|
39
51
|
* @param price - The MultiCurrencyPrice object with amount as string
|
|
40
52
|
* @param currency - The currency code (from response level)
|
|
@@ -43,8 +55,8 @@ export function convertToMoneyData(sdkMoney) {
|
|
|
43
55
|
export function convertMultiCurrencyPriceToMoneyData(price, currency) {
|
|
44
56
|
if (!price?.amount)
|
|
45
57
|
return null;
|
|
46
|
-
const parsed =
|
|
47
|
-
if (
|
|
58
|
+
const parsed = toNumericAmount(price.amount);
|
|
59
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
48
60
|
return null;
|
|
49
|
-
return { amount:
|
|
61
|
+
return { amount: price.amount, currency };
|
|
50
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@wix/auto_sdk_bookings_catalog-search": "^1.0.2",
|
|
42
42
|
"@wix/auto_sdk_bookings_event-time-slots": "^1.0.112",
|
|
43
43
|
"@wix/auto_sdk_bookings_pricing": "^1.0.87",
|
|
44
|
-
"@wix/auto_sdk_bookings_services": "^1.0.
|
|
44
|
+
"@wix/auto_sdk_bookings_services": "^1.0.265",
|
|
45
45
|
"@wix/auto_sdk_ecom_cart-v-2": "^1.0.86",
|
|
46
46
|
"@wix/forms": "^1.0.384",
|
|
47
47
|
"@wix/headless-media": "0.0.25",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"vitest": "^3.1.4"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@wix/headless-components": "
|
|
81
|
+
"@wix/headless-components": "~0.0.47",
|
|
82
82
|
"react": ">=16.3.0"
|
|
83
83
|
},
|
|
84
84
|
"publishConfig": {
|
|
@@ -91,5 +91,5 @@
|
|
|
91
91
|
"groupId": "com.wixpress.headless-components"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
-
"falconPackageHash": "
|
|
94
|
+
"falconPackageHash": "025aaf3ecf64a7ef23fcc40e8cad3993079c8c4a92038520589a77a2"
|
|
95
95
|
}
|