Skip to content

customer/add API должен поддерживать extra fields msCustomer #261

Closed

Description

@biz87

Описание

После мержа #260 эндпойнт POST /api/v1/customer/add (метод ms3.customer.add() в storefront-JS) работает только для жёсткого whitelist полей first_name, last_name, email, phone. Любая попытка передать другое поле возвращает ошибку ms3_customer_err_field_not_allowed.

Это режет механизм расширения через Утилиты → Свои поля → msCustomer. Если магазин создал extra field (например, loyalty_card, birthday, referral_source), он не может быть обновлён через быстрый API — единственный официальный канал для одиночного поля.

Воспроизведение

  1. В админке: Утилиты → Свои поля → msCustomer → Создать поле loyalty_card (textfield).
  2. На фронте:
    await ms3.customer.add('loyalty_card', '12345');
  3. Получаем 400 с сообщением «Это поле нельзя изменить через быстрый профиль».

Корень проблемы

В CustomerProfileController::updateField() (src/Controllers/Api/Web/CustomerProfileController.php):

$rules = $this->getProfileFieldRules();
if (!isset($rules[$key])) {
    return $this->error($this->modx->lexicon('ms3_customer_err_field_not_allowed'));
}

Метод getProfileFieldRules() возвращает фиксированный whitelist из 4 полей. Все остальное — отсекается.

Предлагаемое решение

Заменить whitelist на blacklist опасных полей + проверку существования поля в xPDO-карте msCustomer. Карта подмешивает extra fields через ms3->loadMap(), поэтому проверка по getFieldMeta(msCustomer::class) автоматически покрывает оба случая — штатные поля и extra fields.

Эскиз:

private const FORBIDDEN_FIELDS = [
    'id', 'token', 'user_id', 'password',
    'is_active', 'email_verified_at',
    'createdon', 'updatedon',
];

public function updateField(array $data): array {
    $key = trim((string)($data['key'] ?? ''));
    $value = trim((string)($data['value'] ?? ''));

    // 1. Запрет чувствительных полей
    if (in_array($key, self::FORBIDDEN_FIELDS, true)) {
        return $this->error($this->modx->lexicon('ms3_customer_err_field_not_allowed'));
    }

    // 2. Поле должно существовать в модели (вкл. extra fields)
    $fieldMeta = $this->modx->getFieldMeta(msCustomer::class);
    if (!isset($fieldMeta[$key])) {
        return $this->error($this->modx->lexicon('ms3_customer_err_field_not_allowed'));
    }

    // 3. Жёсткая валидация для типовых полей, best-effort для остальных
    $rules = $this->getProfileFieldRules();
    if (isset($rules[$key])) {
        $validation = (new Validator())->make([$key => $value], [$key => $rules[$key]]);
        // ...
    }

    // 4. Email — уникальность + сброс верификации
    if ($key === 'email') {
        if (!$this->isEmailAvailable($customer, $value)) {
            return $this->error($this->modx->lexicon('ms3_customer_err_email_exists'));
        }
        $this->resetEmailVerificationIfChanged($customer, $value);
    }

    $customer->set($key, $value);
    // ...
}

После этого:

  • ms3.customer.add('loyalty_card', '12345') — работает.
  • ms3.customer.add('user_id', 999) — блокируется (в FORBIDDEN_FIELDS).
  • ms3.customer.add('totally_made_up_field', 'x') — блокируется (нет в xPDO-карте).
  • ms3.customer.add('email', 'new@example.com') — работает с полной валидацией (уникальность, сброс верификации).

Open questions для имплементации

  1. Валидация типов extra fields. Сейчас в updateField() нет валидации для не-whitelist полей. Можно ли подтянуть type info из определения extra field (type = textfield/numberfield/checkbox/datefield) и валидировать соответственно? Или достаточно best-effort trim((string)$value) + $customer->set()?
  2. FORBIDDEN_FIELDS — какие ещё стоит добавить? Нужно ли запрещать поля, которыми оперируют другие сервисы (OrderUserResolver, CustomerEmailController)?
  3. Аналогичный подход для msOrder? В OrderFieldManager::add() есть похожая логика валидации полей заказа — там тоже жёсткий whitelist или поддержка extra fields?

Связанное

Activity

  1. added theissue type on May 17, 2026
  2. added 2 commits that reference this issue on May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions