Skip to content

Commerce Journey

A product enters the catalog through the data import, Matrix serves it to the storefront listing and product page, Checkout turns a cart into an order, a payment provider settles it, and the operator sees the order in the CMS. Matrix, Volkanos and the other product names are explained on Concepts.

StepModuleEndpointStorefront route
CatalogPIM, filled by the data importnone (the import writes PIM, Matrix reads it)none
ListingMatrixGET /api/matrix/v2/{channel_idx}/products/?category=<url_key>&include=full&language=&currency=&country=/catalog/<url_key>
Product pageMatrixGET /api/matrix/v2/{channel_idx}/products/?url_key=<slug>&include=full/product/<url_key>
CartCheckoutPOST /api/checkout/v2/{channel_idx}/carts/, PATCH /api/checkout/v2/{channel_idx}/carts/{cart_id}/items/”Add to cart” on the product page
CheckoutCheckoutPATCH .../carts/{cart_id}/addresses/, GET .../carts/{cart_id}/shipping-methods/, PATCH .../carts/{cart_id}/shipping/, GET .../carts/{cart_id}/payment-methods/, PATCH .../carts/{cart_id}/payment//checkout
Payment and orderCheckout, payment SDKsPOST /api/checkout/v2/{channel_idx}/orders/redirect_url of the provider, or /checkout/success?ref=<pretty id>
Order in the CMSCheckout (admin API)GET /api/checkout/v2/admin/{channel_idx}/orders/CMS Orders panel

... stands for /api/checkout/v2/{channel_idx}. Every storefront path above is in the published Storefront API spec; the admin orders path comes from the checkout module’s admin URLs.

The storefront calls Matrix and Checkout, the CMS calls Checkout, and Checkout hands the payment to a provider SDK:

Diagram

Inside the backend, the catalog, the prices and the stock reach the Matrix read model:

Diagram
ModuleContributesDocs
pimproducts, categories, attributes, imagesdocs
pim-csvthe CSV import that fills PIMData Import
pricemanagerprice lists, the Price model, the currency of each sale channeldocs
qmsquantities, the XRAY pull and the push into django_checkout.Stockdocs, checkout sync
matrixthe storefront read model: listing, search, prices, stock, omnibus endpointsdocs
checkoutcarts, addresses, shipping and payment methods, orders, channel API keys, provider callbacksrepo
omnibusthe lowest previous price required by the Omnibus rule, served as GET /api/matrix/v2/{channel_idx}/omnibus/docs
agreementsconsents attached to an order, POST /api/agreements/v2/{channel_idx}/orders/{order_id}/agreements/repo
accountscustomer login and address book; its endpoints are v1, so they are outside the published specdocs
payment SDKsPayU, PayPal, Autopay, Przelewy24 and Paynow clients (entirius-py-payu-sdk, entirius-py-paypal-sdk, entirius-py-autopay-sdk, entirius-py-przelewy24-sdk, entirius-py-paynow-sdk); hard dependencies of checkoutcheckout pyproject.toml

What must exist before the first order:

  • A channel in every module of the table. The data import creates them.
  • Prices in the channel currency (pricemanager) and quantities (qms, pushed into checkout stock).
  • At least one shipping method and one payment method per channel: django_checkout.shippingmethod and django_checkout.paymentmethod records. The Emporium test package fixtures ship the codes europe-standard and banktransfer. Both are edited in Django admin.
  • A channel API key: a django_checkout.apikey record bound to the channel. The storefront sends it as the X-API-KEY header; ChannelAPIKeyPermission (api/v2/permissions.py) refuses a key of another channel. Edited in Django admin.

Orders are read in the CMS under the Orders entry of the navigation (/checkout-orders/orders); Review an Order walks through the panel.

Every checkout call below carries the X-API-KEY header of the channel.

GET /api/matrix/v2/{channel_idx}/products/ with the query parameters category, url_key, sku, include, on_stock, page, page_size, language, currency, country. Free text goes to the separate /api/matrix/v2/{channel_idx}/search/ endpoint.

The response is a PaginatedResponse: has_next_page, page, page_size, results, meta. Each result is a ProductCore (schemas/responses/product.py in matrix) with sku, product_type, url_key, name, main_image, on_stock, categories and price, whose fields include gross, net, final_gross and has_special_price. The product page sends url_key=<slug>&include=full and takes the first result.

