Skip to content

Basket and Checkout

Mehrdad Pajuhaan edited this page Oct 23, 2026 · 1 revision

Basket and Checkout

Baskets are the shopping carts of a Selldone shop. Each customer (or guest) has one open basket per product type: PHYSICAL, VIRTUAL, FILE, SERVICE and SUBSCRIPTION. This page shows how to read the baskets, add and remove items with window.$storefront.basket, keep guest baskets working, and which XAPI endpoints the official Storefront uses for the rest of the checkout.

Read the current baskets

Open baskets come with the shop bootstrap call:

const { shop, baskets } = await window.$storefront.shop.fetchShop();

const physicalBasket = baskets?.find((b) => b.type === "PHYSICAL");
console.log(physicalBasket?.items, physicalBasket?.bill);

The official Storefront calls fetchShop() on start-up, after login, and whenever a basket call asks for a refresh.

Add an item

basket.addItem(
  product_id: number,
  variant_id: number | null,
  count: number,
  options?: {
    currency?: keyof typeof Currency;                 // default: window.$storefront.currency.code
    preferences?: BasketItem.IPreferences | null;     // service preferences, area/volume dimensions…
    vendor_product_id?: number | null;                // 🟣 Marketplace: the vendor offer to buy from
    price_id?: number | null;                         // 🎗️ Subscription: the selected subscription price
  },
): Promise<{
  success: boolean;
  error: boolean;
  error_msg: string | null;
  refresh?: boolean;
  basket: Basket;
  bill: Basket.ICalculatedBill;
}>

count is the quantity of this product (and variant) you want in the basket. The official basket item spinner sends the new total each time the customer changes it.

window.$storefront.basket
  .addItem(product_id, variant_id, count, {
    preferences: preferences,
    vendor_product_id: vendor_product_id, // 🟣 Marketplace 🟣
    price_id: subscription_price_id,      // 🎗️ Subscription
  })
  .then(({ basket, bill, refresh, error, error_msg }) => {
    if (basket) {
      setBasket(basket);       // update your state
      setBasketBill(basket, bill);
    }

    // The backend can return an error AND a corrected basket at the same time
    if (error) showError(error_msg);

    // Important: re-fetch shop and baskets (e.g. an item was removed automatically)
    if (refresh) window.$storefront.shop.fetchShop().then(updateBaskets);
  })
  .catch((error) => showError(error));

Unlike most calls, addItem resolves (instead of rejecting) when the response contains error: true, because the server still returns the up-to-date basket and bill. Always check error and error_msg.

Endpoint: PUT /shops/@{shop}/basket/{product_id} with body { currency, variant_id, count, preferences, vendor_product_id, price_id }.

Remove an item

window.$storefront.basket
  .deleteItem(product_id, variant_id) // optional 3rd arg: { currency }
  .then(({ success, basket, bill }) => {
    setBasket(basket);
    setBasketBill(basket, bill);
  })
  .catch((error) => showError(error));

Endpoint: DELETE /shops/@{shop}/basket/{product_id} with { currency, variant_id }.

Guest baskets

Shoppers do not need to log in to fill a basket (unless the shop disables guest checkout). Guest baskets are identified by a guest code:

  1. fetchShop() returns shop.guest_code for a guest.
  2. Save it and send it as the S-Guest header on every request.
  3. StorefrontSDK.Setup() reads the saved code on the next page load and adds the header for you.

This is what the official Storefront does after fetchShop():

import { StorefrontLocalStorages } from "@selldone/core-js/helper/local-storage/StorefrontLocalStorages";

const { shop } = await window.$storefront.shop.fetchShop();

if (shop.guest_code) {
  // Must be saved in localStorage to be reused in all request headers
  StorefrontLocalStorages.SetShopGuestCode(shop.guest_code);
  window.axios.defaults.headers.common["S-Guest"] = shop.guest_code;
} else {
  // Signed-in customer: no guest header
  delete window.axios.defaults.headers.common["S-Guest"];
}

After the customer signs in, call fetchShop() again so the backend can return their baskets.

The bill

bill (Basket.ICalculatedBill) is calculated by the server and is the source of truth for totals. Render subtotal, discounts, coupon, shipping, tax and the final payable amount from it instead of computing them in the browser. Basket and bill models are documented in Selldone Core.

Checkout

The SDK wraps adding and removing items. The rest of the checkout (receiver info, shipping, coupon, payment) is done with window.XAPI, the URL builder the SDK creates, and window.axios, which already carries all required headers. Each builder only returns a URL; you send the request.

// Example: reload the bill of the physical basket
const url = window.XAPI.GET_BASKET_BILL(window.$storefront.name, "PHYSICAL");
const { data } = await window.axios.get(url);
Step URL builder Endpoint
Item message / preferences POST_BASKET_ITEM_MESSAGE, PUT_BASKET_ITEM_PREFERENCES POST /shops/@{shop}/basket/{product_id}/message, PUT …/preferences
Basket config PUT_SET_BASKET_CONFIG(shop, basket_id) PUT /shops/@{shop}/baskets/{basket_id}/config
Customer location POST_SET_MY_LOCATION(shop, type) POST /shops/@{shop}/baskets/{type}/my-location
Apply a coupon PUT_SET_BASKET_COUPON(shop) PUT /shops/@{shop}/coupon
Apply a discount code PUT_DISCOUNT_CODE(shop) PUT /shops/@{shop}/discount-code
Apply a lottery prize PUT_SET_BASKET_LOTTERY(shop) PUT /shops/@{shop}/lottery
Calculated bill GET_BASKET_BILL(shop, type) GET /shops/@{shop}/basket/{type}/bill
Pay POST_BUY_BASKET(shop, type, gateway) POST /shops/@{shop}/basket/{type}/buy/{gateway_code}
Submit a service request POST_SUBMIT_SERVICE_BASKET(shop, type) POST /shops/@{shop}/basket/{type}/submit
Pay a seller-priced bill POST_PAY_BILL(shop, basket_id, bill_id, gateway) POST /shops/@{shop}/basket/{basket_id}/bills/{bill_id}/pay/{gateway_code}
Order detail GET_ORDER_BASKET_INFO(shop, basket_id) GET /shops/@{shop}/baskets/{basket_id}
Order history GET_MY_ORDERS_HISTORY_PHYSICAL(shop, type) GET /shops/@{shop}/basket/orders-{type}

The full list of more than 150 URL builders, with path parameters and auth notes, is in apis/XAPI.ts. Request body fields are not wrapped by the SDK; take them from the official implementation:

Recommended checkout flow

  1. Load the basket from fetchShop() (or refresh it).
  2. Validate items: stock, variant, vendor, required preferences.
  3. Collect receiver and shipping information and save the basket config.
  4. Load the bill and apply coupon, discount code, offers or lottery prize.
  5. Let the customer choose a payment gateway, then call the buy endpoint (or submit a service basket).
  6. Follow the payment redirect, then show the order page.

Avocado orders

For shops that use Avocado (request-based orders where customers describe the items they want), the SDK wraps the main calls under window.$storefront.avocado: addItem(hash, title, message, link, photo, count), updateReceiverInfo(hash, receiver_info), submitOrder(hash, currency) and getOrder(hash). See the API Reference.

Next steps

Clone this wiki locally