Checkout Flow Tutorial

This tutorial walks you through the core building blocks of a checkout flow in Crystallize. You’ll cover three essential steps:

  • Fetching products from the Discovery API
  • Adding items to a cart using the Shop API
  • Creating an order from a placed cart using the Shop API

The examples focus on GraphQL queries rather than framework or language specifics. You’ll use the same queries whether you’re building with JavaScript, TypeScript, PHP, or something else. This keeps the concepts portable and easy to adapt to your stack.

Order view in Crystallize

Fetching a Product

The checkout flow starts by fetching product data from Crystallize using the Discovery API. This API is optimized for read performance and is typically used by storefronts and checkout experiences. By default, the endpoint is publicly accessible, but you can secure it by adding authentication if your setup requires it.

Discovery API endpoint:

https://api.crystallize.com/your-tenant-identifier/discovery

Below is an example GraphQL query that fetches the minimal product data needed to support a basic checkout flow:

query GetProduct {
  browse {
    product(
      language: en
  	  path: "/products/outdoor-furniture/palissade-lounge-sofa",
    ) {
      hits {
        name
        path
      	variants{
          name
          sku
 					retailPrice:defaultPrice
          salesPrice         
        }
      }
    }
  }
}

This query requires two variables:

  • language; the language context for the product content
  • path; the catalogue path that uniquely identifies the product

You can also pass a version argument. If you don’t, Crystallize returns the published version by default. Supplying version: draft lets you preview unpublished changes, which is useful during development or content review.

The query returns high value checkout data, including all product variants. For each variant, you fetch the variant name, SKU, retail price, and sales price. This is typically enough to display product options and prepare items for the cart.

From here, you can expand the query to include richer data such as images, attributes, stock information, or subscription plans, depending on what your checkout experience needs.

Hydrating the Cart

Once you’ve fetched product data from the catalogue, the next step is to create and hydrate a cart using the Shop API. This is where product data turns into an orderable structure with prices, taxes, discounts, and totals calculated for you.

Unlike the Discovery API, the Shop API requires authentication. Make sure you’ve set up access before continuing; the Shop API introduction covers this in detail.

Shop API endpoint:

https://shop-api.crystallize.com/your-tenant-identifier/cart

Before you start

Before hydrating a cart with items, ensure that:

  • The product variant is published in the language you’re using
  • The variant has a price for the selected price variant and currency

Both language and pricing behavior are defined through the context field in the hydrate mutation. The full list of supported context fields is documented in the Shop API introduction, but the example below shows the most commonly used ones for a checkout flow.

mutation HydrateCartContext{
  hydrate(
    input: {
      customer: {        
        identifier:"bard@example.com"
        isGuest: false
      }
      
      context: {
        language: "en"
      
      price: {
        #b2b vs b2c pricing
        pricesHaveTaxesIncludedInCrystallize: true
        decimals: 4,        
        currency: "EUR"        

        # Which price is my goto list priceVariant?
        selectedVariantIdentifier: "sales"
        # Which price should discounts be compared to?
        compareAtVariantIdentifier: "default"
        # If membership price is not set, where should I fallback to?
        fallbackVariantIdentifiers: "default"
      }
      }
      items:[
      {sku: "palissade-lounge-sofa-iron-red", quantity: 1, type:  standard, group: "Outdoor" }
      {sku: "palissade-bar-stool-sky-grey", quantity: 1, type:  standard, group: "Outdoor"}
      {sku: "palissade-bar-stool-anthracite", quantity: 1, type:  standard, group: "Outdoor"}

      {sku: "monstera-deliciosa-medium", quantity: 2, type:  standard, group: "Plants"}
      {sku: "pilea-large", quantity: 2, type:  standard, group: "Plants"}
    ]}) {
	id
  state
  isStale
  isExpired
  appliedPromotions{
    identifier
    name
    mechanism{
      type
      value
    }
  }
	items {
    name
  	variant{
      sku
      yourPrice:price{
        gross
        net
        taxAmount
        taxPercent
      }      
      compareAtPrice{
        gross
        net
      }
      
      product{
        name
        
      }
    }
  	price {
    	net
    	gross
    	taxAmount      
      discounts{
        percent
        amount
      }
  	}
	}
  total{
  	net
    gross
 		discounts{
      percent
      amount
    } 
  }
  }
}

Understanding the hydrate mutation

The hydrate mutation creates or updates a cart and recalculates everything based on the provided context and items.

