Shop API
Fluttrium Shop API — публичный GraphQL для сайтов магазинов. Один адрес на всю платформу, магазин выбирается токеном в заголовке. Через него витрина читает каталог и контент, ведёт корзину и оформляет заказ.
POST https://api.fluttrium.store/shop-apiЗаголовки запроса
Заголовок раздела «Заголовки запроса»| Заголовок | Значение | Зачем |
|---|---|---|
content-type |
application/json |
Тело — { "query": "…", "variables": { … } } |
vendure-token |
токен магазина | Выбирает магазин. Токен выдаёт Fluttrium при подключении |
authorization |
Bearer <токен сессии> |
Сессия покупателя: корзина, вход. Для каталога не нужен |
accept-language |
ru |
Язык сообщений об ошибках. Без него сообщения на английском |
Язык контента (названия, описания) задаётся параметром адреса
?languageCode=ru. Без него — язык магазина по умолчанию.
Сессия покупателя
Заголовок раздела «Сессия покупателя»Корзина и вход привязаны к сессии. Сессию заводит сервер платформы: на первом
же запросе, который её касается (даже на чтении activeOrder), он
возвращает токен в заголовке ответа vendure-auth-token.
-
Первый запрос корзины идёт без
authorization. -
Из ответа возьмите заголовок
vendure-auth-tokenи сохраните его на стороне сервера витрины — например, в httpOnly-cookie. -
Все следующие запросы этого покупателя отправляйте с
authorization: Bearer <токен>. -
Если в ответе пришёл другой
vendure-auth-token(например, после входа), замените сохранённый.
Клиент @fluttrium/shop-sdk
Заголовок раздела «Клиент @fluttrium/shop-sdk»SDK берёт на себя заголовки, таймауты, сессию и разбор ошибок. Пакет публикуется в реестре платформы и открыт на чтение без логина.
@fluttrium:registry=https://npm.fluttrium.store/import { createShopClient, StorefrontContentDocument } from '@fluttrium/shop-sdk';
const shop = createShopClient({ url: 'https://api.fluttrium.store/shop-api', channelToken: process.env.SHOP_CHANNEL_TOKEN!, // токен магазина});
// Публичный запрос с кешем Next.js на минуту.const { data } = await shop.request(StorefrontContentDocument, undefined, { fetchOptions: { next: { revalidate: 60, tags: ['content'] } },});
// Запрос в сессии покупателя: токен из cookie туда, новый — обратно в cookie.const { data: cart, authToken } = await shop.request(ActiveOrderDocument, undefined, { authToken: sessionFromCookie,});if (authToken && authToken !== sessionFromCookie) saveSessionCookie(authToken);Версия SDK равна версии платформы. Ставьте ту, что сейчас на сервере: схема
в пакете описывает именно её. Свои запросы витрина пишет сама и получает
типы из той же схемы через @fluttrium/shop-sdk/codegen — см.
Витрина на Next.js.
Ошибки SDK — ShopApiError с полем kind:
kind |
Что случилось |
|---|---|
config |
Не задан адрес или токен, документ не в строковом режиме codegen |
network |
Сервер платформы недоступен |
timeout |
Нет ответа за таймаут (по умолчанию 15 с) |
aborted |
Запрос отменён вызывающим |
http |
Ответ без данных GraphQL (например, 502) |
graphql |
Сервер ответил ошибками GraphQL, коды — в errors[].extensions.code |
Частичный ответ (корневые поля пришли, упало вложенное) исключением не
считается: данные отдаются, ошибки уходят в onPartialErrors. Иначе витрина
сообщила бы о сбое уже выполненной мутации, и повтор добавил бы товар дважды.
Примеры запросов
Заголовок раздела «Примеры запросов»Цены во всех ответах — в копейках: 290000 — это 2 900 ₽.
query Collections { collections(options: { topLevelOnly: true, take: 100 }) { items { slug name position children { slug name position } } }}# Товары раздела bags с одним из двух значений аспекта (ИЛИ),# по одной карточке на товар.query Catalog { search(input: { collectionSlug: "bags" facetValueFilters: [{ or: ["45", "44"] }] groupByProduct: true take: 48 skip: 0 }) { totalItems items { productId productName slug inStock productAsset { preview } priceWithTax { ... on PriceRange { min max } ... on SinglePrice { value } } } facetValues { count facetValue { id code name facet { code } } } }}Фильтры работают по id значений аспектов. Несколько элементов в
facetValueFilters объединяются через И, значения внутри or — через ИЛИ;
одно обязательное значение — { and: "45" }. Коды и id значений отдаёт
запрос facets.
query Product($slug: String!) { product(slug: $slug) { id name description assets { preview width height focalPoint { x y } } facetValues { code name facet { code } } variants { id sku name priceWithTax stockLevel options { code name group { code } } } }}stockLevel — IN_STOCK, OUT_OF_STOCK или LOW_STOCK, точное число
остатка Shop API не раскрывает.
mutation AddItem($variantId: ID!, $quantity: Int!) { addItemToOrder(productVariantId: $variantId, quantity: $quantity) { __typename ... on Order { code totalQuantity subTotalWithTax lines { id quantity linePriceWithTax productVariant { id name } } } ... on ErrorResult { errorCode message } ... on InsufficientStockError { quantityAvailable } }}Отправляется с authorization: Bearer <токен сессии>. Ответ — либо заказ,
либо объект ошибки.
Ошибки приходят двумя способами.
Ошибки бизнес-логики — данными: мутация возвращает объект с
errorCode и message вместо заказа. Их нужно разбирать и показывать
покупателю.
errorCode |
Когда |
|---|---|
INSUFFICIENT_STOCK_ERROR |
Не хватает остатка, в ответе quantityAvailable |
ORDER_MODIFICATION_ERROR |
Заказ уже ушёл в оплату, менять его нельзя |
ORDER_STATE_TRANSITION_ERROR |
Недопустимый переход состояния заказа |
NO_ACTIVE_ORDER_ERROR |
У сессии нет корзины |
GUEST_CHECKOUT_ERROR, EMAIL_ADDRESS_CONFLICT_ERROR |
Почта принадлежит зарегистрированному покупателю — предложите войти по коду |
PAYMENT_DECLINED_ERROR, PAYMENT_FAILED_ERROR |
Банк отказал или платёж не создан |
INVALID_CREDENTIALS_ERROR |
Неверный или просроченный код входа |
Ошибки запроса — в errors[] ответа GraphQL с кодом в
extensions.code:
| Код | Когда |
|---|---|
CHANNEL_NOT_FOUND |
Неверный токен магазина |
FORBIDDEN |
Нет доступа: например, чужой заказ в orderByCode |
USER_INPUT_ERROR |
Неверные аргументы: строки заказа уже нет, take больше 100 |
Ограничения
Заголовок раздела «Ограничения»| Что | Предел |
|---|---|
Элементов в одном списке (take) |
100 |
| Код входа покупателя | 1 письмо в минуту, 5 в час, 10 в сутки на почту; 5 попыток на код — подробнее |
checkOrderPayment |
запрос к банку по одному заказу не чаще раза в 3 с |
| Сервис виджета СДЭК | 120 запросов в минуту на магазин и адрес покупателя — подробнее |
Кеширование
Заголовок раздела «Кеширование»Shop API отвечает на POST, HTTP-кеш браузеров и прокси его не держит. Кешируйте на сервере витрины, только публичные запросы без сессии. После правки в админке витрина покажет изменение, когда истечёт её кеш.
Песочница
Заголовок раздела «Песочница»Схему удобно изучать в GraphiQL:
api.fluttrium.store/graphiql/shop.
Добавьте заголовок vendure-token с токеном своего магазина во вкладке
заголовков, иначе запросы пойдут в канал по умолчанию.