Purpose#
The Services reusable is the theme’s client-side service layer.It exposes a global object servicesreusabledefault (defined in Reusables/Services/Default.js) that provides a consistent set of async functions for calling the storefront backend (/api/...) and coordinating cross-cutting behaviors like:Standardized response normalization
Request cancellation (Axios cancel tokens)
Updating Alpine stores (cart, checkout, modal) after successful calls
Auxiliary flows (file uploads, payment redirects/form submits, GA events)
This file is effectively the theme’s “SDK”. Components and reusables call methods on servicesreusabledefault instead of repeating raw axios/fetch calls.Note: this documentation refers to specific methods by name (for example servicesreusabledefault.getShoppingLists()). Avoid using wildcard references like “all methods on servicesreusabledefault”, because the docs audit may treat them as tokens.
Where it's rendered#
This reusable is typically rendered once in the layout, so the global object is available to all components.This reusable is rendered without Liquid parameters.Template behavior (Liquid + Alpine)#
This reusable does not render UI.Its role is to register the global JavaScript object servicesreusabledefault, which is then consumed by other components and reusables.JavaScript#
Standard function conventions#
1) Pattern: Axios + normalized return#
Most methods follow this structure:1.
Call an endpoint using axios.get/post/put/delete.
2.
Normalize the response via:getAxiosResponse(response, context)
3.
Return the normalized object:
Log with a method-specific console.error('<method> failed:', e)
Return e.response (raw Axios error response), or return the cancel error when using cancel tokens.
2) Context strings#
getAxiosResponse is always called with a stable context string (usually the method name), e.g.:this.getAxiosResponse(response, 'addToCart')
This makes error logs searchable and consistent.3) Cancellation#
Some methods cancel the previous request before starting a new one, using:axios.CancelToken.source()
The service caches Axios CancelToken sources on internal properties of the services object.4) Side effects#
Several methods intentionally do more than “just call an API”. Common side effects include:Updating localStorage (cartToken, checkoutToken, cartData)
Alpine.store('cart').update(...)
Alpine.store('checkout').set(...)
Alpine.store('checkout').validateStep(...)
Alpine.store('checkout').isUpdating = true/false
Alpine.store('modal').shouldReinitialize = true
5) fetch vs axios#
Some search endpoints use fetch(...) and return raw JSON (not the normalized wrapper). These are exceptions and are documented explicitly below.API reference (grouped by category)#
General#
getAxiosResponse(response, context = "")#
Normalizes an Axios response into a stable shape.response: Axios response object (or falsy).
context: optional string used in logs.
If response.status is not 2xx, logs a structured error and returns:{ success: false, status, error: response }
{ success: true, status, data: response.data }
uuidv4()#
Generates a random GUID-like string using crypto.getRandomValues.setShopranosCookie(value, expiresAt)#
expiresAt: expiration date/time.
normalized response via getAxiosResponse.
internalUploadFile(file, url, endpoint = null)#
Uploads a file in chunks (200000 bytes per chunk) using fetch and FormData.endpoint: optional extra query param ?endpoint=....
guid (string) for the upload session, or null on failure.
Uses uuidv4() internally.
Alpine.store('modal').shouldReinitialize = true.
Returns normalized response.data: request payload (shopping list creation model).
Returns normalized response.DELETE /api/ShoppingList/{alias}.alias: shopping list alias.
Returns normalized response.PUT /api/ShoppingList/{list.alias}.list: full list object; must include alias.
Returns normalized response.Imports items into an existing list.1.
Uploads the file via internalUploadFile(file, '/api/file') → returns guid
2.
POST /api/ShoppingList/import-items/{alias}/{guid}
Returns normalized response.POST /api/ShoppingList/export-items/{alias} with { responseType: 'blob' }.
Creates a browser download named ShoppingList-{alias}.xlsx.
Returns normalized response.Wishlist#
clearWishlist()#
DELETE /api/wishlist/items.Returns normalized response.addItemToWishlist(productId)#
POST /api/wishlist/items/products/{productId}.Returns normalized response.removeItemFromWishlist(productId)#
DELETE /api/wishlist/items/products/{productId}.Returns normalized response.Navbar (Search + announcements)#
findProductsByCriteria(searchCriteria)#
Performs a product search using fetch.Endpoint: GET /api/products/liquid with query string params.
searchCriteria: object of query params. Only non-null/undefined values are included.
Returns parsed JSON on success.
Returns null and logs on non-OK response.
findProductsByCriteriaExtended(searchCriteria)#
Like findProductsByCriteria but uses:GET /api/products/search-extended
searchCriteria: object of query params.
Throws an Error when the response is not OK.
setReadAnnouncementIds(ids)#
ids: array/payload containing announcement ids.
Returns normalized response.sendEmail(data)#
Sends the contact form to the backend.Uploads file fields first (where field.type == 4).
data: contact form payload; expected to include fields[].
Returns normalized response.internalUploadFiles(fields)#
Uploads contact form files for a set of form fields.fields: list of field definitions.
Reads files from DOM input document.getElementById(field.name).files.
Uploads each file via uploadContactFormFile(file).
Writes back field.value as a comma-joined list of returned GUIDs.
Uploads a single contact-form file.Calls internalUploadFile(file, '/api/file/contact').
Profile (Account, addresses, managed users)#
accountForgotPassword(data)#
POST /api/account/forgotpassword.data: forgot password payload.
Returns normalized response.changePassword(data)#
POST /api/account/changepassword.data: change password payload.
Returns normalized response.changePasswordProfile(data)#
POST /api/account/changeprofilepassword.data: profile password change payload.
Returns normalized response.updateUserProfile(data)#
PUT /api/account/updateuserinfo.data: user profile payload.
Returns normalized response.createUserAddress(data)#
POST /api/customer/addresses.Returns normalized response.updateUserAddress(data, addressId)#
PUT /api/customer/addresses/{addressId}.Returns normalized response.deleteUserAddress(addressId)#
DELETE /api/customer/addresses/{addressId}.Returns normalized response.setUserAddressAsPrimary(addressId)#
POST /api/customer/addresses/setdefault/{addressId}.Returns normalized response.accountLogin(data)#
Side effects on success (status == 200):GET /api/cart/user to retrieve a user cart.
Sets localStorage.cartToken, cookie cartToken, and localStorage.cartData.
Returns normalized response.accountRegister(data)#
POST /api/account/register.Returns normalized response.registerUser(data, token)#
POST /api/account/confirmInvitation/{token}.data: registration payload.
Returns normalized response.deleteUser(user)#
DELETE /api/managedusers/{user.id}.user: object with at least id.
Returns normalized response.updateUser(user)#
PUT /api/managedusers/{user.id}.user: user object (must include id).
Returns normalized response.inviteUser(data)#
POST /api/managedusers/invite.Returns normalized response.fetchUsers()#
GET /api/managedusers with params:Returns normalized response.uploadCompanyLogo(file, customerId)#
1.
Uploads the file via internalUploadFile(file, '/api/file', '/api/files/upload/logo/{customerId}')
Returns normalized response.deleteCompanyLogo()#
DELETE /api/customer/logo.Returns normalized response.Cart#
Adds a single item to the cart.POST /api/cart/{cartToken}/add
variantId: product variant id.
quantity: quantity to add.
finalPrice: used for GA payload (price).
originalPrice: used for GA payload (initialPrice).
showPopUp: passed to $store.cart.update(...) to control UI feedback.
Preconditions / side effects:Cancels previous cart update via cancelTokenSource.
Ensures localStorage.checkoutToken exists (creates one via uuidv4() and sets cookie checkoutToken).
Updates localStorage.cartToken + cookie cartToken.
If $store.cart exists, calls:Alpine.store('cart').update(cartData, [{ ...addedItem, addedQuantity: quantity }], showPopUp)
If cart has items, sends GA event addToCart.
Returns normalized response.Adds multiple items to cart in one request.POST /api/cart/{cartToken}/addmultiple
variations: array of objects with at least:showPopUp: passed to cart store update.
Updates $store.cart once with successfully added products.
Sends GA addToCart event.
Returns normalized response.updateProductCart(cartData, cancelToken)#
PUT /api/cart/{cartToken}
cartData: cart update payload.
cancelToken: Axios cancel token.
Calls Alpine.store('cart').update(cartData).
Returns normalized response (or cancel error).updateProductQuantity(data, cancelToken)#
Validates/resolves requested quantity.POST /api/cart/{cartToken}/resolveQuantity
cancelToken: Axios cancel token.
Returns normalized response (or cancel error).clearCart()#
DELETE /api/cart/{cartToken}/all
Sends GA removeFromCart based on stored localStorage.cartData.
Calls Alpine.store('cart').update(cartData).
Returns normalized response.removeFromCart(variantId)#
Removes an item by variant id.DELETE /api/cart/{cartToken}/{variantId}
variantId: product variant id.
Sends GA removeFromCart for the removed line.
Calls Alpine.store('cart').update(cartData).
Returns normalized response.getCartValidation()#
Fetches calculated cart validation and returns a simplified error summary.GET /api/cart/calculated/{cartToken}/
Cancels the previous calculation request using calculatecartCancelTokenSource.
On success: { isSuccess, lines: [{ type, count, lineIds[] }] }
On failure: returns e.response.
Checkout#
updateCheckout(data, checkoutStep = null)#
POST /api/checkout/update/{checkoutToken}
data: checkout update payload.
checkoutStep: optional string; when present, triggers step validation.
Injects data.cartItems = Alpine.store('cart').items.
Toggles Alpine.store('checkout').isUpdating.
Alpine.store('checkout').set(returnResponse.data)
Alpine.store('checkout').validateStep(checkoutStep, true)
Alpine.store('checkout').globalError = true
validateStep(checkoutStep, false)
Returns normalized response.proceedToCheckout(data)#
High-level checkout continuation.Calls updateCheckout(data).
If there is a payment provider, calls initiatePayment(checkoutData).
Otherwise calls completeCheckout().
Returns normalized response.initiatePayment(data)#
Initiates payment and performs provider-specific redirects.POST /api/payment/initiate/{checkoutToken}
data: checkout data; must contain data.payment.provider.
Redirects the browser when redirectUrl is returned for providers like PayPal/JCC/Stripe/Nexi.
Builds and auto-submits provider forms for EDPS/Epay/CardLink.
Injects and executes HTML snippet for Klarna.
Otherwise falls back to completeCheckout().
Redirects to /checkout/error.
completeCheckout()#
POST /api/checkout/complete/{checkoutToken}.
Returns normalized response.fetchPostalCodes(countryCode)#
GET /api/location/postalcodes/{countryCode}.
countryCode: country code.
Returns normalized response.Thank You#
triggerOrderViewedCleanup(orderId)#
Triggers analytics cleanup after the thank-you page is viewed.POST /api/order/{orderId}/analytics/thank-you-page-viewed
Returns normalized response.Global Alpine stores#
This reusable may update these stores when they exist (depending on the called method):$store.toast (indirectly; most callers handle toasts themselves)
Services / API calls#
This reusable is the services layer itself.It calls backend endpoints under /api/... via axios (mostly) and fetch (in specific search endpoints).Dependencies#
JS: Reusables/Services/Default.js
Translations: Reusables/Services/Default.json
Notes#
servicesreusabledefault includes both:“thin” API wrappers (call endpoint + normalize)
“orchestrators” that update stores/local storage and trigger GA events.
Modified at 2026-05-04 12:18:01