POST /api/checkout/v2/{channel_idx}/carts/ with items[].sku, items[].quantity, currency_code and the optional language_code and country_code. The response is a CartV2Response: cart_id, cart_status, validation_status, total_gross, items, shipping_method, payment_method. PATCH /api/checkout/v2/{channel_idx}/carts/{cart_id}/items/ replaces the full item list.

PATCH /api/checkout/v2/{channel_idx}/carts/{cart_id}/addresses/ with billing_address and shipping_address (null means the same as billing). An AddressInput requires firstname, lastname, street, city, postcode, country_code, telephone and dialling_code.

GET /api/checkout/v2/{channel_idx}/carts/{cart_id}/shipping-methods/ lists the methods for the cart; PATCH /api/checkout/v2/{channel_idx}/carts/{cart_id}/shipping/ selects one by code, with delivery_point for pickup methods. GET /api/checkout/v2/{channel_idx}/carts/{cart_id}/payment-methods/ and PATCH /api/checkout/v2/{channel_idx}/carts/{cart_id}/payment/ work the same way: code, plus bank_id, card and save_card for gateways that need them.

POST /api/checkout/v2/{channel_idx}/orders/ with cart_id in the body. The published spec carries no request body for this call; the field comes from OrderCreateView (api/v2/views/order_views.py), which answers 400 without it. The response is an OrderCreateResponse: order_id, order_pretty_id, order_status, redirect_url, split_orders_pretty_ids.

A redirect_url sends the customer to the provider. The provider calls back on the checkout notify/<provider>/ paths; those are v1 and outside the published spec. banktransfer needs no redirect: the order is created as UNPAID and the storefront goes straight to /checkout/success?ref=<order_pretty_id>.

GET /api/checkout/v2/admin/{channel_idx}/orders/, orders/{uid}/ and orders/{uid}/status/ under the same prefix. JWT authentication and a staff user (IsAdminUser), no X-API-KEY.

The storefront (Next.js) keeps every path in API/api.routes.ts:

  • API_PRODUCTS_ROUTE: the listing query in app/[customer_country]/(catalog)/catalog/[catalog_url_key]/api.query.ts sends category=<catalog_url_key> with include=full and reads has_next_page, page, page_size; the product query in app/[customer_country]/(product)/product/[product_url_key]/api.query.ts sends url_key with include=full. Both pass results through utils/NORMALIZERS/product.normalizer.
  • API_CART_ITEMS_ROUTE, API_CART_ADDRESS_ROUTE, API_CART_SHIPPING_ROUTE, API_CART_SHIPPING_SELECT_ROUTE, API_CART_PAYMENT_ROUTE, API_CART_PAYMENT_SELECT_ROUTE and API_CART_ORDERS_ROUTE: the cart and checkout calls, each sent with the channel X-API-KEY.

The checkout at /checkout (app/[customer_country]/checkout/) walks the steps in the order the Playwright test takes them: address form, shipping, payment, review, Place order, then /checkout/success?ref=<pretty id> with the heading “Order placed”.

In the CMS, the Orders panel loads the grid with GET_Orders (src/api/orders/api.js), which calls /api/checkout/v2/admin/{channel_idx}/orders/.

The path is proven by the Emporium test package (entirius-test-package-emporium):

ProofTagWhat it covers
features/checkout/order_flow.feature@checkout @order-flow @v2guest cart with ENT-C003, addresses, shipping europe-standard, payment banktransfer, cart valid, order unpaid with a pretty id
features/matrix_v2/*.feature@matrix-v2listing, filters, search, product detail, stock, prices, omnibus
e2e/storefront/test_order_flow.pyPlaywright/catalog/office-chairs, product ENT-C003, add to cart, checkout, Order placed
e2e/cms/test_order_visible.pyPlaywrightan order placed through the checkout API, then visible in the CMS Orders grid

Running them on a local stack: Quickstart.

A new product field, end to end. Add the field to the PIM feature set, run fill-read-model so Matrix copies it into the read model, and expose it on ProductCore in matrix schemas/responses/product.py. The storefront then maps it in utils/NORMALIZERS/product.normalizer, which every product query already passes through. A field that skips the read model refill stays empty on the storefront even though PIM holds it.

A new payment method. Create a django_checkout.paymentmethod record for the channel with the provider and a code; GET .../carts/{cart_id}/payment-methods/ returns it from then on. Gateway providers use the SDK checkout already depends on, so the provider credentials are the remaining step. A provider without an SDK in checkout’s pyproject.toml needs a new SDK package and a callback view in checkout before a record can point at it.

Load your own catalog: Data Import.