Setting the file. One moment.
Rentals · Wix Headless Templates · wix/skills · Skills Docs
ContentsBack to the top of the page This file
Number 16.659
Position 659 of 828
Type TypeScript
Size 8 KB
Lines 153 rentals/rest/ rentals.ts
TypeScript · 153 lines · 8 KB
12
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
13 import { wixRequest } from "../../shared/rest/client.js" ;
14 import { imgSrc } from "../../shared/rest/media.js" ;
15 import {
16 FALLBACK_FIELDS,
17 RENTALS_APP_ID,
18 RENTALS_FORM_ID,
19 RENTALS_PAGE_SIZE,
20 bookingIdOf,
21 bookingOptions,
22 bookingRequest,
23 cartIdOf,
24 cartRequest,
25 checkoutRedirectRequest,
26 checkoutRequired,
27 confirmedResult,
28 contactOf,
29 dailyEndOptions,
30 defaultTimeZone,
31 endOptionsRequest,
32 friendlyError,
33 isRental,
34 nextSlotsCursor,
35 previewRequest,
36 startsRequest,
37 toDetail,
38 toFormFields,
39 toHourlyEndOptions,
40 toQuote,
41 toStartOptions,
42 toSummary,
43 type Raw,
44 type StartWindow,
45 } from "../app/wix/rentals/rentals-core.js" ;
46 import type { EndOption, RentalDetail, RentalFormField, RentalQuote, RentalResult, RentalSummary, StartOption } from "../app/wix/rentals/types.js" ;
47
48 export { RENTALS_APP_ID, RENTALS_FORM_ID, RENTALS_PAGE_SIZE, dailyEndOptions, defaultTimeZone };
49
50 export interface RentalsPage {
51 items : RentalSummary [];
52 hasMore : boolean ;
53 }
54
55 /**
56 * POST /bookings/v2/services/query { query: { filter: { appId, hidden: false }, paging: { limit, offset } } }
57 */
58 export async function fetchRentals ({ limit = RENTALS_PAGE_SIZE , offset = 0 } : { limit ?: number ; offset ?: number } = {}) : Promise < RentalsPage > {
59 const res = await wixRequest < Raw >( "/bookings/v2/services/query" , { body: { query: { filter: { appId: RENTALS_APP_ID , hidden: false }, paging: { limit, offset } } } });
60 const items = ((res.services ?? []) as Raw []). filter (isRental). map (( r ) => toSummary (r, imgSrc));
61 const total = Number (res.pagingMetadata?.total ?? 0 );
62 return { items, hasMore: total ? offset + (res.services?. length ?? 0 ) < total : false };
63 }
64
65 /** POST /bookings/v2/services/query filtered on mainSlug.name — null when not found or not a rental. */
66 export async function fetchRentalBySlug ( slug : string ) : Promise < RentalDetail | null > {
67 const res = await wixRequest < Raw >( "/bookings/v2/services/query" , { body: { query: { filter: { "mainSlug.name" : slug, appId: RENTALS_APP_ID , hidden: false }, paging: { limit: 1 } } } });
68 const raw = (res.services ?? [])[ 0 ] as Raw | undefined ;
69 return raw && isRental (raw) ? toDetail (raw, imgSrc) : null ;
70 }
71
72 export interface StartsPage {
73 options : StartOption [];
74 timeZone : string | null ;
75 }
76
77 /** POST /service-availability/v2/time-slots (cursor followed) — the starts a customer can pick. */
78 export async function fetchStarts ( rental : Pick < RentalDetail , "id" | "unit" | "resourceTypeId" >, window : StartWindow = {}) : Promise < StartsPage > {
79 const raws : Raw [] = [];
80 let timeZone : string | null = null ;
81 let cursor : string | undefined ;
82 do {
83 const res = await wixRequest < Raw >( "/service-availability/v2/time-slots" , { body: startsRequest (rental, { ... window, cursor }) });
84 raws. push ( ... ((res.timeSlots ?? []) as Raw []));
85 timeZone ??= res.timeZone ?? null ;
86 cursor = nextSlotsCursor (res) ?? undefined ;
87 } while (cursor);
88 return { options: toStartOptions (raws, rental.unit), timeZone };
89 }
90
91 /**
92 * HOURLY: POST /service-availability/v2/time-slots/end-options { serviceId, localStartDate, timeZone, location }
93 * (the response field is `endOptions`). DAILY: walked locally from the starts already fetched.
94 */
95 export async function fetchEndOptions ( rental : Pick < RentalDetail , "id" | "unit" | "minUnits" | "maxUnits" >, start : StartOption , { timeZone , availableDayKeys = [] } : { timeZone ?: string | null ; availableDayKeys ?: string [] } = {}) : Promise < EndOption []> {
96 if (rental.unit === "DAY" ) return dailyEndOptions (start, availableDayKeys, rental);
97 const res = await wixRequest < Raw >( "/service-availability/v2/time-slots/end-options" , { body: { serviceId: rental.id, ... endOptionsRequest (start, timeZone, "id" ) } });
98 return toHourlyEndOptions (res, start);
99 }
100
101 /** POST /bookings/v2/pricing/preview { bookingLineItems: [...] } — falls back to rate × length. */
102 export async function fetchQuote ( rental : Pick < RentalDetail , "id" | "rateAmount" | "currency" | "unit" >, start : StartOption , end : EndOption , timeZone : string = defaultTimeZone ()) : Promise < RentalQuote > {
103 try {
104 const res = await wixRequest < Raw >( "/bookings/v2/pricing/preview" , { body: { bookingLineItems: previewRequest (rental, start, end.endLocal, timeZone) } });
105 return toQuote (res, rental, end.units);
106 } catch {
107 return toQuote ( null , rental, end.units);
108 }
109 }
110
111 /** GET /form-schema-service/v4/forms/{id}/summary (+ the schema for `required`); contact basics when unreadable. */
112 export async function fetchRentalForm ( formId : string | null ) : Promise < RentalFormField []> {
113 const id = encodeURIComponent (formId ?? RENTALS_FORM_ID );
114 try {
115 const [ summary , form ] = await Promise . all ([
116 wixRequest < Raw >( `/form-schema-service/v4/forms/${ id }/summary` , { method: "GET" }),
117 wixRequest < Raw >( `/form-schema-service/v4/forms/${ id }` , { method: "GET" }). catch (() => null ),
118 ]);
119 return toFormFields (summary?.formSummary, form?.form);
120 } catch {
121 return FALLBACK_FIELDS ;
122 }
123 }
124
125 /**
126 * POST /bookings/v2/bookings → POST /ecom/v2/carts → POST /ecom/v2/carts/{id}/calculate →
127 * POST /headless/v1/redirect-session (paid) | POST /ecom/v2/carts/{id}/place-order (free / offline).
128 */
129 export async function rentResource ( rental : RentalDetail , start : StartOption , end : EndOption , formValues : Record < string , unknown >, timeZone : string = defaultTimeZone ()) : Promise < RentalResult > {
130 let created : Raw ;
131 try {
132 created = await wixRequest < Raw >( "/bookings/v2/bookings" , { body: { booking: bookingRequest (rental, start, end.endLocal, timeZone, "id" ), ... bookingOptions (formValues) } });
133 } catch (e) {
134 throw new Error ( friendlyError (e instanceof Error ? e.message : String (e)));
135 }
136 const bookingId = bookingIdOf (created);
137 if ( ! bookingId) throw new Error ( "The rental couldn't be booked — the time may have just been taken." );
138
139 const newCart = await wixRequest < Raw >( "/ecom/v2/carts" , { body: cartRequest ([bookingId], contactOf (created), start.location?.type === "BUSINESS" ? start.location.id : null ) });
140 const cartId = cartIdOf (newCart);
141 if ( ! cartId) throw new Error ( "The rental couldn't be reserved — please try again." );
142
143 const calc = await wixRequest < Raw >( `/ecom/v2/carts/${ cartId }/calculate` , { body: {} });
144 if ( checkoutRequired (rental, calc)) {
145 const origin = typeof window !== "undefined" ? window.location.origin : "" ;
146 const session = await wixRequest < Raw >( "/headless/v1/redirect-session" , { body: checkoutRedirectRequest (cartId, origin) });
147 const url = session?.redirectSession?.fullUrl;
148 if ( ! url) throw new Error ( "Checkout couldn't start — please try again." );
149 return { kind: "redirect" , url };
150 }
151 const order = await wixRequest < Raw >( `/ecom/v2/carts/${ cartId }/place-order` , { body: {} });
152 return confirmedResult (bookingId, order);
153 }