# Build a cart and checkout Create one cart, keep it current, and complete a purchase through hosted or custom checkout. The cart response supplies the current amounts and eligibility; the browser does not calculate the payable total independently. ## Before you begin Verify a sellable variant and [matching request context](/concepts/storefront/request-context). Use the [Storefront quickstart](/getting-started/storefront-quickstart#2-create-a-cart) to create the cart and persist its ID. Prepare the shipping, tax, and test payment configuration required by your project. ## 1. Edit the cart Use [cartLineItemsAdd](/api/storefront-graphql/mutations/cart-line-items-add) with variant IDs to add items. Use [cartLineItemsUpdate](/api/storefront-graphql/mutations/cart-line-items-update) and [cartLineItemsRemove](/api/storefront-graphql/mutations/cart-line-items-remove) with existing cart line-item IDs. Check business errors and render the latest cart after each response. Serialize dependent changes to avoid stale quantities and amounts. Persist cart identity in the shopper's session, not a global application variable. Check that the current cart is editable before continuing a previous session. An ordered cart belongs to the completed purchase. ## 2. Choose hosted or custom checkout For hosted checkout, open the current cart's `checkoutUrl` after verifying the basket. Your storefront still owns product discovery and cart presentation. You do not need to recreate the remaining checkout screens. For custom checkout, continue with the sequence below and support each configured payment-session type your application offers. ## 3. Set contact and address data Storefront API ```graphql mutation UpdateCheckout($input: CartUpdateInput!) { cartUpdate(input: $input) { cart { id customerEmail availableShippingMethods { id name } total { centAmount currencyCode fractionDigits } } errors { __typename ... on UserError { message } } } } ``` Variables ```json { "input": { "cartId": "", "customerEmail": "buyer@example.com", "shippingAddress": { "firstName": "Alex", "lastName": "Example", "address1": "Example Street 1", "city": "Copenhagen", "postalCode": "1000", "countryCode": "DK", "email": "buyer@example.com" } } } ``` This address is illustrative; use appropriate test data for your configured delivery area. Add billing address information where your flow requires it. For an authenticated buyer, derive customer identity from the session rather than trusting an arbitrary submitted ID. ## 4. Select returned shipping Storefront API ```graphql mutation ChooseShipping($input: CartShippingLinesSetInput!) { cartShippingLinesSet(input: $input) { cart { id shippingLines { id } total { centAmount currencyCode } } errors { __typename ... on UserError { message } } } } ``` Variables ```json { "input": { "cartId": "", "shippingMethodIds": [ "" ] } } ``` Use a method ID from the latest `availableShippingMethods` response. The input expects method IDs, not rate IDs or your own shipping prices. Changing address or cart contents can invalidate a previous selection. ## 5. Initialize an eligible payment method Query [paymentGateways](/api/storefront-graphql/queries/payment-gateways) for this cart. Then call [cartPaymentSessionInitialize](/api/storefront-graphql/mutations/cart-payment-session-initialize) with `cartId` and `gatewayId`. Request `__typename` and the fragments needed by the chosen session type. Complete required provider interaction with the provider's client integration. Do not equate session initialization with a successful payment. The Next.js example implements these documents in `src/lib/thorcommerce/storefront/mutations/checkout.graphql` and the UI under `src/features/checkout`. ## 6. Complete and verify Storefront API ```graphql mutation CompleteCheckout($input: CartCompleteInput!) { cartComplete(input: $input) { order { id orderNumber } errors { __typename ... on UserError { message } } } } ``` Variables ```json { "input": { "cartId": "" } } ``` Persist the order ID before showing success. Disable duplicate submission while the request is pending. For a lost response, reconcile with the same cart ID using [order-placement recovery](/concepts/checkout/order-placement). Do not start a second purchase because the first request timed out. Test quantity changes, unavailable stock, address changes, rejected payment, refresh during checkout, and a lost completion response in your test environment. See [Recover from storefront errors](/guides/storefront/error-recovery) for the recovery interface.