
JavaScript API
Headless API для программного взаимодействия с MiniShop3 без привязки к DOM.
Архитектура
MiniShop3 использует двухуровневую архитектуру:
API Core (Headless)
Независимое ядро без привязки к DOM:
- ApiClient.js — HTTP-клиент для запросов к серверу
- TokenManager.js — управление токенами авторизации
- CartAPI.js — методы работы с корзиной
- OrderAPI.js — методы работы с заказом
- CustomerAPI.js — методы работы с покупателем
- hooks.js — система хуков для расширения
UI Layer (SSR)
DOM-привязки для работы с серверной разметкой:
- CartUI.js — обработчики форм корзины, рендеринг
- OrderUI.js — обработчики форм заказа
- CustomerUI.js — обработчики форм покупателя
- ProductCardUI.js — переключение состояний карточек товаров
Подключение
Полное (по умолчанию)
Плагин MiniShop3 автоматически подключает все скрипты. Управление через системную настройку ms3_frontend_assets.
Минимальное (только Headless)
Для SPA-приложений (Vue, React, Svelte) подключите только ядро:
<script src="/assets/components/minishop3/js/web/modules/hooks.js"></script>
<script src="/assets/components/minishop3/js/web/core/TokenManager.js"></script>
<script src="/assets/components/minishop3/js/web/core/ApiClient.js"></script>
<script src="/assets/components/minishop3/js/web/core/CartAPI.js"></script>
<script src="/assets/components/minishop3/js/web/core/OrderAPI.js"></script>
<script src="/assets/components/minishop3/js/web/core/CustomerAPI.js"></script>Инициализация вручную
// Конфигурация
const config = {
apiUrl: '/assets/components/minishop3/api.php',
tokenName: 'ms3_token'
}
// Инициализация ядра
const tokenManager = new TokenManager(config)
const apiClient = new ApiClient({ baseUrl: config.apiUrl, tokenManager })
tokenManager.setApiClient(apiClient)
// Получение токена
await tokenManager.ensureToken()
// API модули
const cart = new CartAPI(apiClient)
const order = new OrderAPI(apiClient)
const customer = new CustomerAPI(apiClient)
// Готово к использованию
const response = await ms3.cartAPI.add(123, 2, { color: 'red' })Глобальные объекты
При стандартном подключении доступны:
| Объект | Описание |
|---|---|
window.ms3 | Главный объект со всеми модулями |
window.ms3Hooks | Система хуков |
window.ms3Message | Уведомления (если подключён UI) |
window.ms3Config | Конфигурация с сервера |
// Использование через глобальный объект
await ms3.cartAPI.add(123, 1)
await ms3.orderAPI.submit()
await ms3.customerAPI.updateProfile({ email: 'new@example.com' })CartAPI
API для работы с корзиной.
Методы
get()
Получить содержимое корзины.
const response = await ms3.cartAPI.get()
// response:
{
success: true,
message: "ms3_cart_get_success",
data: {
cart: {
// Объект, где ключ — product_key
"ms43ce202fd27c84c28230cc771328e37f": {
id: 10,
product_id: 152248,
order_id: 7,
product_key: "ms43ce202fd27c84c28230cc771328e37f",
name: "Название товара",
count: 2,
price: 148343,
weight: 2.508,
cost: 296686,
options: [],
properties: {
old_price: 0,
discount_price: 0,
discount_cost: 0
}
}
},
status: {
total_count: 9,
total_cost: 1094850,
total_weight: 35.62,
total_discount: 19241,
total_positions: 3
}
}
}add(id, count, options, render)
Добавить товар в корзину.
| Параметр | Тип | Описание |
|---|---|---|
id | number | ID товара (обязательный) |
count | number | Количество (по умолчанию 1) |
options | object | Опции товара (цвет, размер и т.д.) |
render | array | Токены для SSR (см. ниже) |
// Простое добавление
await ms3.cartAPI.add(123)
// С количеством
await ms3.cartAPI.add(123, 3)
// С опциями
await ms3.cartAPI.add(123, 1, { color: 'red', size: 'L' })SSR (Server-Side Rendering)
Параметр render позволяет получить готовый HTML с сервера. Токены должны быть зарегистрированы на сервере через TokenService. Если токены настроены, HTML возвращается в response.data.render. Это продвинутая функция для оптимизации — в большинстве случаев не требуется.
Формат ответа:
{
success: true,
message: "Товар добавлен в корзину",
data: {
cart: [...], // Обновлённая корзина
status: {...}, // Итоги
render: { // HTML (если запрошен)
mini: "<div>...</div>",
full: "<div>...</div>"
}
}
}change(productKey, count, render)
Изменить количество товара.
| Параметр | Тип | Описание |
|---|---|---|
productKey | string | Уникальный ключ товара в корзине |
count | number | Новое количество |
render | array | Токены для рендеринга |
await ms3.cartAPI.change('ms5d41d8cd98f00b204e9800998ecf8427e', 5)product_key
Каждый товар в корзине имеет уникальный product_key. Один товар с разными опциями — разные ключи.
remove(productKey, render)
Удалить товар из корзины.
await ms3.cartAPI.remove('ms5d41d8cd98f00b204e9800998ecf8427e')clean(render)
Очистить корзину.
await ms3.cartAPI.clean()Только API-вызов
cartAPI.clean() — это headless метод, он только отправляет запрос к серверу. Мини-корзина, счётчики и карточки товаров не обновятся автоматически. Для полного обновления UI используйте ms3.cartUI.handleClean() — он вызовет API, отрендерит HTML и выбросит событие ms3:cart:updated. Подробнее — в разделе Frontend JavaScript.
OrderAPI
API для работы с заказом.
Методы
get()
Получить текущий заказ (данные из сессии).
const response = await ms3.orderAPI.get()
// response.data.order (фрагмент):
{
delivery_id: 1,
payment_id: 2,
order_comment: "Позвонить перед доставкой",
address_first_name: "Иван",
address_last_name: "Иванов",
address_email: "ivan@example.com",
address_phone: "+79991234567",
address_comment: ""
}add(key, value)
Сохранить поле заказа.
await ms3.orderAPI.add('first_name', 'Иван')
await ms3.orderAPI.add('email', 'ivan@example.com')
await ms3.orderAPI.add('phone', '+79991234567')
await ms3.orderAPI.add('delivery_id', 1)
await ms3.orderAPI.add('payment_id', 2)
await ms3.orderAPI.add('order_comment', 'Позвонить перед доставкой')
// комментарий к адресу: await ms3.orderAPI.add('comment', '…')Автосохранение
Каждый вызов add() сохраняет значение на сервере. Данные не теряются при перезагрузке страницы.
remove(key)
Удалить поле заказа.
await ms3.orderAPI.remove('order_comment')clean()
Очистить все данные заказа.
await ms3.orderAPI.clean()submit()
Оформить заказ.
const response = await ms3.orderAPI.submit()
if (response.success) {
// response.data:
{
order_id: 42,
redirect: "/shop/order-success?msorder=42"
}
window.location.href = response.data.redirect
} else {
// response:
{
success: false,
message: "Заполните обязательные поля",
data: {
errors: ["first_name", "email", "phone"]
}
}
}getCost()
Получить расчёт стоимости заказа.
const response = await ms3.orderAPI.getCost()
// response:
{
success: true,
message: "Стоимость рассчитана",
data: {
cost: 0,
cart_cost: 0,
delivery_cost: 0,
payment_cost: 0,
total_count: 0,
total_cost: 0,
total_weight: 0,
total_discount: 0,
total_positions: 0
}
}CustomerAPI
API для работы с данными авторизованного покупателя.
Требуется авторизация
Все методы CustomerAPI доступны только для авторизованных пользователей. Для гостей используйте OrderAPI.add() для сохранения данных заказа.
Методы
updateProfile(data)
Обновить профиль авторизованного пользователя.
await ms3.customerAPI.updateProfile({
first_name: 'Иван',
last_name: 'Иванов',
email: 'ivan@example.com',
phone: '+79991234567'
})createAddress(data)
Создать новый адрес.
await ms3.customerAPI.createAddress({
name: 'Домашний',
city: 'Москва',
street: 'Тверская',
building: '1',
room: '10',
index: '123456'
})updateAddress(id, data)
Обновить существующий адрес.
await ms3.customerAPI.updateAddress(5, {
city: 'Санкт-Петербург',
street: 'Невский проспект'
})deleteAddress(id)
Удалить адрес.
await ms3.customerAPI.deleteAddress(5)Система хуков
Хуки позволяют расширять поведение без изменения исходного кода.
Регистрация хука
ms3Hooks.addHook('hookName', async (context) => {
// context — данные операции
// context.cancel = true — отменить операцию
})Доступные хуки
Корзина
| Хук | Контекст | Описание |
|---|---|---|
beforeAddCart | { id, count, options } | Перед добавлением товара |
afterAddCart | { id, count, options, response } | После добавления товара |
beforeChangeCart | { productKey, count } | Перед изменением количества |
afterChangeCart | { productKey, count, response } | После изменения количества |
beforeRemoveCart | { productKey } | Перед удалением товара |
afterRemoveCart | { productKey, response } | После удаления товара |
beforeCleanCart | {} | Перед очисткой корзины |
afterCleanCart | { response } | После очистки корзины |
Заказ
| Хук | Контекст | Описание |
|---|---|---|
beforeAddOrder | { key, value } | Перед сохранением поля |
afterAddOrder | { key, value, response } | После сохранения поля |
beforeSubmitOrder | {} | Перед оформлением |
afterSubmitOrder | { response } | После оформления |
beforeCleanOrder | {} | Перед очисткой |
afterCleanOrder | { response } | После очистки |
Покупатель
| Хук | Контекст | Описание |
|---|---|---|
beforeAddCustomer | { key, value } | Перед сохранением данных |
afterAddCustomer | { key, value, response } | После сохранения данных |
beforeUpdateProfile | { data } | Перед обновлением профиля |
afterUpdateProfile | { data, response } | После обновления профиля |
beforeCreateAddress | { data } | Перед созданием адреса |
afterCreateAddress | { data, response } | После создания адреса |
Примеры хуков
Валидация перед добавлением:
ms3Hooks.addHook('beforeAddCart', async (ctx) => {
if (!ctx.options.size) {
alert('Выберите размер!')
ctx.cancel = true
}
})Аналитика:
ms3Hooks.addHook('afterAddCart', async (ctx) => {
if (!ctx.response.success) return
gtag('event', 'add_to_cart', {
currency: 'RUB',
value: ctx.response.data.status.total_cost,
items: [{ item_id: ctx.id, quantity: ctx.count }]
})
})
ms3Hooks.addHook('afterSubmitOrder', async (ctx) => {
if (!ctx.response.success) return
gtag('event', 'purchase', {
transaction_id: ctx.response.data.order_id,
value: ctx.response.data.total_cost
})
})Подтверждение очистки:
ms3Hooks.addHook('beforeCleanCart', async (ctx) => {
if (!confirm('Очистить корзину?')) {
ctx.cancel = true
}
})События DOM
Список событий
| Событие | Описание | detail |
|---|---|---|
ms3:ready | ms3 инициализирован | — |
ms3:cart:updated | Корзина обновлена | { cart, status } |
Подписка
document.addEventListener('ms3:ready', () => {
console.log('MiniShop3 готов')
})
document.addEventListener('ms3:cart:updated', (e) => {
const { cart, status } = e.detail
// Обновить счётчик в шапке
document.querySelector('.cart-count').textContent = status.total_count
document.querySelector('.cart-total').textContent = status.total_cost + ' руб.'
})Использование в SPA
Vue 3
// composables/useCart.js
import { ref, onMounted } from 'vue'
export function useCart() {
const cart = ref([])
const status = ref({ total_count: 0, total_cost: 0 })
const loading = ref(false)
async function loadCart() {
loading.value = true
const response = await ms3.cartAPI.get()
if (response.success) {
cart.value = response.data.cart
status.value = response.data.status
}
loading.value = false
}
async function addToCart(id, count = 1, options = {}) {
loading.value = true
const response = await ms3.cartAPI.add(id, count, options)
if (response.success) {
cart.value = response.data.cart
status.value = response.data.status
}
loading.value = false
return response
}
async function removeFromCart(productKey) {
const response = await ms3.cartAPI.remove(productKey)
if (response.success) {
cart.value = response.data.cart
status.value = response.data.status
}
return response
}
async function changeQuantity(productKey, count) {
const response = await ms3.cartAPI.change(productKey, count)
if (response.success) {
cart.value = response.data.cart
status.value = response.data.status
}
return response
}
async function clearCart() {
const response = await ms3.cartAPI.clean()
if (response.success) {
cart.value = []
status.value = { total_count: 0, total_cost: 0 }
}
return response
}
onMounted(() => {
loadCart()
// Подписка на внешние обновления
document.addEventListener('ms3:cart:updated', (e) => {
cart.value = e.detail.cart
status.value = e.detail.status
})
})
return {
cart,
status,
loading,
addToCart,
removeFromCart,
changeQuantity,
clearCart,
loadCart
}
}Использование в компоненте:
<script setup>
import { useCart } from '@/composables/useCart'
const { cart, status, loading, addToCart, removeFromCart } = useCart()
</script>
<template>
<div class="cart">
<div v-if="loading">Загрузка...</div>
<div v-for="item in cart" :key="item.product_key" class="cart-item">
<span>{{ item.pagetitle }}</span>
<span>{{ item.count }} x {{ item.price }} руб.</span>
<button @click="removeFromCart(item.product_key)">Удалить</button>
</div>
<div class="cart-total">
Итого: {{ status.total_cost }} руб.
</div>
</div>
</template>React
// hooks/useCart.js
import { useState, useEffect, useCallback } from 'react'
export function useCart() {
const [cart, setCart] = useState([])
const [status, setStatus] = useState({ total_count: 0, total_cost: 0 })
const [loading, setLoading] = useState(false)
const loadCart = useCallback(async () => {
setLoading(true)
const response = await window.ms3.cartAPI.get()
if (response.success) {
setCart(response.data.cart)
setStatus(response.data.status)
}
setLoading(false)
}, [])
const addToCart = useCallback(async (id, count = 1, options = {}) => {
setLoading(true)
const response = await window.ms3.cartAPI.add(id, count, options)
if (response.success) {
setCart(response.data.cart)
setStatus(response.data.status)
}
setLoading(false)
return response
}, [])
const removeFromCart = useCallback(async (productKey) => {
const response = await window.ms3.cartAPI.remove(productKey)
if (response.success) {
setCart(response.data.cart)
setStatus(response.data.status)
}
return response
}, [])
useEffect(() => {
loadCart()
const handleCartUpdate = (e) => {
setCart(e.detail.cart)
setStatus(e.detail.status)
}
document.addEventListener('ms3:cart:updated', handleCartUpdate)
return () => document.removeEventListener('ms3:cart:updated', handleCartUpdate)
}, [loadCart])
return { cart, status, loading, addToCart, removeFromCart, loadCart }
}Vanilla JavaScript
class CartManager {
constructor() {
this.cart = []
this.status = { total_count: 0, total_cost: 0 }
this.listeners = []
}
async init() {
await this.load()
document.addEventListener('ms3:cart:updated', (e) => {
this.cart = e.detail.cart
this.status = e.detail.status
this.notify()
})
}
async load() {
const response = await ms3.cartAPI.get()
if (response.success) {
this.cart = response.data.cart
this.status = response.data.status
this.notify()
}
}
async add(id, count = 1, options = {}) {
const response = await ms3.cartAPI.add(id, count, options)
if (response.success) {
this.cart = response.data.cart
this.status = response.data.status
this.notify()
}
return response
}
subscribe(callback) {
this.listeners.push(callback)
return () => {
this.listeners = this.listeners.filter(l => l !== callback)
}
}
notify() {
this.listeners.forEach(cb => cb(this.cart, this.status))
}
}
// Использование
const cartManager = new CartManager()
await cartManager.init()
cartManager.subscribe((cart, status) => {
document.querySelector('.cart-count').textContent = status.total_count
})
document.querySelector('.add-to-cart').addEventListener('click', () => {
cartManager.add(123, 1, { size: 'L' })
})Формат ответов
Все методы возвращают Promise с объектом:
{
success: true, // Успех операции
message: "Текст", // Сообщение (опционально)
data: { // Данные (при success: true)
cart: [...],
status: {...},
render: {...}
}
}При ошибке:
{
success: false,
message: "Описание ошибки",
data: {
errors: ["field1", "field2"] // Поля с ошибками
}
}Токены и авторизация
MiniShop3 использует токены для идентификации сессии корзины.
Как работает
- При первом запросе
TokenManagerполучает токен с сервера - Токен сохраняется в
localStorageс временем жизни - Токен автоматически добавляется ко всем запросам
- При истечении токен обновляется автоматически
Ручное управление
// Получить текущий токен
const token = ms3.tokenManager.getToken()
// Принудительно обновить
await ms3.tokenManager.fetchNewToken()
// Удалить токен
ms3.tokenManager.removeToken()Связанные страницы
- Frontend JavaScript — полная документация включая UI
- REST API — описание серверных endpoints
- События — PHP события компонента
