Back to Arky

E-commerce Store

Build a cart-first storefront with products, quotes, checkout, and payments

Arky storefronts are cart-first. The browser should not keep its own checkout model in local storage; it should use the SDK storefront store, load the server cart, and let checkout create the order from that cart.

Store Setup

Create one store for the app and call setContext when the storefront boots or when locale/market context changes.

import { initialize, mountCheckoutAction } from "arky-sdk/storefront";

export const arky = initialize(import.meta.env.PUBLIC_ARKY_PUBLISHABLE_KEY, {
	locale: "en",
	market: "us",
});

arky.setContext({ locale: "en" });
await arky.action.track({
	key: "page.view",
	payload: { path: location.pathname },
});
await arky.eshop.cart.load();

eshop.cart.load creates or resumes the current arky_vst_... visitor session and loads the backend cart. Public catalog reads do not create a visitor session. Anonymous visitors still receive a contact record when they begin a stateful flow, so the cart is always tied to visitor context.

Product Listing

Use the e-shop module on the store. The call updates arky.eshop.state, so a page can either use the returned value or subscribe to the store state. Variant order is semantic: variants[0] is the default configured by the store operator.

const { items: products } = await arky.eshop.product.list({
	limit: 24,
});

const first = products[0];
const variant = first.variants[0];
const price = arky.utils.formatPrice(variant.prices);

Product Detail

Adding a product should update the server cart immediately. The local UI gets refreshed from the cart response.

const product = await arky.eshop.product.get({ id: productId });
const variant = product.variants[0];

await arky.eshop.cart.addProduct(product, variant, 1);

Cart UI

The cart store exposes Nano Store atoms, so React, Svelte, Vue, Astro islands, or plain browser code can subscribe without a framework-specific adapter.

const unsubscribe = arky.eshop.cart.snapshot.subscribe((snapshot) => {
	console.log(snapshot.item_count);
	console.log(snapshot.product_items);
	console.log(snapshot.booking_items);
});

const status = arky.eshop.cart.status.get();
const currentQuote = arky.eshop.cart.quote_result.get();

unsubscribe();

Common cart methods live directly under eshop.cart.

await arky.eshop.cart.setProductQuantity(item.id, 2);
await arky.eshop.cart.removeProduct(item.id);
await arky.eshop.cart.applyPromoCode("SUMMER20");
await arky.eshop.cart.selectShippingMethod("standard");

Quote

Quote calculation also goes through the cart. Pass addresses, promo codes, forms, and shipping method when they are available.

const quote = await arky.eshop.cart.quote({
	shipping_method_id: "standard",
	promo_code: "SUMMER20",
	shipping_address: {
		name: "Jane Doe",
		street1: "456 Contact Ave",
		city: "New York",
		state: "NY",
		postal_code: "10001",
		country: "US",
	},
});

console.log(quote?.money.total);

Money And Tax

Quote and checkout totals come from the backend cart. Storefronts should render the returned quote/order values instead of recalculating tax in the browser.

The current flow is:

item unit price * quantity
minus promo-code discount
= taxable base
plus item tax when the market is exclusive
plus shipping
plus shipping tax when the selected shipping method is taxable
= order total

Markets choose the tax mode:

  • exclusive: prices are net prices and tax is added on top.
  • inclusive: prices already include tax and the tax amount is backed out for reporting.

Zones provide the configured tax rate and available payment/shipping methods. Promo codes are discounts; they are not stored value.

Checkout

Checkout creates the order from the cart. Storefronts should not call an order checkout endpoint directly.

const result = await arky.eshop.cart.checkout({
	payment_method_key: "credit_card",
	return_url: window.location.href,
});

console.log(result.order_id, result.number);

if (result.payment_action.type === "stripe_embedded_checkout") {
	const mounted = await mountCheckoutAction(
		result.payment_action,
		"#stripe-checkout",
	);
	// Call mounted.destroy() when the checkout container is removed.
}
Note

Card checkout stays inside the storefront. The server returns a short-lived, closed stripe_embedded_checkout action containing the connected account’s publishable key and Checkout client secret. Mount it with the shared SDK controller; never construct Stripe account context yourself. Arky confirms payment from the connected account’s signed webhook. The browser completion callback is navigation and presentation state, not proof of payment.

Order State

Orders expose separate lifecycle and fulfillment summary, while payment is an explicit child:

  • status: order lifecycle: pending, confirmed, partially_cancelled, or cancelled.
  • payment_id: the identity of the one separate OrderPayment; use the order-scoped payment endpoint when payment state is needed.
  • fulfillment_status: delivery/work summary, such as unfulfilled, in_progress, partially_fulfilled, fulfilled, or not_required.

Physical products use shipping_lines[] on the order for the buyer-selected shipping charge. Product, booking, digital-product, payment, fulfillment, shipment, refund, and dispute records are fetched through explicit order-scoped APIs. Those resources have independent identity or lifecycle and are never silently hydrated onto a normal Order response.

Booking availability is read from a compact ProviderSchedule record for the provider’s local day. OrderBooking remains authoritative; the schedule is a transactionally maintained, rebuildable capacity projection so storefronts do not scan orders or bookings to render availability.

Mixed Carts

Products and scheduled service lines share the same cart. Product storefronts usually use eshop.cart.addProduct, while service pages can use the service controller and checkout through the same cart.

await arky.eshop.service.initialize();
await arky.eshop.service.select(service);
await arky.eshop.service.findFirstAvailable();

const serviceState = arky.eshop.service.state.get();
await arky.eshop.service.selectTimeSlot(serviceState.slots[0]);
await arky.eshop.service.addToCart();

const order = await arky.eshop.cart.checkout({
	payment_method_key: "cash",
});

Storefront Rules

  • Use initialize as the storefront entry point.
  • Use the backend cart as the source of truth.
  • Use eshop.cart.quote before presenting totals.
  • Use eshop.cart.checkout to create orders.
  • Use arky.client only for unusual low-level cases that are not represented by the store yet.