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