Scheduled Services
Book services through the same cart and checkout as products
Scheduling is part of commerce. A booking service is what is booked and sets what every booking of it is: its segments and buffers, slot interval, booking window, how late a customer may still change a booking, its reminders and its tax category. A booking resource is who or what does it, with its timezone, capacity and working hours. A booking offering says one resource provides one service, inside the resource’s hours or its own narrower ones, at a store location, and is priced like any sellable. Checkout makes one order whose booking lines are the bookings: products and bookings share one cart, one quote, one payment and one order.
See E-shop for creating services, resources and offerings.
Setup
Every storefront read and cart names its catalog:
import { initialize } from "arky-sdk/storefront";
import { createOrderToken } from "arky-sdk/utils";
const arky = initialize("arky_pk_...", {
locale: "en",
market: "hr",
createVisitorIdentity: () => ({
customer_id: crypto.randomUUID(),
session_id: crypto.randomUUID(),
}),
createSessionId: () => crypto.randomUUID(),
createId: () => crypto.randomUUID(),
createOrderToken,
});
const { items: [catalog] } = await arky.eshop.catalog.find();
const cart = arky.eshop.cart({ buyer: { type: "customer" }, catalog_id: catalog.id });
await cart.load();
Service Flow
eshop.bookingCalendar({ buyer, catalog_id }) keeps the service, its availability, the month
calendar and the selected slot together:
const { items: services } = await arky.eshop.bookingService.find({ catalog_id: catalog.id, limit: 20 });
const calendar = arky.eshop.bookingCalendar({ buyer: { type: "customer" }, catalog_id: catalog.id });
await calendar.select({ booking_service_id: services[0].id });
calendar.selectFirstAvailableDate();
const { slots } = calendar.state.get();
calendar.selectSlot({ slot: slots[0] });
await calendar.addToCart({ slot: slots[0] });
addToCartadds the slot as a booking line to the cart store of the calendar’s buyer and catalog, the oneeshop.cart({ buyer, catalog_id })answers, loading or creating that cart first. To book at a company’s or location’s price, open the calendar for that buyer. The line is in that cart store’sbooking_itemsand on the server cart.- Each line id comes from your
createId; passidwith the slot to pick it yourself. - A slot that spans days, such as a stay of several nights, is
is_multi_day: itstime_textis the date range, and once it is chosen its days areis_in_rangein the calendar.
Building your own scheduler? Add the line directly:
await cart.addBooking({
booking_offering_id: offeringId,
requested_interval: { from, to },
capacity_units: 1,
});
- Availability is computed per offering in its resource’s timezone, for at most 60 days per
request. Its days are
"YYYY-MM-DD"strings, and each slot says how many spots are left. After a resource’s timezone changes, a slot whose time reaches a day it keeps in the earlier timezone shows no spots until that day has passed. - Reading availability reserves nothing; checkout makes the binding check.
- A booking’s interval covers the service’s segments. Its buffers hold the resource before and after but aren’t part of the appointment.
Quote And Checkout
const checkout = arky.eshop.checkoutFlow({ buyer: { type: "customer" }, catalog_id: catalog.id });
await checkout.open();
checkout.setContact({ contact_email: "ana@example.com" });
const state = await checkout.place({ return_url: (orderId) => `${location.origin}/bookings/${orderId}` });
console.log(state.step, state.placed?.number);
open loads the cart the booking was added to and quotes it, and place checks the quote out under
an order id and token from your factories, with the quote’s suggested payment option. See
Checkout for the steps, mounting the payment and recovering a
lost answer. The cart store’s checkout({ contact_email, payment }) places the order directly when
you build the page yourself.
A service-only order needs no shipping address or method. The quote’s ready is false while a
booking is no longer available (a booking_unavailable blocker). The blocker and the line’s
availability carry a code that says why: BOOKING_OFFERING.OUTSIDE_WINDOW,
BOOKING_OFFERING.NOT_A_SLOT, BOOKING_OFFERING.OUTSIDE_HOURS, ORDER.BOOKING_UNAVAILABLE (no free
place) or ORDER.BOOKING_TIMEZONE_CHANGED (the time reaches a day the resource keeps in its earlier
timezone). Quote again after the buyer picks another slot.
A confirmed booking sends the store’s booking_confirmed email, and each of the service’s
reminder_offsets_minutes sends booking_reminder before it, in the order’s language. A reminder
goes only while the booking is confirmed and upcoming, for the start it was planned for.
Customer Changes
The service’s customer_changes says what a customer may do with their own booking on the
storefront: not_allowed, or until_before_start with a number of minutes. Within it, the signed-in
customer, or a guest holding the order’s link, can cancel or move the booking:
await arky.eshop.order.rescheduleBookingItem({
order_id: order.id,
line_item_id: booking.id,
expected_updated_at: order.updated_at,
starts_at: newSlot.from,
});
await arky.eshop.order.cancelBookingItem({
order_id: order.id,
line_item_id: booking.id,
credit_id: crypto.randomUUID(),
expected_updated_at: order.updated_at,
});
- A move goes to another start of the same offering, which must be bookable like a new booking. The line keeps its units and money, and keeps each earlier start, up to 20 moves.
- Cancelling names the id of the credit it creates, picked by your code: money owed back to the buyer, refunded through the payment.
Managing Bookings
Staff act on a booking line with its line id and the order’s expected_updated_at:
import { createAdmin } from "arky-sdk/admin";
import { orderBookingItems } from "arky-sdk";
const sdk = createAdmin({ apiUrl: "https://api.arky.io", secretKey: process.env.ARKY_SECRET_KEY });
const order = await sdk.eshop.order.get({ store_id: storeId, id: orderId });
const booking = orderBookingItems(order)[0];
await sdk.eshop.order.attendBookingItem({
store_id: storeId,
order_id: order.id,
line_item_id: booking.id,
expected_updated_at: order.updated_at,
});
attendBookingItemandmarkBookingItemNoShowrecord who came, once the booking has started (409 ORDER.BOOKING_NOT_STARTEDbefore), on a confirmed booking whose attendance is still open.rescheduleBookingItemmoves a booking until it starts, andcancelBookingItemcancels it until it ends, whatever the service’scustomer_changessays. Both carrynotify_customer: a moved booking sendsbooking_confirmedagain and a cancelled onebooking_cancelled, unless it isfalse.sdk.eshop.order.findBookingItemslists bookings across orders, by service, resource, time and status (pending,confirmed,attended,no_show,cancelled).- The server’s clock decides every boundary. Show the actions and let the server refuse when a boundary isn’t met.
Cancelling a booking frees its capacity. Money goes back only through the credit’s refund; a late payment never brings back an expired or cancelled booking.