1
0
mirror of synced 2024-11-30 01:06:06 +03:00
bitrix-module/intaro.retailcrm/lib/service/loyaltyaccountservice.php

787 lines
28 KiB
PHP
Raw Normal View History

<?php
/**
* @category Integration
* @package Intaro\RetailCrm\Service
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Service;
use CUser;
use Logger;
use DateTime;
use Intaro\RetailCrm\Component\ConfigProvider;
use Intaro\RetailCrm\Component\Factory\ClientFactory;
use Intaro\RetailCrm\Component\Json\Serializer;
use Intaro\RetailCrm\Component\ServiceLocator;
use Intaro\RetailCrm\Model\Api\IdentifiersPair;
use Intaro\RetailCrm\Model\Api\LoyaltyAccount;
use Intaro\RetailCrm\Model\Api\LoyaltyAccountApiFilterType;
use Intaro\RetailCrm\Model\Api\Request\Loyalty\Account\LoyaltyAccountRequest;
use Intaro\RetailCrm\Model\Api\Request\Loyalty\Account\LoyaltyAccountActivateRequest;
use Intaro\RetailCrm\Model\Api\Request\Loyalty\Account\LoyaltyAccountCreateRequest;
use Intaro\RetailCrm\Model\Api\Request\Loyalty\Account\LoyaltyAccountEditRequest;
use Intaro\RetailCrm\Model\Api\Request\SmsVerification\SmsVerificationConfirmRequest;
use Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountActivateResponse;
use Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountCreateResponse;
use Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountEditResponse;
use Intaro\RetailCrm\Model\Api\Response\SmsVerification\SmsVerificationConfirmResponse;
use Intaro\RetailCrm\Model\Api\Response\SmsVerification\SmsVerificationStatusRequest;
use Intaro\RetailCrm\Model\Api\Response\SmsVerification\SmsVerificationStatusResponse;
use Intaro\RetailCrm\Model\Api\SerializedCreateLoyaltyAccount;
use Intaro\RetailCrm\Model\Api\SmsVerificationConfirm;
use Intaro\RetailCrm\Model\Bitrix\User;
use Intaro\RetailCrm\Model\Bitrix\UserLoyaltyData;
use Intaro\RetailCrm\Repository\UserRepository;
/**
* Class LoyaltyAccountService
*/
class LoyaltyAccountService
{
public const STANDARD_FIELDS = [
'UF_AGREE_PL_INTARO' => 'checkbox',
'UF_PD_PROC_PL_INTARO' => 'checkbox',
'PERSONAL_PHONE' => 'text',
];
/**
* @var \Intaro\RetailCrm\Model\Bitrix\User|null
*/
private $user;
/**
* Получает статус текущего состояния верификации
*
* @param string $checkId Идентификатор проверки кода
*
* @return \Intaro\RetailCrm\Model\Api\Response\SmsVerification\SmsVerificationStatusResponse|null
*/
public function getSmsStatus(string $checkId): ?SmsVerificationStatusResponse
{
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$request = new SmsVerificationStatusRequest();
$request->checkId = $checkId;
return $client->checkStatusPlVerification($request);
}
/**
* @param int $loyaltyId
*
* @return \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountActivateResponse|null
*/
public function activateLoyaltyAccount(int $loyaltyId): ?LoyaltyAccountActivateResponse
{
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$activateRequest = new LoyaltyAccountActivateRequest();
$activateRequest->loyaltyId = $loyaltyId;
$response = $client->activateLoyaltyAccount($activateRequest);
if ($response === null) {
return null;
}
if ($response->success && $response->loyaltyAccount->activatedAt instanceof DateTime) {
return $response;
}
Utils::handleApiErrors($response);
return $response;
}
/**
* @param string $phone
* @param string $card
* @param string $externalId
*
* @return \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountCreateResponse|null
*/
public function createLoyaltyAccount(string $phone, string $card, string $externalId): ?LoyaltyAccountCreateResponse
{
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$sitesAvailable = ConfigProvider::getSitesAvailable();
$createRequest = new LoyaltyAccountCreateRequest();
$createRequest->site = $sitesAvailable;
$createRequest->loyaltyAccount = new SerializedCreateLoyaltyAccount();
$createRequest->loyaltyAccount->phoneNumber = $phone ?? '';
$createRequest->loyaltyAccount->cardNumber = $card ?? '';
$createRequest->loyaltyAccount->customFields = [];
$createRequest->loyaltyAccount->customer = new IdentifiersPair();
$createRequest->loyaltyAccount->customer->externalId = $externalId;
$createResponse = $client->createLoyaltyAccount($createRequest);
if ($createResponse instanceof LoyaltyAccountCreateResponse) {
Utils::handleApiErrors($createResponse, GetMessage('REGISTER_ERROR'));
}
return $createResponse;
}
/**
* @param \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountCreateResponse|null $createResponse
* @param int $userId
*/
public function activateLpUserInBitrix(?LoyaltyAccountCreateResponse $createResponse, int $userId): void
{
//если участник ПЛ создан
if (($createResponse !== null)
&& $createResponse->success === true
) {
global $USER_FIELD_MANAGER;
$USER_FIELD_MANAGER->Update('USER', $userId, [
'UF_EXT_REG_PL_INTARO' => $createResponse->loyaltyAccount->active === true ? 'Y' : '',
'UF_LP_ID_INTARO' => $createResponse->loyaltyAccount->id,
]);
Utils::handleApiErrors($createResponse, GetMessage('REGISTER_ERROR'));
} elseif ($this->proveUserInLpExists($createResponse)) {
global $USER_FIELD_MANAGER;
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$sitesAvailable = ConfigProvider::getSitesAvailable();
$getRequest = new LoyaltyAccountRequest();
$getRequest->filter = new LoyaltyAccountApiFilterType();
$getRequest->filter->sites = is_array($sitesAvailable) ? $sitesAvailable : [$sitesAvailable];
$getRequest->filter->customerExternalId = (string)$userId;
$getResponse = $client->getLoyaltyAccounts($getRequest);
if ($getResponse instanceof LoyaltyAccountCreateResponse) {
Utils::handleApiErrors($getResponse, GetMessage('REGISTER_ERROR'));
}
$USER_FIELD_MANAGER->Update('USER', $userId, [
'UF_EXT_REG_PL_INTARO' => ($getResponse->loyaltyAccounts)[0]->active === true ? 'Y' : '',
'UF_LP_ID_INTARO' => ($getResponse->loyaltyAccounts)[0]->id,
]);
}
}
/**
* Подтверждает верификацию
*
* @param string $code Проверочный код
* @param string $checkId Идентификатор проверки кода
*
* @return \Intaro\RetailCrm\Model\Api\Response\SmsVerification\SmsVerificationConfirmResponse|null
*/
public function confirmVerification(string $code, string $checkId): ?SmsVerificationConfirmResponse
{
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$request = new SmsVerificationConfirmRequest();
$request->verification = new SmsVerificationConfirm();
$request->verification->code = $code;
$request->verification->checkId = $checkId;
$response = $client->sendVerificationCode($request);
if ($response !== null) {
Utils::handleApiErrors($response, GetMessage('DEBITING_BONUSES_ERROR'));
}
return $response;
}
/**
* Возвращает статус пользователя в системе лояльности
*
* @param $userId
*
* @return bool
*/
public static function getLoyaltyPersonalStatus($userId = null): bool
{
if ($userId === null) {
global $USER;
$userId = $USER->GetID();
}
$userFields = CUser::GetByID($userId)->Fetch();
return isset($userFields['UF_EXT_REG_PL_INTARO']) && $userFields['UF_EXT_REG_PL_INTARO'] === '1';
}
/**
* Возвращает ID аккаунта в программе лояльности
*
* @param $userId
*
* @return int|null
*/
public static function getLoyaltyAccountId($userId): ?int
{
$userFields = CUser::GetByID($userId)->Fetch();
return $userFields['UF_LP_ID_INTARO'] ?? null;
}
/**
* Метод возвращает значение для поля 'privilegeType'. Валидация поля происходит по кейсам:
* 1. Программа лояльност не работает с корп. клиентами.
* 2. Участие клиента не найдено в CRM, соответственно либо нет участия, либо нет клиента.
* 3. Участие клиента найдено, но не активно (поле status = deactivated).
* 4. Программа лояльности деактивирована в CRM.
*
* @param $client
* @param $userId
* @param array $arParams
*
* @return string
*/
public static function getPrivilegeType($client, $userId, array $arParams = []): string
{
$file = 'loyaltyStatus';
$privilegeType = 'none';
if (!empty($arParams['customerCorporate']['privilegeType'])) {
$privilegeType = $arParams['crmOrder']['privilegeType'];
} elseif (ConfigProvider::getLoyaltyProgramStatus() === 'Y' && self::getLoyaltyPersonalStatus($userId)) {
$privilegeType = 'loyalty_level';
}
if (!empty($arParams['customerCorporate']) || !empty($arParams['orderCompany'])) {
return 'none';
}
if ($privilegeType === 'none') {
return 'none';
}
$loyaltyAccountId = self::getLoyaltyAccountId($userId);
if ($loyaltyAccountId === null) {
Logger::getInstance()->write('Участие клиента не найдено', $file);
return 'none';
}
$loyaltyAccount = $client->getLoyaltyAccount($loyaltyAccountId)->loyaltyAccount ?? null;
if ($loyaltyAccount === null) {
Logger::getInstance()->write('Участие клиента c ID: ' . $loyaltyAccountId . ' не найдено', $file);
return 'none';
}
if ($loyaltyAccount->status === 'deactivated') {
Logger::getInstance()->write('Участие клиента c ID: ' . $loyaltyAccountId . ' деактивированно', $file);
return 'none';
}
$loyaltyId = $loyaltyAccount->loyalty->id ?? null;
if ($loyaltyId === null) {
Logger::getInstance()->write('Программа лояльности не найдена', $file);
return 'none';
}
$loyalty = $client->getLoyaltyLoyalty($loyaltyId)->loyalty ?? null;
if ($loyalty === null) {
Logger::getInstance()->write('Программа лояльности c ID: ' . $loyaltyId . ' не найдена', $file);
return 'none';
}
if (!$loyalty->active) {
Logger::getInstance()->write('Программа лояльности c ID: ' . $loyaltyId . ' деактивированна', $file);
return 'none';
}
return $privilegeType;
}
/**
* @return array|null
* @throws \ReflectionException
*/
public function checkRegInLp(): ?array
{
global $USER;
if ($USER->IsAuthorized()) {
$this->user = UserRepository::getById($USER->GetID());
}
if (!$this->user) {
return [];
}
$loyalty = $this->user->getLoyalty();
//Изъявлял ли ранее пользователь желание участвовать в ПЛ?
if ($loyalty->getIsAgreeRegisterInLoyaltyProgram() === 1) {
//ДА. Существует ли у него аккаунт?
if (!empty($loyalty->getIdInLoyalty())) {
//ДА. Активен ли его аккаунт?
if ($loyalty->getIsUserLoyaltyAccountActive() === 1) {
//ДА. Отображаем сообщение "Вы зарегистрированы в Программе лояльности"
return ['msg' => GetMessage('REG_COMPLETE')];
}
//НЕТ. Аккаунт не активен
return $this->tryActivate($loyalty->getIdInLoyalty());
}
// Аккаунт не существует. Выясняем, каких полей не хватает для СОЗДАНИЯ аккаунта, выводим форму
//Если все необходимые поля заполнены, то пытаемся его еще раз зарегистрировать
if (count($this->getStandardFields($this->user)) === 0) {
$createResponse = $this->registerAndActivateUser($this->user->getId(), $this->user->getPersonalPhone(), $loyalty);
if ($createResponse === false) {
header('Refresh 0');
}
if (isset($createResponse['error']) && $createResponse['error']) {
$createResponse['form']['button'] = [
'name' => GetMessage('TRY_AGAIN'),
'action' => 'resetUserLpFields'
];
}
return $createResponse;
}
return [
'msg' => GetMessage('COMPLETE_YOUR_REGISTRATION'),
'form' => [
'button' => [
'name' => GetMessage('CREATE'),
'action' => 'saveUserLpFields',
],
'fields' => $this->getStandardFields($this->user),
'externalFields' => (array) json_decode(ConfigProvider::getLoyaltyFields(), true),
],
];
}
//НЕТ. Отображаем форму на создание новой регистрации в ПЛ
return [
'msg' => GetMessage('INVITATION_TO_REGISTER'),
'form' => [
'button' => [
'name' => GetMessage('CREATE'),
'action' => 'saveUserLpFields',
],
'fields' => $this->getStandardFields($this->user),
'externalFields' => (array) json_decode(ConfigProvider::getLoyaltyFields(), true),
],
];
}
/**
* @param int $idInLoyalty
*
* @return array
* @throws \ReflectionException
*/
public function tryActivate(int $idInLoyalty): array
{
global $USER;
/** @var \Intaro\RetailCrm\Service\CookieService $service */
$service = ServiceLocator::get(CookieService::class);
$smsCookie = $service->getSmsCookie('lpRegister');
$nowTime = new DateTime();
if ($smsCookie !== null
&& isset($smsCookie->resendAvailable)
&& $smsCookie->resendAvailable > $nowTime
) {
return [
'msg' => GetMessage('SMS_VERIFICATION'),
'form' => [
'button' => [
'name' => GetMessage('SEND'),
'action' => 'sendVerificationCode',
],
'fields' => [
'smsVerificationCode' => [
'type' => 'text',
],
'checkId' => [
'type' => 'hidden',
'value' => $smsCookie->checkId,
],
],
],
'resendAvailable' => $smsCookie->resendAvailable->format('Y-m-d H:i:s'),
'idInLoyalty' => $idInLoyalty,
];
}
//Пробуем активировать аккаунт
$activateResponse = $this->activateLoyaltyAccount($idInLoyalty);
if ($activateResponse === null) {
return ['msg' => GetMessage('ACTIVATE_ERROR')];
}
//Если отметка не стоит, но аккаунт активирован на стороне CRM
if (
($activateResponse->success && $activateResponse->loyaltyAccount->active === true)
|| $activateResponse->errorMsg === GetMessage('ALREADY_ACTIVE')
) {
$this->setLoyaltyActivateFlag($USER->GetID());
return ['msg' => GetMessage('REG_COMPLETE')];
}
$requiredFields = $this->checkErrors($activateResponse);
//если есть незаполненные обязательные поля
if ($_GET['activate'] === 'Y' && count($requiredFields) > 0) {
return [
'msg' => GetMessage('ACTIVATE_YOUR_ACCOUNT'),
'form' => [
'button' => [
'name' => GetMessage('ACTIVATE'),
'action' => 'activateAccount',
],
'fields' => $this->getStandardFields($this->user),
'externalFields' => $this->getExternalFields($requiredFields),
],
];
}
if ($_GET['activate'] !== 'Y' && count($requiredFields) > 0) {
return [
'msg' => GetMessage('GO_TO_PERSONAL'),
];
}
if ($activateResponse !== null
&& isset($activateResponse->loyaltyAccount->active)
&& $activateResponse->loyaltyAccount->active === true
) {
return ['msg' => GetMessage('REG_COMPLETE')];
}
//нужна смс верификация
if (isset($activateResponse->verification, $activateResponse->verification->checkId)
&& $activateResponse !== null
&& !isset($activateResponse->verification->verifiedAt)
) {
$smsCookie = $service->setSmsCookie('lpRegister', $activateResponse->verification);
return [
'msg' => GetMessage('SMS_VERIFICATION'),
'form' => [
'button' => [
'name' => GetMessage('SEND'),
'action' => 'sendVerificationCode',
],
'fields' => [
'smsVerificationCode' => [
'type' => 'text',
],
'checkId' => [
'type' => 'hidden',
'value' => $smsCookie->checkId,
],
],
],
'resendAvailable' => $smsCookie->resendAvailable->format('Y-m-d H:i:s'),
'idInLoyalty' => $idInLoyalty,
];
}
if (
count($activateResponse->errors) === 1
&& strpos(current($activateResponse->errors), GetMessage('FIELD_ERROR')) !== false
) {
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$client->getLoyaltyLoyalty((int)ConfigProvider::getLoyaltyProgramId());
}
return ['msg' => GetMessage('ACTIVATE_ERROR') . ' ' . $activateResponse->errorMsg ?? ''];
}
/**
* @param int|null $userId
*/
public function setLoyaltyActivateFlag(?int $userId): void
{
if ($userId === null) {
return;
}
global $USER_FIELD_MANAGER;
$USER_FIELD_MANAGER->Update('USER', $userId, ['UF_EXT_REG_PL_INTARO' => true]);
}
/**
* @param int $loyaltyId
* @param array $getEntityFields
*
* @return \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountEditResponse|null
*/
public function editLoyaltyAccount(int $loyaltyId, array $getEntityFields): ?LoyaltyAccountEditResponse
{
$request = new LoyaltyAccountEditRequest();
$request->id = $loyaltyId;
$request->loyaltyAccount = new LoyaltyAccount();
$request->loyaltyAccount->phoneNumber = $getEntityFields['phoneNumber'] ?? '';
$request->loyaltyAccount->cardNumber = $getEntityFields['cardNumber'] ?? '';
unset($getEntityFields['phoneNumber'], $getEntityFields['cardNumber']);
$request->loyaltyAccount->customFields = $getEntityFields;
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
try {
return $client->editLoyaltyAccount($request);
} catch (\ReflectionException $exception){
return null;
}
}
/**
* @param array $requireFields
*
* @return array
*/
private function getExternalFields(array $requireFields): array
{
$fieldSettings = json_decode(ConfigProvider::getLoyaltyFields(), true);
$missingFields = $this->compareFields(
$requireFields,
(array)$fieldSettings
);
if (count($missingFields) > 0) {
$fieldSettings = $this->updateLoyaltyFieldsSettings();
}
return $this->getRequireFieldsSettings($requireFields, $fieldSettings);
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\User $user
*
* @return array
* @throws \ReflectionException
*/
private function getStandardFields(User $user): array
{
$resultFields = [];
$userFields = Serializer::serializeArray($user->getLoyalty());
$userFields['PERSONAL_PHONE'] = $user->getPersonalPhone();
foreach (self::STANDARD_FIELDS as $key => $value) {
if ($value === 'text' && empty($userFields[$key])) {
$resultFields[$key] = [
'type' => $value,
];
}
if ($value === 'checkbox' && $userFields[$key] !== 1) {
$resultFields[$key] = [
'type' => $value,
];
}
}
return $resultFields;
}
/**
* @param int $userId
* @param string $userPhone
* @param \Intaro\RetailCrm\Model\Bitrix\UserLoyaltyData $loyalty
*
* @return array|string[]|null
*/
private function registerAndActivateUser(
int $userId,
string $userPhone,
UserLoyaltyData $loyalty
): ?array {
$phone = $userPhone ?? '';
$card = $loyalty->getBonusCardNumber() ?? '';
$customerId = (string)$userId;
$createResponse = $this->createLoyaltyAccount($phone, $card, $customerId);
$this->activateLpUserInBitrix($createResponse, $userId);
$errorMsg = Utils::getErrorMsg($createResponse);
if ($this->proveUserInLpExists($createResponse)) {
if (
$createResponse->loyaltyAccount->active === false
&& $createResponse->loyaltyAccount->activatedAt === null
) {
return [
'msg' => GetMessage('GO_TO_PERSONAL'),
'error' => false
];
} else {
return ['msg' => GetMessage('REG_COMPLETE'), 'error' => false];
}
}
if ($errorMsg !== null) {
return ['msg' => $errorMsg, 'error' => true];
}
//Создать получилось, но аккаунт не активен
if ($createResponse !== null
&& $createResponse->success === true
&& $createResponse->loyaltyAccount->active === false
&& $createResponse->loyaltyAccount->activatedAt === null
&& isset($createResponse->loyaltyAccount->id)
) {
return [
'msg' => GetMessage('GO_TO_PERSONAL'),
'error' => false
];
}
if ($createResponse !== null && $createResponse->success === true) {
//Повторная регистрация оказалась удачной
return ['msg' => GetMessage('REG_COMPLETE'), 'error' => false];
}
return [];
}
/**
* @return array
*/
private function updateLoyaltyFieldsSettings(): array
{
/** @var \Intaro\RetailCrm\Component\ApiClient\ClientAdapter $client */
$client = ClientFactory::createClientAdapter();
$loyaltiesResponse = $client->getLoyaltyLoyalties();
if ($loyaltiesResponse !== null && isset($loyaltiesResponse->loyalties[0])) {
ConfigProvider::setLoyaltyProgramId($loyaltiesResponse->loyalties[0]->id);
$loyalty = $client->getLoyaltyLoyalty((int)$loyaltiesResponse->loyalties[0]->id);
if (null !== $loyalty) {
$json = json_encode($loyalty->requiredFields);
ConfigProvider::setLoyaltyFields($json);
}
}
return (array)json_decode($json, true);
}
/**
* @param array $requireFields
* @param array $fieldSettings
*
* @return array
*/
private function compareFields(array $requireFields, array $fieldSettings): array
{
return array_diff($requireFields, array_column($fieldSettings, 'code'));
}
/**
* @param \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountActivateResponse $activateResponse
*
* @return array
*/
private function checkErrors(LoyaltyAccountActivateResponse $activateResponse): array
{
if (
count($activateResponse->errors) > 0
&& strpos(current($activateResponse->errors), GetMessage('LOYALTY_FIELD_ERROR')) !== false
) {
return (array)explode(
', ',
trim(str_replace(GetMessage('LOYALTY_FIELD_ERROR'), '', current($activateResponse->errors)))
);
}
return [];
}
/**
* @param array $requireFields
* @param array $fieldSettings
*
* @return array
*/
private function getRequireFieldsSettings(array $requireFields, array $fieldSettings): array
{
$resultFieldsArray = [];
$codes = array_column($fieldSettings, 'code');
$defaultFieldsCodes = ['Last name' => 'lastName', 'Email' => 'email', 'Phone number' => 'phoneNumber', 'First Name' => 'firstName'];
foreach ($requireFields as $requireField) {
if (isset($defaultFieldsCodes[$requireField])) {
$requireField = $defaultFieldsCodes[$requireField];
}
$key = array_search($requireField, $codes, true);
if ($key === false) {
continue;
}
$resultFieldsArray[] = $fieldSettings[$key];
}
return $resultFieldsArray;
}
/**
* @param \Intaro\RetailCrm\Model\Api\Response\Loyalty\Account\LoyaltyAccountCreateResponse|null $createResponse
*
* @return bool
*/
public function proveUserInLpExists(?LoyaltyAccountCreateResponse $createResponse): bool
{
if (
$createResponse !== null
&& $createResponse->success !== true
&& isset($createResponse->errors)
&& isset(($createResponse->errors)['loyalty'])
&& ($createResponse->errors)['loyalty'] === 'The customer is in this loyalty program already'
) {
$response = true;
} else {
$response = false;
}
return $response;
}
}