@wix/headless-bookings 1.0.16 → 1.0.18
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/core/service/Service.js +4 -2
- 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/payment/payment.d.ts +3 -0
- package/cjs/dist/services/payment/payment.def.d.ts +15 -1
- package/cjs/dist/services/payment/payment.js +89 -58
- package/cjs/dist/services/service/service.js +1 -1
- 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/core/service/Service.js +4 -2
- 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/payment/payment.d.ts +3 -0
- package/dist/services/payment/payment.def.d.ts +15 -1
- package/dist/services/payment/payment.js +89 -58
- package/dist/services/service/service.js +1 -1
- 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 +3 -3
|
@@ -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.
|
|
@@ -124,7 +148,7 @@ async function getPreviewedPrice(bookingLineItems) {
|
|
|
124
148
|
deposit: result.priceInfo?.deposit ?? null,
|
|
125
149
|
};
|
|
126
150
|
}
|
|
127
|
-
async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption) {
|
|
151
|
+
async function buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption, depositSelected) {
|
|
128
152
|
const slots = item.rangeTimeSlots && item.rangeTimeSlots.length > 0
|
|
129
153
|
? item.rangeTimeSlots.map((s) => ({
|
|
130
154
|
localStartDate: s.startDate,
|
|
@@ -138,7 +162,12 @@ async function buildLineItemWithPreview(item, totalParticipants, selectedPayment
|
|
|
138
162
|
},
|
|
139
163
|
]
|
|
140
164
|
: [];
|
|
141
|
-
|
|
165
|
+
// A deposit with no configured amount is a percentage — only the server knows
|
|
166
|
+
// the figure, and guessing it risks charging something else.
|
|
167
|
+
const needsServerResolvedDeposit = (0, servicePayment_js_1.requiresDeposit)(item.service) && !(0, servicePayment_js_1.hasDeposit)(item.service);
|
|
168
|
+
if (item.service._id &&
|
|
169
|
+
slots.length &&
|
|
170
|
+
(getDurationRange(item.service) || needsServerResolvedDeposit)) {
|
|
142
171
|
const bookingLineItems = slots.map((slot) => ({
|
|
143
172
|
serviceId: item.service._id,
|
|
144
173
|
localStartDate: slot.localStartDate,
|
|
@@ -148,37 +177,34 @@ async function buildLineItemWithPreview(item, totalParticipants, selectedPayment
|
|
|
148
177
|
}));
|
|
149
178
|
const previewedPrice = await getPreviewedPrice(bookingLineItems).catch(() => null);
|
|
150
179
|
if (previewedPrice != null) {
|
|
151
|
-
const depositValue = item.service
|
|
180
|
+
const depositValue = (0, servicePayment_js_1.getDepositValue)(item.service);
|
|
152
181
|
const previewedDeposit = previewedPrice.deposit;
|
|
153
|
-
const hasDeposit =
|
|
154
|
-
|
|
155
|
-
|
|
182
|
+
const hasDeposit = (0, servicePayment_js_1.requiresDeposit)(item.service) &&
|
|
183
|
+
(previewedDeposit != null
|
|
184
|
+
? previewedDeposit > 0
|
|
185
|
+
: (0, servicePayment_js_1.hasDeposit)(item.service));
|
|
156
186
|
const deposit = hasDeposit
|
|
157
187
|
? previewedDeposit != null
|
|
158
188
|
? previewedDeposit
|
|
159
189
|
: Number(depositValue) * totalParticipants
|
|
160
190
|
: undefined;
|
|
161
|
-
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
...(deposit ? { depositAmount: String(deposit) } : {}),
|
|
169
|
-
quantity: 1,
|
|
170
|
-
paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption),
|
|
171
|
-
};
|
|
191
|
+
return toLineItem({
|
|
192
|
+
lineItemId: item.lineItemId,
|
|
193
|
+
serviceId: item.service._id,
|
|
194
|
+
price: previewedPrice.price,
|
|
195
|
+
deposit,
|
|
196
|
+
paymentOption: getPaymentOption(item.service, hasDeposit, selectedPaymentOption, depositSelected),
|
|
197
|
+
});
|
|
172
198
|
}
|
|
173
199
|
}
|
|
174
|
-
return buildLineItem(item.service, totalParticipants, item.lineItemId, selectedPaymentOption);
|
|
200
|
+
return buildLineItem(item.service, totalParticipants, item.lineItemId, selectedPaymentOption, depositSelected);
|
|
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 lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption)));
|
|
207
|
+
const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants, selectedPaymentOption, depositSelected)));
|
|
182
208
|
const response = await (0, auto_sdk_ecom_totals_calculator_1.calculateTotals)({
|
|
183
209
|
lineItems,
|
|
184
210
|
});
|
|
@@ -209,7 +235,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
209
235
|
const errorSignal = signalsService.signal(null);
|
|
210
236
|
const serviceOptionsSignal = signalsService.signal(config.serviceOptions || DEFAULT_SERVICE_OPTIONS);
|
|
211
237
|
// Internal calculate function
|
|
212
|
-
const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption) => {
|
|
238
|
+
const calculate = async (pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected) => {
|
|
213
239
|
if (!pricingServiceSelections.length) {
|
|
214
240
|
paymentDetailsSignal.set(null);
|
|
215
241
|
return;
|
|
@@ -217,7 +243,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
217
243
|
isLoadingSignal.set(true);
|
|
218
244
|
errorSignal.set(null);
|
|
219
245
|
try {
|
|
220
|
-
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
|
|
246
|
+
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected);
|
|
221
247
|
paymentDetailsSignal.set(paymentDetails);
|
|
222
248
|
}
|
|
223
249
|
catch (error) {
|
|
@@ -245,16 +271,17 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
245
271
|
// Mode 2: Lazy loading - pricingServiceSelections provided via internal config
|
|
246
272
|
else if (config.pricingServiceSelections?.length) {
|
|
247
273
|
serviceOptionsSignal.set(deriveServiceOptions(config.pricingServiceSelections));
|
|
248
|
-
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption);
|
|
274
|
+
void calculate(config.pricingServiceSelections, config.totalParticipants ?? 1, config.selectedPaymentOption, config.depositSelected);
|
|
249
275
|
}
|
|
250
276
|
// Mode 3: Reactive - observe BookingService signals (client-side only)
|
|
251
277
|
else if (typeof window !== 'undefined' && bookingService) {
|
|
252
|
-
// Computed signal that changes when services, selected time slots, or
|
|
253
|
-
//
|
|
278
|
+
// Computed signal that changes when services, selected time slots, or the
|
|
279
|
+
// consumer's payment choices change (not participants).
|
|
254
280
|
const servicesKey = signalsService.computed(() => {
|
|
255
281
|
const selections = bookingService?.serviceSelections.get() || [];
|
|
256
282
|
const timezone = bookingService?.timezone.get();
|
|
257
283
|
const selectedPaymentOption = bookingService?.selectedPaymentOption.get();
|
|
284
|
+
const depositSelected = bookingService?.depositSelected.get();
|
|
258
285
|
return JSON.stringify({
|
|
259
286
|
selections: selections.map((s) => ({
|
|
260
287
|
serviceId: s.service._id,
|
|
@@ -264,16 +291,18 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
264
291
|
})),
|
|
265
292
|
timezone,
|
|
266
293
|
selectedPaymentOption,
|
|
294
|
+
depositSelected,
|
|
267
295
|
});
|
|
268
296
|
});
|
|
269
297
|
signalsService.effect(() => {
|
|
270
|
-
// Subscribe to servicesKey to trigger effect when service, time
|
|
271
|
-
//
|
|
298
|
+
// Subscribe to servicesKey to trigger effect when service, time slot,
|
|
299
|
+
// or payment choices change.
|
|
272
300
|
servicesKey.get();
|
|
273
301
|
// Read current selections without subscribing (peek)
|
|
274
302
|
const selections = bookingService?.serviceSelections.peek() || [];
|
|
275
303
|
const timezone = bookingService?.timezone.peek();
|
|
276
304
|
const selectedPaymentOption = bookingService?.selectedPaymentOption.peek();
|
|
305
|
+
const depositSelected = bookingService?.depositSelected.peek();
|
|
277
306
|
// For single service mode, take the first selection
|
|
278
307
|
const firstSelection = selections[0];
|
|
279
308
|
const numParticipants = firstSelection?.totalParticipants || 1;
|
|
@@ -288,7 +317,7 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
288
317
|
if (pricingServiceSelections.length) {
|
|
289
318
|
serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
|
|
290
319
|
}
|
|
291
|
-
void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption);
|
|
320
|
+
void calculate(pricingServiceSelections, numParticipants, selectedPaymentOption, depositSelected);
|
|
292
321
|
});
|
|
293
322
|
}
|
|
294
323
|
return {
|
|
@@ -306,10 +335,12 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
|
|
|
306
335
|
* @param params.totalParticipants - Number of participants (default: 1)
|
|
307
336
|
* @param params.selectedPaymentOption - Optional consumer-selected payment option;
|
|
308
337
|
* overrides the service-config heuristic on each line item's `paymentOption`.
|
|
338
|
+
* @param params.depositSelected - Optional deposit-vs-full-upfront choice; `false`
|
|
339
|
+
* resolves a deposit service to full payment. Omit to keep the deposit default.
|
|
309
340
|
*/
|
|
310
341
|
async function loadPaymentConfig(params) {
|
|
311
342
|
try {
|
|
312
|
-
const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, } = params;
|
|
343
|
+
const { pricingServiceSelections, totalParticipants = 1, selectedPaymentOption, depositSelected, } = params;
|
|
313
344
|
if (!pricingServiceSelections.length) {
|
|
314
345
|
return {
|
|
315
346
|
type: 'success',
|
|
@@ -319,7 +350,7 @@ async function loadPaymentConfig(params) {
|
|
|
319
350
|
},
|
|
320
351
|
};
|
|
321
352
|
}
|
|
322
|
-
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption);
|
|
353
|
+
const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, totalParticipants, selectedPaymentOption, depositSelected);
|
|
323
354
|
return {
|
|
324
355
|
type: 'success',
|
|
325
356
|
config: {
|
|
@@ -90,7 +90,7 @@ exports.ServiceService = services_definitions_1.implementService.withConfig()(ex
|
|
|
90
90
|
const initialService = config.service || {};
|
|
91
91
|
serviceSignal = signalsService.signal(initialService);
|
|
92
92
|
}
|
|
93
|
-
const isLoadingSignal = signalsService.signal(!!config.serviceId);
|
|
93
|
+
const isLoadingSignal = signalsService.signal(!config.service && (!!config.serviceId || !!config.serviceSlug));
|
|
94
94
|
const errorSignal = signalsService.signal(null);
|
|
95
95
|
// Default appId to BOOKING_APP_ID if not provided
|
|
96
96
|
const appId = config.appId ?? constants_js_1.BOOKING_APP_ID;
|
|
@@ -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
|
*/
|
|
@@ -535,6 +535,97 @@ export const mockServiceWithDeposit = {
|
|
|
535
535
|
},
|
|
536
536
|
},
|
|
537
537
|
};
|
|
538
|
+
/** Deposit set as a fixed amount under `depositDetails`, the newer field. */
|
|
539
|
+
export const mockServiceDepositDetailsAmount = {
|
|
540
|
+
...mockService,
|
|
541
|
+
_id: 'test-service-deposit-details-amount',
|
|
542
|
+
name: 'Deposit Details Amount Service',
|
|
543
|
+
payment: {
|
|
544
|
+
rateType: RateType.FIXED,
|
|
545
|
+
fixed: {
|
|
546
|
+
price: {
|
|
547
|
+
value: '100.00',
|
|
548
|
+
currency: 'USD',
|
|
549
|
+
},
|
|
550
|
+
depositDetails: {
|
|
551
|
+
amount: {
|
|
552
|
+
value: '25.00',
|
|
553
|
+
currency: 'USD',
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
options: {
|
|
558
|
+
online: true,
|
|
559
|
+
deposit: true,
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
/** Percentage deposit: `options.deposit` set, no amount anywhere on the entity. */
|
|
564
|
+
export const mockServicePercentageDeposit = {
|
|
565
|
+
...mockService,
|
|
566
|
+
_id: 'test-service-percentage-deposit',
|
|
567
|
+
name: 'Percentage Deposit Service',
|
|
568
|
+
payment: {
|
|
569
|
+
rateType: RateType.FIXED,
|
|
570
|
+
fixed: {
|
|
571
|
+
price: {
|
|
572
|
+
value: '10',
|
|
573
|
+
currency: 'USD',
|
|
574
|
+
},
|
|
575
|
+
fullUpfrontPaymentAllowed: true,
|
|
576
|
+
},
|
|
577
|
+
options: {
|
|
578
|
+
online: true,
|
|
579
|
+
inPerson: false,
|
|
580
|
+
deposit: true,
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Mock service with a deposit that may also be paid in full upfront — the only
|
|
586
|
+
* configuration where `booking.depositSelected` may be `false`.
|
|
587
|
+
*/
|
|
588
|
+
export const mockServiceWithDepositFullUpfront = {
|
|
589
|
+
...mockService,
|
|
590
|
+
_id: 'test-service-deposit-full-upfront',
|
|
591
|
+
name: 'Service with Deposit and Full Upfront Allowed',
|
|
592
|
+
payment: {
|
|
593
|
+
rateType: RateType.FIXED,
|
|
594
|
+
fixed: {
|
|
595
|
+
price: {
|
|
596
|
+
value: '100.00',
|
|
597
|
+
currency: 'USD',
|
|
598
|
+
},
|
|
599
|
+
deposit: {
|
|
600
|
+
value: '30.00',
|
|
601
|
+
currency: 'USD',
|
|
602
|
+
},
|
|
603
|
+
fullUpfrontPaymentAllowed: true,
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
/**
|
|
608
|
+
* Mock VARIED-rate service with a deposit (both under `payment.varied`).
|
|
609
|
+
*/
|
|
610
|
+
export const mockServiceVariedWithDeposit = {
|
|
611
|
+
...mockService,
|
|
612
|
+
_id: 'test-service-varied-deposit',
|
|
613
|
+
name: 'Varied Service with Deposit',
|
|
614
|
+
payment: {
|
|
615
|
+
rateType: RateType.VARIED,
|
|
616
|
+
varied: {
|
|
617
|
+
defaultPrice: {
|
|
618
|
+
value: '80.00',
|
|
619
|
+
currency: 'USD',
|
|
620
|
+
},
|
|
621
|
+
deposit: {
|
|
622
|
+
value: '20.00',
|
|
623
|
+
currency: 'USD',
|
|
624
|
+
},
|
|
625
|
+
fullUpfrontPaymentAllowed: true,
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
};
|
|
538
629
|
/**
|
|
539
630
|
* Mock service with in-person payment option
|
|
540
631
|
*/
|