Skip to content
  1. Компоненты
  2. MiniShop3
  3. Разработка
  4. Backend API
  5. API покупателя

API покупателя ​

Покупатель (msCustomer) не зависит от modUser. Поле user_id необязательно.

  • msCustomer: профиль (email, телефон, пароль, статистика заказов)
  • msCustomerToken: токены аутентификации и верификации
  • msCustomerAddress: сохранённые адреса доставки

Контроллер Customer ​

Контроллер $ms3->customer отдаёт профиль, адреса и токен сессии.

Часть методов отдаёт результат в конверте

getFields(), set(), add(), generateToken() и updateToken() возвращают конверт ['success' => bool, 'message' => string, 'data' => array]. Это тот же Utils::success() / Utils::error(), что и в Web API. Нужные значения лежат в data. При пустом токене success равен false.

Остальные методы отдают значение напрямую:

МетодВозвращает
getObject(), getByToken()msCustomer или null
create()msCustomer или null
addAddress()bool
getAddresses()массив адресов
getOrCreate()int
validate()значение или массив ошибок
php
$ms3 = $modx->services->get('ms3');

// Получить данные текущего покупателя (по токену сессии)
$response = $ms3->customer->getFields();
// ['success' => true, 'message' => '', 'data' => ['id' => 5, 'email' => 'user@example.com', ...]]
$fields = $response['data'];

// Получить объект msCustomer
$customer = $ms3->customer->getObject();

// Найти покупателя по токену
$customer = $ms3->customer->getByToken($token);

// Установить несколько полей
$result = $ms3->customer->set([
    'first_name' => 'Иван',
    'last_name' => 'Иванов',
    'phone' => '+79991234567',
]);

// Добавить/обновить одно поле (с валидацией и событиями)
$result = $ms3->customer->add('email', 'new@example.com');

Создание покупателя ​

php
// Программное создание
$customer = $ms3->customer->create([
    'email' => 'user@example.com',
    'first_name' => 'Иван',
    'phone' => '+79991234567',
]);
// Вызывает события msOnBeforeCreateCustomer / msOnCreateCustomer

// Поиск или создание при оформлении заказа
$customerId = $ms3->customer->getOrCreate($orderData);
// Ищет по токену → по email → авторегистрация или создание записи

Управление адресами ​

php
// Добавить адрес
$ms3->customer->addAddress([
    'customer_id' => $customerId,
    'city' => 'Москва',
    'street' => 'Ленина',
    'building' => '10',
    'room' => '5',
]);

// Получить адреса покупателя
$addresses = $ms3->customer->getAddresses($customerId);

Валидация полей ​

php
// Регистрация правил валидации
$ms3->customer->registerValidation(
    // Правила (формат Rakit Validator)
    ['email' => 'required|email', 'phone' => 'required|min:10'],
    // Сообщения об ошибках
    ['email:required' => 'Укажите email']
);

// Валидация значения
$result = $ms3->customer->validate('email', 'user@example.com');
// Значение или массив ошибок

Токены сессии ​

php
// Сгенерировать новый токен
$response = $ms3->customer->generateToken();
// ['success' => true, 'message' => '', 'data' => ['token' => 'abc123...', 'lifetime' => 86400000]]
$token = $response['data']['token'];

// Обновить существующий токен (при пустом аргументе создаст новый)
$response = $ms3->customer->updateToken($currentToken);

