fenestron
8/2/2019 - 1:40 PM

Extremebox Architecture

Описание Backend-части для Extremebox

Описание архитектуры

Сайт состоит из нескольких частей:

  • API - Запросы к backend-части приложения
  • CRM - Кабинет партнера + админка
  • CDN - Сеть доставки статичного контента (картинки)

Поиск

GET /search - api для поиска услуг.

Параметры:

  • date - Дата. ['required', 'date', 'after_or_equal:today']
  • a - id активностей. ['array', 'exists:activities,id']
  • members - Количество участников. ['integer', 'min:1']
  • time_from - Время начала поиска. ['date_format:H:i:s']
  • time_to - Время конца поиска. ['date_format:H:i:s']
  • price_from - Цена от. ['integer', 'min:0', 'max:200000']
  • price_to - Цена до. ['integer', 'min:0', 'max:200000']
  • c - Удобства. ['array', Rule::in(['parking', 'placement', 'rest_zone', 'shower', 'for_kids', 'canteen', 'own_food', 'bar', 'photographer', 'wifi'])]

Покупка

/prebuy

Перед совершением покупки необходимо проверить возможность совершения покупки и забронировать сеанс.

Если сеанс невозможно купить, то будет возвращена 418 ошибка с текстом "Сеанс занят".

Параметры:

  • service_id => ['required', 'exists:services,id'],
  • date => ['required', 'date', 'after_or_equal:today'],
  • time => ['required', 'date_format:H:i:s'],
  • duration => ['required', 'integer'],
  • members => ['required', 'integer', 'min:1'],

/cart/clean

Очищение корзины.

Параметры:

  • service_id => ['required', 'exists:services,id'],
  • date => ['required', 'date', 'after_or_equal:today'],
  • time => ['required', 'date_format:H:i:s'],
  • duration => ['required', 'integer'],
  • members => ['required', 'integer', 'min:1'],

Если пользователь в корзине нажимает на "Крестик", то вызывается этот метод и забронированный сеанс снова становится доступен для покупки.

Метод всегда возращает okResponse. Доступен только авторизованным пользователям.

/buy/seance

Покупка сеанса

Параметры:

  • service_id - ID услуги. ['required', 'exists:services,id']
  • date - Дата. ['required', 'date', 'after_or_equal:today']
  • time - Время. ['required', 'date_format:H:i:s']
  • members Кол-во участников- . ['required', 'integer', 'min:1']
  • is_gift - true если подарок. ['required', 'boolean']
  • promocode - code промокода. ['exists:promocodes,code']
  • payment_method - Способ оплаты. ['required', Rule::in(['cash', 'acquiring'])]
  • clients - Массив клиентов. ['array']
  • duration - Продолжительность сеанса. ['integer']
  • additional - Массив дополнительных услуг. ['array']

Пример корректных данных:

Сертификаты

/certificate/buy

Покупка сертификата

Параметры:

  • value => ['required', 'integer', 'min:1000', 'max:300000']
  • appearance => ['required',]
  • package => ['required', 'in:envelope,virtual']
  • payment_method => ['required', Rule::in(config('consts.payment_method'))]
  • email => ['required', 'email']
  • address => ['required_if:package,envelope']

/certificate/activate

Активация сертификата

Параметры:

  • code - Код сертификата

Алгоритмы

Вычисление цены

Используемые далее переменные возрщаются при вызове метода /search (в каждом элементе полученного массива) и при вызове метода /prebuy

Цена на конкретную услугу вычисляется по следующему алгоритму (псевдокод):

if ($this->datetime->isWeekend()) {
    $base_price = $this->option['pwe'];
} elseif ($this->datetime->isFriday() && $this->datetime->toTimeString() > '18:00:00') {
    $base_price = $this->option['pfa'];
} elseif ($this->datetime->isWeekday() && $this->datetime->toTimeString() > '18:00:00') {
    $base_price = $this->option['pda'];
} else {
    $base_price = $this->option['pdb'];
}

if ($this->service->price_type == 'team') {
    $price = $base_price;
} elseif ($this->service->price_type == 'fix_client') {
    $price = $this->service->fix_price + $base_price * $this->members;
} else {
    $price = $base_price * $this->members;
}

if (promocode) { // Промокод и его значение получается отдельным запросом.
  $price -= promocode->value;
}

Если коротко, то формула такая:

price = (base_price * (customer_type == 'team' ? 1 : members)) * (100 - max(discounts)) / 100 + (each_additional * each_additional_price) - promocode_value

Процесс оплаты:

Если у пользователя на балансе есть какое-то количество денег, то при оплате онлайн происходит следующее:

С баланса списывается min(balance, price) рублей, если price был меньше balance, т о сумма оплаты онлайн = 0 и мы просто говорим пользователю, что покупка прошла успешно (как при оплате наличными)

Соответственно нужно оповестить пользователя об этом (написать, что N рублей будет списано с баланса).

Покупка наличными / онлайн

payment_type автоматически становится acquiring и не может стать cash если:

  • Был использован промокод
  • Покупаемая услуга является горящим предложением
  • prepayment_amount == 100

Покупка в подарок

Для покупки в подарок пользователь может выбрать кого-то из своих "друзей". На бекенде "друзья" называются "Локальными клиентами". Чтобы получить список локальных клиентов пользователя, существует запрос - GET /v1/user/locals

Логика работы следующая: При покупке "в подарок" дергается этот запрос и пользователь может выбрать одного из своих "локальных клиентов". Помимо выбора, он может ввести данные нового локального клиента. При следующих покупках эти данные будут возвращаться в запросе.

Промокоды

Чтобы активировать промокод, необходимо сделать POST /v1/promocode/check

Передается одно поле - promocode - код промокода.

Если промокод существует и доступен для активации, то будет возвращено число - сумма в рублях, на которую действует введенный промокод.

Если промокод недействителен, то будет выброшена ошибка валидации.

Разделение по оплатам:

  • Если prepayment_amount == 100, то доступна только оплата онлайн 100% от цены

  • Если prepayment_amount == 0, то доступна оплата 100% онлайн и оплата 100% в клубе

  • Если 0 < prepayment_amount < 100, то действует следующая логика:

    Если выбрана оплата онлайн, то 100% от цены оплачивается онлайн.

    Если выбрана оплата в клубе, то покупка делится на 2 части: $cash - оплата в клубе, $acquiring - оплата онлайн. Эти переменные вычисляются по следующему алгоритму:

$cash = (int) (price * (100 - prepayment_amount) / 100)

$acquiring = price - $cash