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 servicesreusabledefault.*(...) instead of repeating raw axios/fetch calls.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 sources on:servicesreusabledefault.cancelTokenSource
servicesreusabledefault.calculatecartCancelTokenSource
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-04-14 13:18:56