lifetime задаёт миллисекунды до истечения: секунды из ms3_customer_token_ttl, умноженные на 1000. Транспортный пакет создаёт настройку со значением 86400 (сутки). У свежего токена в lifetime около 86400000 (issue #848).

Аутентификация (AuthManager) ​

AuthManager аутентифицирует покупателя через подключаемые провайдеры. is_active и is_blocked он проверяет после того, как провайдер вернул покупателя. handleFailedLoginByEmail() вызывает процессор входа — только если покупатель не найден или пароль неверен.

php
$authManager = $modx->services->get('ms3_auth_manager');

Аутентификация ​

php
// Стандартная аутентификация (email + пароль)
$customer = $authManager->authenticate([
    'email' => 'user@example.com',
    'password' => 'secret123',
]);
// Возвращает msCustomer или null

if ($customer) {
    // Обновляет last_login_at, сбрасывает failed_login_attempts
}

Подключаемые провайдеры ​

По умолчанию зарегистрирован PasswordAuthProvider (email + пароль). authenticate() перебирает провайдеры и спрашивает каждого, подходят ли ему переданные данные (supports()).

Подошедший провайдер перебор не завершает. Если его собственный authenticate() вернул null (например, пароль не совпал), перебор продолжается дальше по списку. Прерывает его только один случай: провайдер нашёл покупателя, но тот неактивен или заблокирован — тогда authenticate() сразу отдаёт null. Причину отказа показывает getLastAuthFailure(): inactive, blocked, invalid_credentials или none при успехе. На blocked и inactive счётчик неудачных попыток наращивать не нужно — этим и пользуется процессор Api\Customer\Login.

Свой провайдер:

php
use MiniShop3\Controllers\Auth\AuthProviderInterface;
use MiniShop3\Model\msCustomer;

class TelegramAuthProvider implements AuthProviderInterface
{
    public function getName(): string
    {
        return 'telegram';
    }

    public function supports(array $credentials): bool
    {
        return isset($credentials['telegram_hash'], $credentials['telegram_id']);
    }

    public function authenticate(array $credentials): ?msCustomer
    {
        if (!$this->verifyTelegramHash($credentials)) {
            return null;
        }
        return $this->modx->getObject(msCustomer::class, [
            'phone' => $credentials['phone'],  // или другой идентификатор
        ]);
    }
}

// Регистрация провайдера
$authManager->registerProvider(new TelegramAuthProvider($modx));

Токены и сессии ​

php
// Создать токен аутентификации
$token = $authManager->createToken($customer, 'api', 86400);
// msCustomerToken: token, type='api', expires_at = now + 86400

// Проверить токен (msCustomer или null, если истёк либо использован)
$customer = $authManager->validateToken($tokenString, 'api');

// Отозвать все токены покупателя
$count = $authManager->revokeTokens($customer);

// Отозвать только определённый тип
$count = $authManager->revokeTokens($customer, 'api');

// Очистить просроченные токены (для cron-задачи)
$deleted = $authManager->cleanupExpiredTokens();

// Войти: новый токен + сессия + cookie + перенос черновика корзины
// (['token' => '...', 'expires_at' => '...'] или null)
$session = $authManager->establishCustomerSession($customer);

// Псевдоним предыдущего метода, оставлен для читаемости вызовов из API
$session = $authManager->establishApiSession($customer);

// Выйти: отозвать токены типа api, очистить сессию и cookie,
// выдать гостевой токен взамен (bool)
$authManager->logoutCurrentCustomer();

// Найти покупателя по email, приведя адрес к единому виду (msCustomer или null)
$customer = $authManager->findCustomerByEmail('user@example.com');

// Нарастить счётчик неудачных попыток, когда покупателя ещё нет под рукой (void)
$authManager->handleFailedLoginByEmail('user@example.com');

establishApiSession() ничего своего не делает

Метод просто вызывает establishCustomerSession(). Отдельной «сессии API», отличной от сессии фронтенда, в компоненте нет: и то и другое — один токен в таблице ms3_customer_tokens плюс запись в сессии PHP.

establishCustomerSession() всегда выпускает новый токен, даже если покупатель уже входил. Это защита от подмены сессии: старая строка токена удаляется, поэтому подставленная заранее cookie доступ после входа не сохранит. Черновик корзины при этом переносится на новый токен.

Типы токенов ​

ТипКонстантаОписаниеОдноразовый
apimsCustomerToken::TYPE_APIТокен API-сессииНет
refreshmsCustomerToken::TYPE_REFRESHТокен обновления сессииНет
magic_linkmsCustomerToken::TYPE_MAGIC_LINKСсылка для входа без пароляДа
email_verificationmsCustomerToken::TYPE_EMAIL_VERIFICATIONПодтверждение emailДа
password_resetmsCustomerToken::TYPE_PASSWORD_RESETСброс пароляДа

После первого применения одноразовый токен получает used_at.

Токен сброса пароля не помечают через markAsUsed(). После успешной смены пароля AuthManager::revokeTokens() вызывается без типа и отзывает все токены покупателя. Сессии на других устройствах тоже прекращаются.

Блокировка при неудачных попытках ​

php
// Обработка неудачного входа (вызывается автоматически)
$authManager->handleFailedLogin($customer);
// Увеличивает failed_login_attempts
// При достижении ms3_customer_max_login_attempts (по умолчанию 5)
// блокирует на ms3_customer_block_duration секунд.
// Установщик пишет 300. Если строки настройки нет, код берёт 3600.

Регистрация (RegisterService) ​

php
$registerService = $modx->services->get('ms3_register_service');

$result = $registerService->register([
    'email' => 'user@example.com',
    'password' => 'Secret123!',       // опционально — сгенерируется автоматически
    'first_name' => 'Иван',
    'last_name' => 'Иванов',
    'phone' => '+79991234567',
    'privacy_accepted' => true,        // GDPR-согласие
    'token' => $sessionToken,          // привязка к сессии (из checkout)
]);

if ($result['success']) {
    $customer = $result['customer'];        // msCustomer
    $autoPassword = $result['auto_password']; // null если пароль был указан
}

Поведение при регистрации ​

  1. Проверка уникальности email и телефона
  2. Если пароль не указан, генерируется 16-символьный случайный пароль
  3. Хеширование пароля (bcrypt)
  4. Создание msCustomer
  5. Если ms3_customer_require_email_verification включена, уходит письмо верификации
  6. Если пароль сгенерирован и ms3_customer_send_welcome_email = true, уходит приветственное письмо с паролем

Валидация пароля ​

php
$result = $registerService->validatePassword('weak');
// ['valid' => false, 'message' => лексикон ms3_customer_err_password_too_short]

Минимальную длину в сообщении придётся подставить самостоятельно

Код передаёт в лексикон значение (['length' => $minLength]), но строка ms3_customer_err_password_too_short в поставке написана с плейсхолдером {length}. modLexicon подставляет только [[+length]], поэтому в message приходит нераскрытый плейсхолдер: «Пароль должен содержать минимум {length} символов».

Пока это так, в своём интерфейсе берите длину из настройки ms3_password_min_length и подставляйте сами:

php
$minLength = (int) $modx->getOption('ms3_password_min_length', null, 8);
$message = str_replace('{length}', (string) $minLength, $result['message']);

Либо перепишите строку лексикона в своём языковом наборе под [[+length]] — тогда подстановка заработает штатно.

НастройкаПо умолчаниюОписание
ms3_password_min_length8Минимальная длина
ms3_password_require_uppercasefalseТребовать заглавную букву
ms3_password_require_numberfalseТребовать цифру
ms3_password_require_specialfalseТребовать спецсимвол

Верификация email (EmailVerificationService) ​

php
$verification = $modx->services->get('ms3_email_verification_service');

// Отправить письмо верификации
$sent = $verification->sendVerificationEmail($customer);

// Проверить токен (по ссылке из письма)
$customer = $verification->verifyToken($tokenString);
// Устанавливает email_verified_at, помечает токен как использованный

// Проверить, подтверждён ли email
if ($verification->isVerified($customer)) {
    // Email подтверждён
}

// Повторная отправка (с защитой от спама — 5 минут между отправками)
$result = $verification->resendVerificationEmail($customer);

ms3_email_verification_token_ttl (по умолчанию 86400): время жизни токена верификации в секундах.

Web API покупателя — /api/v1/customer/* ​

Контроллер: CustomerProfileController. Методы появились в 1.10.x / 1.11.0.

МетодПутьОписание
POST/api/v1/customer/addОдно поле профиля: key + value. С 1.11.0 можно все колонки msCustomer из xPDO-карты (включая Object Extension), кроме 18 полей из CustomerPublicDto::SYSTEM_NON_EDITABLE_FIELDS: id, user_id, customer_group_id, token, password, email_verified_at, флаги и счётчики блокировки, агрегаты заказов, служебные даты. first_name, last_name, email, phone проходят Rakit-валидацию, остальные значения нормализуются по phptype из метаданных поля. Смена email сбрасывает email_verified_at.
POST/api/v1/customer/changeAddressВыбирает сохранённый адрес в черновике заказа. Принимает address_hash.
GET/api/v1/customer/email/verifyПодтверждает email по токену из письма. Параметры: token, html=1 (HTML-редирект вместо JSON), format=json. По умолчанию редирект на сайт с ms3_email_verified=1|0. Свой URL: ms3_email_verification_url. URL успеха: ms3_email_verification_success_url.
POST/api/v1/customer/email/resend-verificationПовторно отправляет письмо верификации авторизованному покупателю. Сервис не даёт отправлять чаще раза в 5 минут.

Ограничение частоты запросов (RateLimiter) ​

RateLimiter считает попытки в кеше MODX.

php
$limiter = $modx->services->get('ms3_rate_limiter');

// Проверить и инкрементировать счётчик
$allowed = $limiter->check(
    'login',              // действие
    $clientIp,            // идентификатор (IP, email и т.д.)
    5,                    // максимум попыток
    300                   // окно в секундах
);

if (!$allowed) {
    // Лимит превышен
}

// Проверить без инкремента
if ($limiter->isBlocked('login', $clientIp, 5)) {
    // Заблокирован
}

// Сбросить счётчик (после успешного входа)
$limiter->reset('login', $clientIp);

Адреса покупателя (CustomerAddressManager) ​

php
$addressManager = $modx->services->get('ms3_customer_address_manager');

// Добавить адрес
$success = $addressManager->add([
    'customer_id' => $customerId,
    'city' => 'Москва',
    'street' => 'Ленина',
    'building' => '10',
    'room' => '5',
]);
// Автоматически: генерирует name, вычисляет hash для дедупликации
// Вызывает msOnBeforeAddCustomerAddress / msOnAddCustomerAddress

// Получить все адреса
$addresses = $addressManager->getByCustomerId($customerId);

// Получить один адрес
$address = $addressManager->getById($addressId);

// Обновить адрес
$addressManager->update($addressId, $customerId, [
    'city' => 'Санкт-Петербург',
    'street' => 'Невский',
]);

// Удалить адрес (проверяет принадлежность покупателю)
$addressManager->delete($addressId, $customerId);

Дедупликация адресов ​

Хеш адреса: MD5 от city|street|building|room в нижнем регистре. Адрес с уже существующим хешем повторно не создаётся.

Проверка дубликатов (CustomerDuplicateChecker) ​

Вызывается при создании покупателя.

php
$checker = $modx->services->get('ms3_customer_duplicate_checker');

// Найти существующего покупателя по данным
$existing = $checker->findDuplicate([
    'email' => 'user@example.com',
    'phone' => '+79991234567',
]);
// Ищет по OR: совпадение email ИЛИ телефона

// Проверить, есть ли данные для поиска
if ($checker->hasCheckableData($data)) {
    // Есть email или phone
}

// Изменить поля для проверки
$checker->setCheckFields(['email']);  // только по email

Настройка ms3_customer_duplicate_fields (JSON) задаёт поля для проверки. По умолчанию: ["email", "phone"].

Нормализация при сравнении ​

ПолеНормализация
emailПриведение к нижнему регистру
phoneИзвлечение только цифр (минимум 7)
ОстальныеОбрезка пробелов

Фабрика покупателей (CustomerFactory) ​

Создаёт покупателя из данных заказа при финализации в админке.

php
$factory = $modx->services->get('ms3_customer_factory');

$customer = $factory->createFromOrderData([
    'first_name' => 'Иван',
    'last_name' => 'Иванов',
    'email' => 'user@example.com',
    'phone' => '+79991234567',
]);

Если ms3_customer_sync_create_moduser = true, фабрика также создаёт modUser + modUserProfile и добавляет в группу из ms3_customer_sync_user_group.

Поля msCustomer ​

ПолеТипПо умолчаниюОписание
user_idinteger0ID пользователя MODX (опционально)
customer_group_idintegernullГруппа покупателя. Админка пишет её через PUT /api/mgr/customers/{id}
first_namestring''Имя
last_namestring''Фамилия
emailstring''Email
phonestring''Телефон
tokenstring''Токен текущей сессии (unique)
passwordstringnullХеш пароля (bcrypt)
email_verified_atdatetimenullДата подтверждения email
is_activebooleantrueАктивен
is_blockedbooleanfalseЗаблокирован
failed_login_attemptsinteger0Неудачных попыток входа
blocked_untildatetimenullЗаблокирован до
created_atdatetimenowДата создания
updated_atdatetimenullДата обновления
last_login_atdatetimenullПоследний вход
orders_countinteger0Количество заказов
total_spentfloat0.00Сумма заказов
last_order_atdatetimenullДата последнего заказа
privacy_accepted_atdatetimenullДата согласия на обработку данных
privacy_ipstringnullIP при согласии

Поля msCustomerAddress ​

ПолеТипПо умолчаниюОписание
customer_idinteger0ID покупателя
hashstringnullMD5-хеш адреса (дедупликация)
namestringnullНазвание (автогенерация из город/улица)
countrystringnullСтрана
indexstringnullПочтовый индекс
regionstringnullРегион
citystringnullГород
metrostringnullСтанция метро
streetstringnullУлица
buildingstringnullДом
entrancestringnullПодъезд
floorstringnullЭтаж
roomstringnullКвартира/офис
commenttextnullКомментарий
createdondatetimenullДата создания
updatedondatetimenullДата обновления
activeinteger1Адрес активен
is_defaultinteger0Адрес по умолчанию

Поля msCustomerToken ​

ПолеТипПо умолчаниюОписание
customer_idinteger0ID покупателя
tokenstring''Строка токена, в базе до 128 символов. Код выпускает 64 hex-символа
typeenum'api'Тип: api, refresh, magic_link, email_verification, password_reset
expires_atdatetime—Срок действия
created_atdatetimenowДата создания
used_atdatetimenullДата использования (для одноразовых)

Системные настройки ​

НастройкаПо умолчаниюОписание
ms3_customer_max_login_attempts5Попыток входа до блокировки
ms3_customer_block_duration300Длительность блокировки (секунды)
ms3_customer_token_ttl86400Время жизни токена сессии (секунды)
ms3_customer_require_email_verificationfalseТребовать подтверждение email
ms3_customer_send_welcome_emailtrueОтправлять приветственное письмо
ms3_customer_auto_login_after_registertrueАвтовход после регистрации
ms3_customer_auto_register_on_ordertrueАвторегистрация при оформлении
ms3_customer_auto_login_on_ordertrueАвтовход при оформлении
ms3_customer_duplicate_fields["email", "phone"]Поля для проверки дубликатов
ms3_customer_sync_create_moduserfalseСоздавать modUser при создании покупателя
ms3_customer_sync_user_group0ID группы MODX для новых пользователей
ms3_password_min_length8Минимальная длина пароля
ms3_password_reset_token_ttl3600Время жизни токена сброса пароля
ms3_email_verification_token_ttl86400Время жизни токена верификации

Настройка-двойник в админке

Рядом в том же разделе стоит ms3_customer_api_token_ttl. Её не читает ни одна строка кода, хотя значение такое же. Рабочая настройка: ms3_customer_token_ttl из таблицы выше (issue #797).

Ещё у двух настроек значение установщика расходится с запасным значением в коде (#898):

КлючУстановщикЕсли строки нет
ms3_customer_block_duration3003600 в AuthManager::handleFailedLogin()
ms3_customer_require_email_verificationfalsetrue в RegisterService и процессоре регистрации

Manager REST покупателей ​

Админка ходит сюда, а не в Processors\Customer\Update. Поэтому при правке покупателя в админке событие msOnBeforeUpdateCustomer не вызывается.

МетодПутьПраво
GET/api/mgr/customersmsorder_list
GET/api/mgr/customers/{id}msorder_view
PUT/api/mgr/customers/{id}msorder_save
DELETE/api/mgr/customers/{id}msorder_remove
DELETE/api/mgr/customers/bulkmsorder_remove
GET/api/mgr/customers/{id}/addressesmsorder_list
POST/api/mgr/customers/{id}/addressesmsorder_save
PUT/api/mgr/customers/{id}/addresses/{address_id}msorder_save
DELETE/api/mgr/customers/{id}/addresses/{address_id}msorder_remove

Группы покупателей, /api/mgr/customer-groups:

ДействиеПраво
Списокmsorder_list
Карточкаmsorder_view
Записьmsorder_save
Удалениеmsorder_remove

Процессоры для runProcessor() остаются рядом.

ПроцессорПраво
Customer\Get, Address\Getmsorder_view
оба GetListmsorder_list
оба Updatemsorder_save
оба Removemsorder_remove
GetListCombobox, Multipleсвоего права в классе нет

Customer\GetListCombobox принимает параметр id и при combo=1 поднимает перечисленные записи наверх выражением FIELD(). Значение попадает в SQL без подготовленного выражения, поэтому с 1.14.2-beta1 процессор прогоняет его через IntArrayDecoder::decode(). На выходе остаётся список положительных целых, остальное отбрасывается. Пишете свой combo-процессор с такой же сортировкой — повторите этот шаг, иначе id станет точкой внедрения SQL.

События ​

СобытиеКогда вызывается
msOnBeforeCreateCustomer / msOnCreateCustomerСоздание покупателя
msOnBeforeUpdateCustomer / msOnUpdateCustomerПроцессор Customer/Update. Встроенная админка его не вызывает (#847)
msOnBeforeAddToCustomer / msOnAddToCustomerИзменение поля через контроллер
msOnBeforeValidateCustomerValue / msOnValidateCustomerValueВалидация значения поля
msOnErrorValidateCustomerValueОшибка валидации значения
msOnBeforeGetOrderCustomer / msOnGetOrderCustomerПоиск/создание при оформлении
msOnBeforeAddCustomerAddress / msOnAddCustomerAddressДобавление адреса

Параметры событий: События.