SKUs and items
The SKU is the most important identifier. It tells the Shop API exactly which product variant from the catalogue should be added to the cart. SKUs are globally unique in Crystallize. 

At minimum, you only need to provide a SKU; this adds one unit of that variant. In this example, quantity, type, and group are also defined for clarity.

Automatic price calculations
You only send SKU and quantity. The Shop API calculates:

  • Net price
  • Gross price
  • Tax amount and tax percentage
  • Discounts and totals

This removes pricing logic from your frontend and ensures consistency across channels.

Customer information
Customer data can represent either a guest or a registered user. This information is later used for pricing rules, promotions, subscriptions, or customer specific logic.

Language context
The language determines which localized product data and pricing rules are applied. It must match a published language in the catalogue.

Price context
The price context defines how pricing behaves in the cart:

  • Whether prices include tax, typically included for B2C and excluded for B2B
  • Decimal precision for calculations, where four decimals helps avoid rounding issues
  • Currency, which must match the configured price variants
  • Which price variant is used as the active price
  • Which price is used as the comparison baseline for discounts
  • Which price to fall back to if a specific variant is missing

Item grouping and type
The type field defines the order item type, such as standard items, fees, service, subscription, or shipping.

The group field is a free form label that lets you group items logically in the cart. This is useful for bundles, configurations, recipes, or simply separating product categories within the order.

At this point, you have a fully hydrated cart with calculated prices, taxes, discounts, and totals. 

Placing and generating an order

Before an order can be created, the cart must be placed. This usually happens when the user enters the checkout flow, for example when you start collecting customer details, selecting shipping, or choosing a payment method.

Placing the cart locks it and makes it immutable. From this point on, items, quantities, and prices can no longer be changed.

This step is important for security and consistency. While the payment process is running, often in a separate window or with a third party provider, the customer cannot modify the cart. That guarantees the amount being paid matches the cart that will later become an order.

Placing the cart

The mutation below places an existing cart by its ID:

mutation PlaceCart{
  place( id: "3bd96c85-c840-472b-a7f0-cafc4f9a5e2b"){
    id
    items{
      name
      quantity
    }
  }
}

Handling back navigation in checkout
If the user navigates back in your checkout flow after the cart has been placed, you must create a new cart. The simplest way to do this is to run the hydrate mutation again without providing a cart ID. This automatically creates a fresh cart with the same logic and pricing context.

Creating an order from a cart

Once the cart is placed, you can create an order directly from it using the order part of the Shop API.

Shop API endpoint for order context:

https://shop-api.crystallize.com/your-tenant-identifier/order

The mutation below creates an order from an existing cart. You provide the cart ID along with optional metadata such as order type and payment status. You can also specify the stock location, which is useful for multi country setups and in store pickup scenarios. If needed, you may assign the order to a specific fulfilment pipeline and stage at creation time.

mutation CreateOrderFromCart{
  createFromCart(
    id: "3bd96c85-c840-472b-a7f0-cafc4f9a5e2b",
    input: {
      type: standard,
      paymentStatus: paid
    }
  )
  {
    id
    type	 
  }
}

Order types

Crystallize supports multiple order types to cover a wide range of business scenarios:

  • standard
  • draft
  • creditNote
  • replacement
  • backorder
  • preOrder
  • quote
  • recurring
  • split
  • test

This allows you to model everything from normal webshop orders to subscriptions, quotes, and operational edge cases using the same API.

Payment status

You can also set the payment status when creating the order:

  • paid
  • partiallyPaid
  • partiallyRefunded
  • refunded
  • unpaid

Payment status can be updated later if your payment flow is asynchronous or handled by a third party provider.

Once the order is created, it appears in Crystallize with the same structure as the cart. Item grouping, such as the “Outdoor” and “Plants” groups from earlier examples, is preserved and shown visually in the order view for easier management and fulfillment.

Order view in Crystallize

JS API Client

If you’re building with JavaScript or TypeScript, the Crystallize JS API Client provides helper functions that simplify working with the Discovery and Shop APIs. It handles common tasks such as request setup, authentication, and response parsing, so you can focus on implementing your checkout logic.

The client is designed to be framework agnostic and works equally well in Node.js, serverless environments, and modern frontend frameworks.

You can find full usage examples, configuration options, and installation instructions in the JS API Client Documentation . The source code is also available in the JS API Client GitHub repository, which is useful if you want to explore advanced use cases or contribute improvements.

Check Out Our Checkout Flow Tutorial Livestream

;