From 01fe3c9741071d8597313b3a5d27eb98293bb836 Mon Sep 17 00:00:00 2001 From: Kocmonavtik <61938582+Kocmonavtik@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:57:57 +0300 Subject: [PATCH] ref #71456 Added loyalty program support (#338) --- CHANGELOG.md | 3 + VERSION | 2 +- resources/pot/retailcrm-es_ES.pot | 145 ++++- resources/pot/retailcrm-ru_RU.pot | 141 +++++ src/assets/css/retailcrm-loyalty-style.css | 34 ++ src/assets/js/retailcrm-loyalty-actions.js | 121 ++++ src/assets/js/retailcrm-loyalty.js | 36 ++ .../class-wc-retailcrm-abstracts-settings.php | 36 ++ .../api/class-wc-retailcrm-client-v5.php | 403 +++++++------ src/include/class-wc-retailcrm-base.php | 262 ++++++++- src/include/class-wc-retailcrm-loyalty.php | 539 ++++++++++++++++++ src/include/class-wc-retailcrm-orders.php | 113 +++- .../class-wc-retailcrm-loyalty-form.php | 90 +++ src/include/functions.php | 15 + .../order/class-wc-retailcrm-order-item.php | 107 +++- .../class-wc-retailcrm-loyalty-constraint.php | 33 ++ .../class-wc-retailcrm-loyalty-validator.php | 120 ++++ src/languages/retailcrm-es_ES.l10n.php | 50 +- src/languages/retailcrm-es_ES.mo | Bin 13576 -> 18403 bytes src/languages/retailcrm-ru_RU.l10n.php | 50 +- src/languages/retailcrm-ru_RU.mo | Bin 16757 -> 22678 bytes src/readme.txt | 5 +- src/retailcrm.php | 6 +- src/uninstall.php | 2 +- tests/datasets/data-loyalty-retailcrm.php | 345 +++++++++++ .../class-wc-retailcrm-test-case-helper.php | 5 +- tests/loyalty/test-wc-retailcrm-client-v5.php | 131 +++++ tests/test-wc-retailcrm-base.php | 5 + tests/test-wc-retailcrm-loyalty.php | 495 ++++++++++++++++ tests/test-wc-retailcrm-orders.php | 6 +- .../test-wc-retailcrm-loyalty-validator.php | 142 +++++ 31 files changed, 3218 insertions(+), 224 deletions(-) create mode 100644 src/assets/css/retailcrm-loyalty-style.css create mode 100644 src/assets/js/retailcrm-loyalty-actions.js create mode 100644 src/assets/js/retailcrm-loyalty.js create mode 100644 src/include/class-wc-retailcrm-loyalty.php create mode 100644 src/include/components/class-wc-retailcrm-loyalty-form.php create mode 100644 src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-constraint.php create mode 100644 src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-validator.php create mode 100644 tests/datasets/data-loyalty-retailcrm.php create mode 100644 tests/loyalty/test-wc-retailcrm-client-v5.php create mode 100644 tests/test-wc-retailcrm-loyalty.php create mode 100644 tests/validators/test-wc-retailcrm-loyalty-validator.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e014f..2c43e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2024-07-15 4.8.0 +* Added loyalty program + ## 2024-06-27 4.7.9 * Fixed undefined array key number in order history diff --git a/VERSION b/VERSION index 3c31ca9..88f1811 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.7.9 +4.8.0 diff --git a/resources/pot/retailcrm-es_ES.pot b/resources/pot/retailcrm-es_ES.pot index 970ebdc..a09c4d5 100644 --- a/resources/pot/retailcrm-es_ES.pot +++ b/resources/pot/retailcrm-es_ES.pot @@ -445,8 +445,149 @@ msgstr "Se requiere clave API con acceso a una tienda" msgid "The currency of the site differs from the currency of the store in CRM. For the integration to work correctly, the currencies in CRM and CMS must match" msgstr "La moneda del sitio web es distinto a la tienda del CRM. Para el funcionamiento correcto de la integración, las monedas del CMS y CRM deben coincid" +msgid "Loyalty program" +msgstr "Programa de fidelización" + +msgid "Activate program loyalty" +msgstr "Activar programa de fidelización" + +msgid "Enable this setting for activate program loyalty on site" +msgstr "Activa esta configuración para activar programa de fidelización en la página web" + +msgid "Terms of loyalty program" +msgstr "Condiciones del programa de fidelización" + +msgid "Insert the terms and conditions of the loyalty program" +msgstr "Introduce las condiciones del programa de fidelización" + +msgid "Conditions of personal data processing" +msgstr "Condiciones de procesamiento de datos personales" + +msgid "Insert the terms and conditions for processing personal data" +msgstr "Introduce las condiciones para el procesamiento de datos personales" + +msgid "To activate the loyalty program it is necessary to activate the 'enable use of coupons option'" +msgstr "Para activar el programa de fidelización es necesario activar la opción 'habilitar el uso de cupones'" + +msgid "Bonus account" +msgstr "Cuenta de bonos" + +msgid "Participation ID: " +msgstr "ID de participación: " + +msgid "Current level: " +msgstr "Nivel actual: " + +msgid "Bonuses on the account: " +msgstr "Bonos en la cuenta: " + +msgid "Bonus card number: " +msgstr "Número de tarjeta de bonos: " + +msgid "Date of registration: " +msgstr "Fecha de registro: " + +msgid "Current level rules" +msgstr "Reglas del nivel actual" + +msgid "Required amount of purchases to move to the next level: " +msgstr "Cantidad de compras necesarias para pasar al siguiente nivel: " + +msgid "Activate participation in the loyalty program" +msgstr "Activar participación en el programa de fidelización" + +msgid "Send" +msgstr "Enviar" + +msgid "To register in the loyalty program, fill in the form:" +msgstr "Para registrarse en el programa de fidelización, rellena el formulario:" + +msgid " I agree with " +msgstr " Estoy de acuerdo con " + +msgid "loyalty program terms" +msgstr "términos del programa de fidelización" + +msgid "terms of personal data processing" +msgstr "términos de procesamiento de datos personales" + +msgid "Phone" +msgstr "Teléfono" + +msgid "Error while registering in the loyalty program. Try again later" +msgstr "Error al registrarse en el programa de fidelización. Inténtalo de nuevo más tarde" + +msgid "The card is not linked" +msgstr "La tarjeta no está vinculada" + +msgid "Error while retrieving data. Try again later" +msgstr "Error al recuperar los datos. Inténtalo de nuevo más tarde" + +msgid "Error when activating the loyalty program. Try again later" +msgstr "Error al activar el programa de fidelización. Inténtalo de nuevo más tarde" + +msgid "Enter the correct phone number" +msgstr "Introduce el número de teléfono correcto" + +msgid "Close" +msgstr "Cerrar" + +msgid "Ordinary products: accrual of 1 bonus for each %s %s" +msgstr "Productos ordinarios: acumulación de 1 bono por cada %s %s" + +msgid "Promotional products: accrual of 1 bonus for each %s %s" +msgstr "Productos promocionales: acumulación de 1 bono por cada %s %s" + +msgid "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount" +msgstr "Productos ordinarios: acumulación de bonos en la cantidad de %s%% de la suma de la compra" + +msgid "Promotional products: bonus accrual in the amount of %s%% of the purchase amount" +msgstr "Productos promocionales: acumulación de bonos en la cantidad de %s%% de la suma de la compra" + +msgid "Ordinary products: %s%% discount" +msgstr "Productos ordinarios: %s%% de descuento" + +msgid "Promotional products: %s%% discount" +msgstr "Productos promocionales: %s%% de descuento" + msgid "Uploading services" -msgstr "Descarga de servicios" +msgstr "Subida de servicios" msgid "Goods with the 'virtual' option enabled will be uploaded to Simla as services" -msgstr "Los bienes con la opción 'virtual' activada se cargarán en Simla como servicios" +msgstr "Los productos con la opción 'virtual' activada se subirán a Simla como servicios" + +msgid "User not found in the system" +msgstr "Usuario no encontrado en el sistema" + +msgid "Error when searching for participation in loyalty programs" +msgstr "Error al buscar la participación en programas de fidelización" + +msgid "No active participation in the loyalty program was detected" +msgstr "No se detectó la participación activa en el programa de fidelización" + +msgid "No bonuses for debiting" +msgstr "No hay bonos para debitar" + +msgid "Loyalty program not found" +msgstr "Programa de fidelización no encontrado" + +msgid "Loyalty program is not active" +msgstr "Programa de fidelización no está activo" + +msgid "Loyalty program blocked" +msgstr "Programa de fidelización bloqueado" + +msgid "This user is a corporate person" +msgstr "Este usuario es una persona jurídica" + +msgid "It is possible to write off" +msgstr "Es posible debitar" + +msgid "bonuses" +msgstr "bonos" + +msgid "Use coupon:" +msgstr "Utiliza el cupón:" + +msgid "Points will be awarded upon completion of the order:" +msgstr "Los puntos se concederán al finalizar el pedido:" diff --git a/resources/pot/retailcrm-ru_RU.pot b/resources/pot/retailcrm-ru_RU.pot index b353e9a..d6cf7c0 100644 --- a/resources/pot/retailcrm-ru_RU.pot +++ b/resources/pot/retailcrm-ru_RU.pot @@ -454,8 +454,149 @@ msgstr "Требуется API ключ с доступом к одному ма msgid "The currency of the site differs from the currency of the store in CRM. For the integration to work correctly, the currencies in CRM and CMS must match" msgstr "Валюта сайта отличается от валюты магазина в CRM. Для корректной работы интеграции, валюты в CRM и CMS должны совпадать" +msgid "Loyalty program" +msgstr "Программа лояльности" + +msgid "Activate program loyalty" +msgstr "Активировать программу лояльности" + +msgid "Enable this setting for activate program loyalty on site" +msgstr "Активируйте эту настройку для активации программы лояльности на сайте" + +msgid "Terms of loyalty program" +msgstr "Условия программы лояльности" + +msgid "Insert the terms and conditions of the loyalty program" +msgstr "Вставьте условия участия в программе лояльности" + +msgid "Conditions of personal data processing" +msgstr "Условия обработки персональных данных" + +msgid "Insert the terms and conditions for processing personal data" +msgstr "Вставьте условия обработки персональных данных" + +msgid "To activate the loyalty program it is necessary to activate the 'enable use of coupons option'" +msgstr "Для активации программы лояльности необходимо активировать опцию 'включить использование купонов'" + +msgid "Bonus account" +msgstr "Бонусный счёт" + +msgid "Participation ID: " +msgstr "ID участия: " + +msgid "Current level: " +msgstr "Текущий уровень: " + +msgid "Bonuses on the account: " +msgstr "Бонусов на счёте: " + +msgid "Bonus card number: " +msgstr "Номер бонусной карты: " + +msgid "Date of registration: " +msgstr "Дата регистрации: " + +msgid "Current level rules" +msgstr "Правила текущего уровня" + +msgid "Required amount of purchases to move to the next level: " +msgstr "Необходимая сумма покупок для перехода на следующий уровень: " + +msgid "Activate participation in the loyalty program" +msgstr "Активировать участие в программе лояльности" + +msgid "Send" +msgstr "Отправить" + +msgid "To register in the loyalty program, fill in the form:" +msgstr "Для регистрации в программе лояльности заполните форму:" + +msgid " I agree with " +msgstr " Я согласен с " + +msgid "loyalty program terms" +msgstr "условиями программы лояльности" + +msgid "terms of personal data processing" +msgstr "условиями обработки персональных данных" + +msgid "Phone" +msgstr "Телефон" + +msgid "Error while registering in the loyalty program. Try again later" +msgstr "Ошибка при регистрации в программе лояльности. Попробуйте позже" + +msgid "The card is not linked" +msgstr "Карта не привязана" + +msgid "Error while retrieving data. Try again later" +msgstr "Ошибка при получении данных. Попробуйте позже" + +msgid "Error when activating the loyalty program. Try again later" +msgstr "Ошибка при активации программы лояльности. Попробуйте позже" + +msgid "Enter the correct phone number" +msgstr "Введите корректный номер телефона" + +msgid "Close" +msgstr "Закрыть" + +msgid "Ordinary products: accrual of 1 bonus for each %s %s" +msgstr "Обычные товары: начисление 1 бонуса за каждые %s %s" + +msgid "Promotional products: accrual of 1 bonus for each %s %s" +msgstr "Акционные товары: начисление 1 бонуса за каждые %s %s" + +msgid "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount" +msgstr "Обычные товары: начисление бонусов в размере %s%% от суммы покупки" + +msgid "Promotional products: bonus accrual in the amount of %s%% of the purchase amount" +msgstr "Акционные товары: начисление бонусов в размере %s%% от суммы покупки" + +msgid "Ordinary products: %s%% discount" +msgstr "Обычные товары: %s%% скидка" + +msgid "Promotional products: %s%% discount" +msgstr "Акционные товары: %s%% скидка" + msgid "Uploading services" msgstr "Выгрузка услуг" msgid "Goods with the 'virtual' option enabled will be uploaded to Simla as services" msgstr "Товары с включенной опцией 'виртуальные' будут выгружаться в CRM как услуги" + +msgid "User not found in the system" +msgstr "Пользователь не найден в системе" + +msgid "Error when searching for participation in loyalty programs" +msgstr "Ошибка при поиске участия в программах лояльности" + +msgid "No active participation in the loyalty program was detected" +msgstr "Не обнаружено активного участия в программе лояльности" + +msgid "No bonuses for debiting" +msgstr "Нет бонусов для списания" + +msgid "Loyalty program not found" +msgstr "Программа лояльности не найдена" + +msgid "Loyalty program is not active" +msgstr "Программа лояльности не активна" + +msgid "Loyalty program blocked" +msgstr "Программа лояльности заблокирована" + +msgid "This user is a corporate person" +msgstr "Данный пользователь является корпоратиным лицом" + +msgid "It is possible to write off" +msgstr "Возможно списать" + +msgid "bonuses" +msgstr "бонусов" + +msgid "Use coupon:" +msgstr "Используйте купон:" + +msgid "Points will be awarded upon completion of the order:" +msgstr "По завершению заказа будет начислено баллов:" diff --git a/src/assets/css/retailcrm-loyalty-style.css b/src/assets/css/retailcrm-loyalty-style.css new file mode 100644 index 0000000..933467b --- /dev/null +++ b/src/assets/css/retailcrm-loyalty-style.css @@ -0,0 +1,34 @@ +.popup-fade-loyalty { + display: none; +} +.popup-fade-loyalty:before { + content: ''; + background: #000; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.7; + z-index: 9999; +} +.popup-loyalty { + position: fixed; + top: 20%; + left: 50%; + padding: 20px; + width: 1000px; + margin-left: -500px; + height: 50%; + background: #fff; + border: 1px solid orange; + border-radius: 4px; + z-index: 99999; + opacity: 1; + overflow: auto; +} +.popup-close-loyalty { + position: absolute; + top: 10px; + right: 10px; +} diff --git a/src/assets/js/retailcrm-loyalty-actions.js b/src/assets/js/retailcrm-loyalty-actions.js new file mode 100644 index 0000000..256fadf --- /dev/null +++ b/src/assets/js/retailcrm-loyalty-actions.js @@ -0,0 +1,121 @@ +jQuery(function() { + jQuery('#loyaltyRegisterForm').on("submit", function (event) { + var termsCheck = jQuery('#termsLoyalty'); + var privacyCheck = jQuery('#privacyLoyalty'); + + if (termsCheck.length) { + if (!termsCheck.is(':checked')) { + event.preventDefault(); + return false; + } + } + + if (privacyCheck.length) { + if (!privacyCheck.is(':checked')) { + event.preventDefault(); + return false; + } + } + + let phone = jQuery('#phoneLoyalty'); + + if (!phone.val().match(/(?:\+|\d)[\d\-\(\) ]{7,}\d/)) { + + if (!jQuery('#warningLoyaltyPhone').length) { + phone.parent().append('' + messagePhone + '') + } + + event.preventDefault(); + return false; + } else { + jQuery('#warningLoyaltyPhone').remove(); + } + + jQuery.ajax({ + url: loyaltyUrl.url + '/admin-ajax.php?action=register_customer_loyalty', + method: 'POST', + timeout: 0, + data: {ajax: 1, phone: phone.val(), userId: customerId}, + dataType: 'json' + }) + .done(function (response) { + if (response.hasOwnProperty('error')) { + jQuery('#loyaltyRegisterForm').append('
'+ response.error + '
') + + event.preventDefault(); + return false; + } else { + location.reload(); + } + }) + + event.preventDefault(); + }); + + jQuery('#loyaltyActivateForm').on("submit", function (event) { + var activateCheckbox = jQuery('#loyaltyActiveCheckbox'); + + if (activateCheckbox.length) { + if (!activateCheckbox.is(':checked')) { + event.preventDefault(); + return false; + } + } + + jQuery.ajax({ + url: loyaltyUrl.url + '/admin-ajax.php?action=activate_customer_loyalty', + method: 'POST', + timeout: 0, + data: {ajax: 1, loyaltyId: loyaltyId}, + dataType: 'json' + }) + .done(function (response) { + if (response.hasOwnProperty('error')) { + jQuery('#loyaltyRegisterForm').append(''+ response.error + '
') + + event.preventDefault(); + return false; + } else { + location.reload(); + } + }) + + event.preventDefault(); + }); + + jQuery('.popup-open-loyalty').click(function() { + if (jQuery(this).attr('id') === 'terms-popup') { + jQuery('#popup-loyalty-text').html(termsLoyalty); + } else { + jQuery('#popup-loyalty-text').html(privacyLoyalty); + } + + jQuery('.popup-fade-loyalty').fadeIn(); + return false; + }); + + jQuery('.popup-close-loyalty').click(function() { + jQuery(this).parents('.popup-fade-loyalty').fadeOut(); + return false; + }); + + jQuery(document).keydown(function(e) { + if (e.keyCode === 27) { // Key Escape + e.stopPropagation(); + jQuery('.popup-fade-loyalty').fadeOut(); + } + }); + + jQuery('.popup-fade-loyalty').click(function(e) { + if (jQuery(e.target).closest('.popup-loyalty').length == 0) { + jQuery(this).fadeOut(); + } + }); + + jQuery('#phoneLoyalty').keydown(function (e) { + let key = e.key; + + return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')'|| key == '-' || + key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace'; + }); +}); diff --git a/src/assets/js/retailcrm-loyalty.js b/src/assets/js/retailcrm-loyalty.js new file mode 100644 index 0000000..c043d51 --- /dev/null +++ b/src/assets/js/retailcrm-loyalty.js @@ -0,0 +1,36 @@ +jQuery(function() { + if (jQuery('#woocommerce_integration-retailcrm_loyalty').is(':checked')) { + checkActiveCoupon(); + } + + jQuery('#woocommerce_integration-retailcrm_loyalty').change(function () { + if (this.checked) { + checkActiveCoupon(); + } + }) + + function checkActiveCoupon() + { + jQuery.ajax({ + url: AdminUrl.url + '/admin-ajax.php?action=get_status_coupon', + method: 'POST', + timeout: 0, + data: {ajax: 1}, + dataType: 'json' + }) + .done(function (response) { + if (response.coupon_status !== 'yes') { + var checkElement = jQuery('#woocommerce_integration-retailcrm_loyalty'); + checkElement.parent().css('color', 'red'); + checkElement.css('border-color', 'red'); + checkElement.prop('checked', false); + + if (!jQuery('#coupon_warning').length) { + checkElement.parent().parent().append( + "" + response.translate.coupon_warning + "
" + ); + } + } + }) + } +}); diff --git a/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php b/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php index ba8bbef..9adf83b 100644 --- a/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php +++ b/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php @@ -117,6 +117,7 @@ abstract class WC_Retailcrm_Abstracts_Settings extends WC_Integration $this->form_fields['online_assistant'] = [ 'title' => __('Online assistant', 'retailcrm'), + 'css' => 'width:400px; height:215px; resize: horizontal;', 'type' => 'textarea', 'id' => 'online_assistant', 'placeholder' => __('Insert the Online consultant code here', 'retailcrm') @@ -593,6 +594,41 @@ abstract class WC_Retailcrm_Abstracts_Settings extends WC_Integration 'description' => __('WhatsApp chat will be opened with this contact', 'retailcrm') ]; + /** + * Loyalty Program + */ + + $this->form_fields[] = [ + 'title' => __('Loyalty program', 'retailcrm'), + 'type' => 'heading', + 'description' => '', + 'id' => 'loyalty_options' + ]; + + $this->form_fields['loyalty'] = [ + 'label' => __('Activate program loyalty', 'retailcrm'), + 'title' => __('Loyalty program', 'retailcrm'), + 'class' => 'checkbox', + 'type' => 'checkbox', + 'description' => __('Enable this setting for activate program loyalty on site', 'retailcrm') + ]; + + $this->form_fields['loyalty_terms'] = [ + 'title' => __('Terms of loyalty program', 'retailcrm'), + 'css' => 'width:400px; height:215px; resize: horizontal;', + 'type' => 'textarea', + 'id' => 'loyalty_terms', + 'placeholder' => __('Insert the terms and conditions of the loyalty program', 'retailcrm') + ]; + + $this->form_fields['loyalty_personal'] = [ + 'title' => __('Conditions of personal data processing', 'retailcrm'), + 'css' => 'width:400px; height:215px; resize: horizontal;', + 'type' => 'textarea', + 'id' => 'loyalty_personal', + 'placeholder' => __('Insert the terms and conditions for processing personal data', 'retailcrm') + ]; + /** * Generate icml file */ diff --git a/src/include/api/class-wc-retailcrm-client-v5.php b/src/include/api/class-wc-retailcrm-client-v5.php index 449125f..f877604 100644 --- a/src/include/api/class-wc-retailcrm-client-v5.php +++ b/src/include/api/class-wc-retailcrm-client-v5.php @@ -169,17 +169,7 @@ class WC_Retailcrm_Client_V5 */ public function customersCorporateList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/customers-corporate', @@ -273,17 +263,7 @@ class WC_Retailcrm_Client_V5 */ public function customersCorporateNotesList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/customers-corporate/notes', @@ -421,17 +401,8 @@ class WC_Retailcrm_Client_V5 ) { $this->checkIdParameter($by); - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); + $parameters['by'] = $by; return $this->client->makeRequest( "/customers-corporate/$id/addresses", @@ -537,17 +508,8 @@ class WC_Retailcrm_Client_V5 ) { $this->checkIdParameter($by); - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); + $parameters['by'] = $by; return $this->client->makeRequest( "/customers-corporate/$id/companies", @@ -640,17 +602,8 @@ class WC_Retailcrm_Client_V5 ) { $this->checkIdParameter($by); - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); + $parameters['by'] = $by; return $this->client->makeRequest( "/customers-corporate/$id/contacts", @@ -769,17 +722,7 @@ class WC_Retailcrm_Client_V5 */ public function usersList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/users', @@ -840,17 +783,7 @@ class WC_Retailcrm_Client_V5 */ public function segmentsList(array $filter = [], $limit = null, $page = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/segments', @@ -870,17 +803,7 @@ class WC_Retailcrm_Client_V5 */ public function customFieldsList(array $filter = [], $limit = null, $page = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/custom-fields', @@ -994,17 +917,7 @@ class WC_Retailcrm_Client_V5 */ public function customDictionariesList(array $filter = [], $limit = null, $page = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/custom-fields/dictionaries', @@ -1101,17 +1014,7 @@ class WC_Retailcrm_Client_V5 */ public function ordersList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/orders', @@ -1445,17 +1348,7 @@ class WC_Retailcrm_Client_V5 */ public function customersList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/customers', @@ -1666,16 +1559,8 @@ class WC_Retailcrm_Client_V5 */ public function customersNotesList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); + return $this->client->makeRequest( '/customers/notes', WC_Retailcrm_Request::METHOD_GET, @@ -1748,17 +1633,7 @@ class WC_Retailcrm_Client_V5 */ public function ordersPacksList(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/orders/packs', @@ -1899,17 +1774,7 @@ class WC_Retailcrm_Client_V5 */ public function tasksList(array $filter = [], $limit = null, $page = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/tasks', @@ -2008,17 +1873,7 @@ class WC_Retailcrm_Client_V5 */ public function storeProductsGroups(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/store/product-groups', @@ -2042,17 +1897,7 @@ class WC_Retailcrm_Client_V5 */ public function storeInventories(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/store/inventories', @@ -2180,17 +2025,7 @@ class WC_Retailcrm_Client_V5 */ public function storeProducts(array $filter = [], $page = null, $limit = null) { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } + $parameters = $this->buildParameters($filter, $page, $limit); return $this->client->makeRequest( '/store/products', @@ -3008,6 +2843,183 @@ class WC_Retailcrm_Client_V5 ); } + /** Loyalty program */ + + /** Customer registration in the loyalty program */ + public function createLoyaltyAccount(array $parameters, $site = null) + { + if ([] === $parameters) { + throw new InvalidArgumentException( + 'Parameter `parameters` must contains a data' + ); + } + + return $this->client->makeRequest( + '/loyalty/account/create', + WC_Retailcrm_Request::METHOD_POST, + $this->fillSite($site, ['loyaltyAccount' => json_encode($parameters)]) + ); + } + + /** Receiving information about participation in the loyalty program */ + public function getLoyaltyClientInfo(int $clientIdLoyalty) + { + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty", + WC_Retailcrm_Request::METHOD_GET + ); + } + + /** Activation of participation in the loyalty program */ + public function activateLoyaltyAccount(int $clientIdLoyalty) + { + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/activate", + WC_Retailcrm_Request::METHOD_POST + ); + } + + /** Editing participation in the loyalty program */ + public function editLoyaltyAccount(int $clientIdLoyalty, array $parameters) + { + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/edit", + WC_Retailcrm_Request::METHOD_POST, + ['id' => $clientIdLoyalty, 'loyaltyAccount' => json_encode($parameters)] + ); + } + + /** List of participation in the loyalty program */ + public function getLoyaltyAccountList(array $filter = [], $limit = null, $page = null) + { + $parameters = $this->buildParameters($filter, $limit, $page); + + return $this->client->makeRequest( + '/loyalty/accounts', + WC_Retailcrm_Request::METHOD_GET, + $parameters + ); + } + + /** List of loyalty programs */ + public function getListLoyalty(array $filter = [], $limit = null, $page = null) + { + $parameters = $this->buildParameters($filter, $limit, $page); + + return $this->client->makeRequest( + '/loyalty/loyalties', + WC_Retailcrm_Request::METHOD_GET, + $parameters + ); + } + + /** Receiving information about the loyalty program */ + public function getLoyalty(int $idLoyalty) + { + return $this->client->makeRequest( + "/loyalty/loyalties/$idLoyalty", + WC_Retailcrm_Request::METHOD_GET + ); + } + + /** Charge bonus */ + public function chargeBonusLoyalty(int $clientIdLoyalty, float $amount, string $comment = '') + { + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/bonus/charge", + WC_Retailcrm_Request::METHOD_POST, + ['amount' => $amount, 'comment' => $comment] + ); + } + + public function creditBonusLoyalty(int $clientIdLoyalty, array $parameters) + { + if (!isset($parameters['amount'])) { + throw new InvalidArgumentException( + 'Parameter `amount` does not exist' + ); + } + + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/bonus/credit", + WC_Retailcrm_Request::METHOD_POST, + $parameters + ); + } + + /** History of the client's bonus account */ + public function getClientBonusHistory(int $clientIdLoyalty, array $filter = [], $limit = null, $page = null) + { + $parameters = $this->buildParameters($filter, $limit, $page); + $parameters['id'] = $clientIdLoyalty; + + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/bonus/operations", + WC_Retailcrm_Request::METHOD_GET, + $parameters + ); + } + + public function getDetailClientBonus( + int $clientIdLoyalty, + string $status, + array $filter = [], + $limit = null, + $page = null + ) { + $parameters = $this->buildParameters($filter, $limit, $page); + $parameters['id'] = $clientIdLoyalty; + $parameters['status'] = $status; + + return $this->client->makeRequest( + "/loyalty/account/$clientIdLoyalty/bonus/$status/details", + WC_Retailcrm_Request::METHOD_GET, + $parameters + ); + } + + /** Bonus account history for all participants */ + public function getBonusHistory(string $cursor, array $filter = [], $limit = null) + { + $parameters = $this->buildParameters($filter, $limit); + $parameters['cursor'] = $cursor; + + return $this->client->makeRequest( + "/loyalty/bonus/operations", + WC_Retailcrm_Request::METHOD_GET, + $parameters + ); + } + + /** Maximum discount calculation */ + public function calculateDiscountLoyalty(string $site, array $order, $bonuses = 0) + { + return $this->client->makeRequest( + "/loyalty/calculate", + WC_Retailcrm_Request::METHOD_POST, + ['site' => $site, 'order' => json_encode($order), 'bonuses' => $bonuses] + ); + } + + /** Application of bonuses under the loyalty program */ + public function applyBonusToOrder(string $site, array $order, float $bonuses) + { + return $this->client->makeRequest( + "/orders/loyalty/apply", + WC_Retailcrm_Request::METHOD_POST, + ['site' => $site, 'order' => json_encode($order), 'bonuses' => $bonuses] + ); + } + + public function cancelBonusOrder(array $order) + { + return $this->client->makeRequest( + "/orders/loyalty/cancel-bonus-operations", + WC_Retailcrm_Request::METHOD_POST, + ['order' => json_encode($order)] + ); + } + /** * Update CRM basic statistic * @@ -3121,4 +3133,23 @@ class WC_Retailcrm_Client_V5 return $params; } + + protected function buildParameters(array $filter = [], $limit = null, $page = null): array + { + $parameters = []; + + if (count($filter)) { + $parameters['filter'] = $filter; + } + + if (isset($page)) { + $parameters['page'] = (int) $page; + } + + if (isset($limit)) { + $parameters['limit'] = (int) $limit; + } + + return $parameters; + } } diff --git a/src/include/class-wc-retailcrm-base.php b/src/include/class-wc-retailcrm-base.php index 707d1c2..d20decf 100644 --- a/src/include/class-wc-retailcrm-base.php +++ b/src/include/class-wc-retailcrm-base.php @@ -33,6 +33,12 @@ if (!class_exists('WC_Retailcrm_Base')) { /** @var WC_Retailcrm_Cart */ protected $cart; + /** @var WC_Retailcrm_Loyalty */ + protected $loyalty; + + /** @var array */ + protected $updatedOrderId = []; + /** * Init and hook in the integration. * @@ -86,6 +92,7 @@ if (!class_exists('WC_Retailcrm_Base')) { add_action('wp_ajax_generate_icml', [$this, 'generate_icml']); add_action('wp_ajax_upload_selected_orders', [$this, 'upload_selected_orders']); add_action('wp_ajax_clear_cron_tasks', [$this, 'clear_cron_tasks']); + add_action('wp_ajax_get_status_coupon', [$this, 'get_status_coupon']); add_action('admin_print_footer_scripts', [$this, 'ajax_generate_icml'], 99); add_action('woocommerce_update_customer', [$this, 'update_customer'], 10, 1); add_action('user_register', [$this, 'create_customer'], 10, 2); @@ -99,6 +106,42 @@ if (!class_exists('WC_Retailcrm_Base')) { add_action('admin_enqueue_scripts', [$this, 'include_files_for_admin'], 101); add_action('woocommerce_new_order', [$this, 'create_order'], 11, 1); + + if (isLoyaltyActivate($this->settings)) { + add_action('wp_ajax_register_customer_loyalty', [$this, 'register_customer_loyalty']); + add_action('wp_ajax_activate_customer_loyalty', [$this, 'activate_customer_loyalty']); + add_action('init', [$this, 'add_loyalty_endpoint'], 11, 1); + add_action('woocommerce_account_menu_items', [$this, 'add_loyalty_item'], 11, 1); + add_action('woocommerce_account_loyalty_endpoint', [$this, 'show_loyalty'], 11, 1); + + // Add coupon hooks for loyalty program + add_action('woocommerce_cart_coupon', [$this, 'coupon_info'], 11, 1); + //Remove coupons when cart changes + add_action('woocommerce_add_to_cart', [$this, 'refresh_loyalty_coupon'], 11, 1); + add_action('woocommerce_after_cart_item_quantity_update', [$this, 'refresh_loyalty_coupon'], 11, 1); + add_action('woocommerce_cart_item_removed', [$this, 'refresh_loyalty_coupon'], 11, 1); + add_action('woocommerce_before_cart_empted', [$this, 'clear_loyalty_coupon'], 11, 1); + add_action('woocommerce_removed_coupon', [$this, 'remove_coupon'], 11, 1); + add_action('woocommerce_applied_coupon', [$this, 'apply_coupon'], 11, 1); + add_action('woocommerce_review_order_before_payment', [$this, 'reviewCreditBonus'], 11, 1); + add_action('wp_trash_post', [$this, 'trash_order_action'], 10, 1); + + if ( + !$this->get_option('deactivate_update_order') + || $this->get_option('deactivate_update_order') == static::NO + ) { + add_action('woocommerce_update_order', [$this, 'take_update_order'], 11, 1); + add_action('shutdown', [$this, 'update_order_loyalty'], -1); + add_action('woocommerce_saved_order_items', [$this, 'update_order_items'], 10, 1); + } + } elseif ( + !$this->get_option('deactivate_update_order') + || $this->get_option('deactivate_update_order') == static::NO + ) { + add_action('woocommerce_update_order', [$this, 'update_order'], 10, 1); + } + + // Subscribed hooks add_action('register_form', [$this, 'subscribe_register_form'], 99); add_action('woocommerce_register_form', [$this, 'subscribe_woocommerce_register_form'], 99); @@ -111,13 +154,6 @@ if (!class_exists('WC_Retailcrm_Base')) { ); } - if ( - !$this->get_option('deactivate_update_order') - || $this->get_option('deactivate_update_order') == static::NO - ) { - add_action('woocommerce_update_order', [$this, 'update_order'], 11, 1); - } - if ($this->get_option('abandoned_carts_enabled') === static::YES) { $this->cart = new WC_Retailcrm_Cart($this->apiClient, $this->settings); @@ -127,6 +163,8 @@ if (!class_exists('WC_Retailcrm_Base')) { add_action('woocommerce_cart_emptied', [$this, 'clear_cart']); } + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiClient, $this->settings); + // Deactivate hook add_action('retailcrm_deactivate', [$this, 'deactivate']); @@ -523,6 +561,15 @@ if (!class_exists('WC_Retailcrm_Base')) { } } + public function update_order($orderId) + { + if (WC_Retailcrm_Plugin::history_running() === true) { + return; + } + + $this->orders->updateOrder($orderId); + } + /** * Edit order in retailCRM * @@ -532,13 +579,38 @@ if (!class_exists('WC_Retailcrm_Base')) { * * @throws \Exception */ - public function update_order($order_id) + public function take_update_order($order_id) { - if (WC_Retailcrm_Plugin::history_running() === true) { + if ( + WC_Retailcrm_Plugin::history_running() === true + || did_action('woocommerce_checkout_order_processed') + || did_action('woocommerce_new_order') + ) { return; } - $this->orders->updateOrder($order_id); + $this->updatedOrderId[$order_id] = $order_id; + } + + public function update_order_loyalty() + { + if ($this->updatedOrderId !== []) { + foreach ($this->updatedOrderId as $orderId) { + $this->orders->updateOrder($orderId); + } + } + } + + public function update_order_items($orderId) + { + $this->orders->updateOrder($orderId); + } + + public function trash_order_action($id) + { + if ('shop_order' == get_post_type($id)) { + $this->orders->updateOrder($id, true); + } } /** @@ -607,6 +679,126 @@ if (!class_exists('WC_Retailcrm_Base')) { $this->include_js_scripts_for_admin(); } + public function get_status_coupon() + { + echo json_encode( + [ + 'coupon_status' => get_option('woocommerce_enable_coupons'), + 'translate' => [ + 'coupon_warning' => __( + "To activate the loyalty program it is necessary to activate the 'enable use of coupons option'", + 'retailcrm' + ) + ] + ]); + + wp_die(); + } + + public function register_customer_loyalty() + { + $phone = filter_input(INPUT_POST, 'phone'); + $userId = filter_input(INPUT_POST, 'userId'); + $site = $this->apiClient->getSingleSiteForKey(); + $isSuccessful = false; + + if (!empty($site) && $userId && $phone) { + $isSuccessful = $this->loyalty->registerCustomer($userId, $phone, $site); + } + + if (!$isSuccessful) { + writeBaseLogs('Errors when registering a loyalty program. Passed parameters: ' . + json_encode(['site' => $site, 'userId' => $userId, 'phone' => $phone]) + ); + echo json_encode(['error' => __('Error while registering in the loyalty program. Try again later', 'retailcrm')]); + } else { + echo json_encode(['isSuccessful' => true]); + } + + wp_die(); + } + + public function activate_customer_loyalty() + { + $loyaltyId = filter_input(INPUT_POST, 'loyaltyId'); + $isSuccessful = false; + + if ($loyaltyId) { + $isSuccessful = $this->loyalty->activateLoyaltyCustomer($loyaltyId); + } + + if (!$isSuccessful) { + writeBaseLogs('Errors when activate loyalty program. Passed parameters: ' . json_encode(['loyaltyId' => $loyaltyId])); + echo json_encode(['error' => __('Error when activating the loyalty program. Try again later', 'retailcrm')]); + } else { + echo json_encode(['isSuccessful' => true]); + } + + wp_die(); + } + + public function coupon_info() + { + try { + $result = $this->loyalty->createLoyaltyCoupon(); + + if ($result) { + echo $result; + } + } catch (Throwable $exception) { + writeBaseLogs($exception->getMessage()); + } + } + + public function refresh_loyalty_coupon() + { + try { + $this->loyalty->createLoyaltyCoupon(true); + } catch (Throwable $exception) { + writeBaseLogs($exception->getMessage()); + } + } + + public function clear_loyalty_coupon() + { + try { + $this->loyalty->clearLoyaltyCoupon(); + } catch (Throwable $exception) { + writeBaseLogs($exception->getMessage()); + } + } + + public function remove_coupon($couponCode) + { + try { + if (!$this->loyalty->deleteLoyaltyCoupon($couponCode)) { + $this->loyalty->createLoyaltyCoupon(true); + } + } catch (Throwable $exception) { + writeBaseLogs($exception->getMessage()); + } + } + + public function apply_coupon($couponCode) + { + try { + if (!$this->loyalty->isLoyaltyCoupon($couponCode)) { + $this->loyalty->createLoyaltyCoupon(true); + } + } catch (Throwable $exception) { + writeBaseLogs($exception->getMessage()); + } + } + + public function reviewCreditBonus() + { + $resultHtml = $this->loyalty->getCreditBonuses(); + + if ($resultHtml) { + echo $resultHtml; + } + } + /** * In this method we include CSS file * @@ -645,9 +837,10 @@ if (!class_exists('WC_Retailcrm_Base')) { 'retailcrm-cron-info', 'retailcrm-meta-fields', 'retailcrm-module-settings', + 'retailcrm-loyalty' ]; - $wpAdminUrl = ['url' => get_admin_url()]; + $wpAdminUrl = ['url' => get_admin_url()]; $jsScriptsPath = plugins_url() . '/woo-retailcrm/assets/js/'; foreach ($jsScripts as $scriptName) { @@ -803,6 +996,53 @@ if (!class_exists('WC_Retailcrm_Base')) { wp_die(); } + public function add_loyalty_item($items) + { + $items['loyalty'] = __('Loyalty program', 'retailcrm'); + + return $items; + } + + public function add_loyalty_endpoint() + { + add_rewrite_endpoint('loyalty', EP_PAGES); + } + + public function show_loyalty() + { + $userId = get_current_user_id(); + + if (!isset($userId)) { + return; + } + + $jsScript = 'retailcrm-loyalty-actions'; + $loyaltyUrl = ['url' => get_admin_url()]; + $jsScriptsPath = plugins_url() . '/woo-retailcrm/assets/js/'; + $cssPath = plugins_url() . '/woo-retailcrm/assets/css/'; + $messagePhone = __('Enter the correct phone number', 'retailcrm'); + + wp_register_script($jsScript, $jsScriptsPath . $jsScript . '.js', false, '0.1'); + wp_enqueue_script($jsScript, $jsScriptsPath . $jsScript . '.js', '', '', true); + wp_localize_script($jsScript, 'loyaltyUrl', $loyaltyUrl); + wp_localize_script($jsScript, 'customerId', $userId); + wp_localize_script($jsScript, 'messagePhone', $messagePhone); + wp_localize_script($jsScript, 'termsLoyalty', $this->settings['loyalty_terms']); + wp_localize_script($jsScript, 'privacyLoyalty', $this->settings['loyalty_personal']); + wp_register_style('retailcrm-loyalty-style', $cssPath . 'retailcrm-loyalty-style.css', false, '0.1'); + wp_enqueue_style('retailcrm-loyalty-style'); + + $result = $this->loyalty->getForm($userId); + + if ([] === $result) { + echo ''. __('Error while retrieving data. Try again later', 'retailcrm') . '
'; + } else { + wp_localize_script($jsScript, 'loyaltyId', $result['loyaltyId'] ?? null); + echo $result['form']; + } + } + + /** * Get custom fields with CRM * diff --git a/src/include/class-wc-retailcrm-loyalty.php b/src/include/class-wc-retailcrm-loyalty.php new file mode 100644 index 0000000..0ec3010 --- /dev/null +++ b/src/include/class-wc-retailcrm-loyalty.php @@ -0,0 +1,539 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + class WC_Retailcrm_Loyalty + { + /** @var WC_Retailcrm_Client_V5 */ + protected $apiClient; + + protected $dateFormat; + + protected $settings; + + /** @var WC_Retailcrm_Loyalty_Form */ + protected $loyaltyForm; + + protected $validator; + + public function __construct($apiClient, $settings) + { + $this->apiClient = $apiClient; + $this->settings = $settings; + $this->dateFormat = 'Y-m-d H:i:sP'; + $this->loyaltyForm = new WC_Retailcrm_Loyalty_Form(); + $this->validator = new WC_Retailcrm_Loyalty_Validator( + $this->apiClient, + isCorporateUserActivate($this->settings) + ); + } + + public function getForm(int $userId) + { + $result = []; + + try { + $response = $this->getLoyaltyAccounts($userId); + } catch (Throwable $exception) { + writeBaseLogs('Exception get loyalty accounts: ' . $exception->getMessage()); + + return $result; + } + + $loyaltyAccount = $response['loyaltyAccounts'][0] ?? null; + + if ($loyaltyAccount && (int) $loyaltyAccount['customer']['externalId'] === $userId) { + if ($loyaltyAccount['active'] === true) { + $result['form'] = $this->loyaltyForm->getInfoLoyalty($loyaltyAccount); + } else { + $result['form'] = $this->loyaltyForm->getActivationForm(); + + $result['loyaltyId'] = $loyaltyAccount['id']; + } + } else { + $result['form'] = $this->loyaltyForm->getRegistrationForm(); + } + + return $result; + } + + public function registerCustomer(int $userId, string $phone, string $site): bool + { + $parameters = [ + 'phoneNumber' => $phone, + 'customer' => [ + 'externalId' => $userId + ] + ]; + + try { + $response = $this->apiClient->createLoyaltyAccount($parameters, $site); + + if (!$response->isSuccessful()) { + writeBaseLogs('Error while registering in the loyalty program: ' . $response->getRawResponse()); + } + + return $response->isSuccessful(); + } catch (Throwable $exception) { + writeBaseLogs('Exception while registering in the loyalty program: ' . $exception->getMessage()); + + return false; + } + } + + public function activateLoyaltyCustomer(int $loyaltyId) + { + try { + $response = $this->apiClient->activateLoyaltyAccount($loyaltyId); + + if (!$response->isSuccessful()) { + writeBaseLogs('Error while registering in the loyalty program: ' . $response->getRawResponse()); + } + + return $response->isSuccessful(); + } catch (Throwable $exception) { + writeBaseLogs('Exception while activate loyalty account: ' . $exception->getMessage()); + + return false; + } + } + + private function getDiscountLoyalty($cartItems, $site, $customerId) + { + $response = $this->calculateDiscountLoyalty($cartItems, $site, $customerId); + + if ($response === 0) { + return 0; + } + + $discount = 0; + + //Checking if the loyalty discount is a percent discount + foreach ($response['order']['items'] as $item) { + if (!isset($item['discounts'])) { + continue; + } + + foreach ($item['discounts'] as $discountItem) { + if ($discountItem['type'] === 'loyalty_level') { + $discount += $discountItem['amount']; + } + } + } + + //If the discount has already been given, do not work with points deduction + if ($discount === 0) { + foreach ($response['calculations'] as $calculate) { + if ($calculate['privilegeType'] !== 'loyalty_level') { + continue; + } + + $discount = $calculate['maxChargeBonuses']; + } + } + + return $discount; + } + + private function getLoyaltyAccounts(int $userId) + { + $response = $this->apiClient->customersGet($userId); + + if (!isset($response['customer']['id'])) { + return []; + } + + $filter['customerId'] = $response['customer']['id']; + + $response = $this->apiClient->getLoyaltyAccountList($filter); + + if (!$response->isSuccessful() || !$response->offsetExists('loyaltyAccounts')) { + return null; + } + + return $response; + } + + public function createLoyaltyCoupon($refreshCoupon = false) + { + global $woocommerce; + + $site = $this->apiClient->getSingleSiteForKey(); + $cartItems = $woocommerce->cart->get_cart(); + $customerId = $woocommerce->customer ? $woocommerce->customer->get_id() : null; + + $resultString = ''; + + if (!$customerId || !$cartItems) { + return null; + } + + $couponsLp = []; + // Check exists used loyalty coupons + foreach ($woocommerce->cart->get_coupons() as $code => $coupon) { + if ($this->isLoyaltyCoupon($code)) { + $couponsLp[] = $code; + } + } + + // if you need to refresh coupon that does not exist + if (count($couponsLp) === 0 && $refreshCoupon) { + return null; + } + + //If one loyalty coupon is used, not generate a new one + if (count($couponsLp) === 1 && !$refreshCoupon) { + return null; + } + + // if more than 1 loyalty coupon is used, delete all coupons + if (count($couponsLp) > 1 || $refreshCoupon) { + foreach ($couponsLp as $code) { + $woocommerce->cart->remove_coupon($code); + + $coupon = new WC_Coupon($code); + + $coupon->delete(true); + } + + $woocommerce->cart->calculate_totals(); + } + + if (!$this->validator->checkAccount($customerId)) { + return null; + } + + $lpDiscountSum = $this->getDiscountLoyalty($woocommerce->cart->get_cart(), $site, $customerId); + + if ($lpDiscountSum === 0) { + return null; + } + + //Check the existence of loyalty coupons and delete them + $coupons = $this->getCouponLoyalty($woocommerce->customer->get_email()); + + foreach ($coupons as $item) { + $coupon = new WC_Coupon($item['code']); + + $coupon->delete(true); + } + + //Generate new coupon + $coupon = new WC_Coupon(); + + $coupon->set_usage_limit(0); + $coupon->set_amount($lpDiscountSum); + $coupon->set_email_restrictions($woocommerce->customer->get_email()); + $coupon->set_code('loyalty' . mt_rand()); + $coupon->save(); + + if ($refreshCoupon) { + $woocommerce->cart->apply_coupon($coupon->get_code()); + + return $resultString; + } + + //If a percentage discount, automatically apply a loyalty coupon + if ($this->validator->loyaltyAccount['level']['type'] === 'discount') { + $woocommerce->cart->apply_coupon($coupon->get_code()); + + return $resultString; + } + + $resultString .= '%s
+%s%s.
+%s%s.
+ + + +$line
"; + } + + return $result; + } + } + + +endif; diff --git a/src/include/functions.php b/src/include/functions.php index 4f82f5b..2e099ea 100644 --- a/src/include/functions.php +++ b/src/include/functions.php @@ -202,3 +202,18 @@ function useHpos() return class_exists(Automattic\WooCommerce\Utilities\OrderUtil::class) && Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); } + +function isLoyaltyActivate($settings) +{ + return isset($settings['loyalty']) && $settings['loyalty'] === WC_Retailcrm_Base::YES; +} + +function isCorporateUserActivate($settings) +{ + return isset($settings['corporate_enabled']) && $settings['corporate_enabled'] === WC_Retailcrm_Base::YES; +} + +function isCorporateOrder($wcCustomer, $wcOrder) +{ + return !empty($wcCustomer->get_billing_company()) || !empty($wcOrder->get_billing_company()); +} diff --git a/src/include/order/class-wc-retailcrm-order-item.php b/src/include/order/class-wc-retailcrm-order-item.php index b6186b5..38b0a59 100644 --- a/src/include/order/class-wc-retailcrm-order-item.php +++ b/src/include/order/class-wc-retailcrm-order-item.php @@ -28,6 +28,9 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data */ protected $settings = []; + /** @var bool */ + public $cancelLoyalty = false; + /** * WC_Retailcrm_Order_Item constructor. * @@ -43,13 +46,13 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data * * @return self */ - public function build($item) + public function build($item, $crmItem = null) { $decimalPlaces = wc_get_price_decimals(); // Calculate price and discount $price = $this->calculatePrice($item, $decimalPlaces); - $discountPrice = $this->calculateDiscount($item, $price, $decimalPlaces); + $discountPrice = $this->calculateDiscount($item, $price, $decimalPlaces, $crmItem); $data['productName'] = $item['name']; $data['initialPrice'] = $price; @@ -112,12 +115,44 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data * @param WC_Order_Item_Product $item * @param $price * @param int $decimalPlaces Price rounding from WC settings - * + * @param array|null $crmItem Current trade position in CRM * @return float|int */ - private function calculateDiscount(WC_Order_Item_Product $item, $price, int $decimalPlaces) - { - $productPrice = $item->get_total() ? $item->get_total() / $item->get_quantity() : 0; + private function calculateDiscount( + WC_Order_Item_Product $item, + $price, + int $decimalPlaces, + $crmItem = null + ) { + if ($crmItem) { + $loyaltyDiscount = 0; + + foreach ($crmItem['discounts'] as $discount) { + if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) { + $loyaltyDiscount += $discount['amount']; + + break; + } + } + + /** + * The loyalty program discount is calculated within the CRM system. It must be deleted during transfer to avoid duplication. + */ + $productPrice = ($item->get_total() / $item->get_quantity()) + ($loyaltyDiscount / $crmItem['quantity']); + + if ($this->cancelLoyalty) { + if ($item->get_total() + $loyaltyDiscount <= $item->get_subtotal()) { + $item->set_total($item->get_total() + $loyaltyDiscount); + $item->calculate_taxes(); + $item->save(); + } + + $productPrice = $item->get_total() / $item->get_quantity(); + } + } else { + $productPrice = $item->get_total() ? $item->get_total() / $item->get_quantity() : 0; + } + $productTax = $item->get_total_tax() ? $item->get_total_tax() / $item->get_quantity() : 0; $itemPrice = $productPrice + $productTax; @@ -127,7 +162,7 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data /** * Reset item data. */ - public function resetData() + public function resetData($cancelLoyalty) { $this->data = [ 'offer' => [], @@ -135,5 +170,63 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data 'initialPrice' => 0.00, 'quantity' => 0.00 ]; + + $this->cancelLoyalty = $cancelLoyalty; + } + + /** + * Checking whether the loyalty program discount needs to be canceled. (Changing the sales items in the order) + * + * @param array $wcItems + * @param array $crmItems + * + * @return bool + */ + public function isCancelLoyalty($wcItems, $crmItems): bool + { + /** If the number of sales items does not match */ + if (count($wcItems) !== count($crmItems)) { + $this->cancelLoyalty = true; + + return true; + } + + foreach ($wcItems as $id => $item) { + $loyaltyDiscount = 0; + + /** If a trading position has been added/deleted */ + if (!isset($crmItems[$id])) { + $this->cancelLoyalty = true; + + return true; + } + + /** If the quantity of goods in a trade item does not match */ + if ($item->get_quantity() !== $crmItems[$id]['quantity']) { + $this->cancelLoyalty = true; + + return true; + } + + foreach ($crmItems[$id]['discounts'] as $discount) { + if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) { + $loyaltyDiscount = $discount['amount']; + + break; + } + } + + /** + *If the sum of the trade item including discounts and loyalty program discount exceeds the cost without discounts. + * (Occurs when recalculating an order, deleting/adding coupons) + */ + if (($item->get_total() + $loyaltyDiscount) > $item->get_subtotal()) { + $this->cancelLoyalty = true; + + return true; + } + } + + return false; } } diff --git a/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-constraint.php b/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-constraint.php new file mode 100644 index 0000000..838bae9 --- /dev/null +++ b/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-constraint.php @@ -0,0 +1,33 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + class WC_Retailcrm_Loyalty_Constraint + { + public $notFoundCrmUser = 'User not found in the system'; + + public $errorFoundLoyalty = 'Error when searching for participation in loyalty programs'; + + public $notFoundActiveParticipation = 'No active participation in the loyalty program was detected'; + + public $notExistBonuses = 'No bonuses for debiting'; + + public $notFoundLoyalty = 'Loyalty program not found'; + + public $loyaltyInactive = 'Loyalty program is not active'; + + public $loyaltyBlocked = 'Loyalty program blocked'; + + public $isCorporateUser = 'This user is a corporate person'; + } +endif; diff --git a/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-validator.php b/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-validator.php new file mode 100644 index 0000000..2e08d09 --- /dev/null +++ b/src/include/validators/loyalty-validator/class-wc-retailcrm-loyalty-validator.php @@ -0,0 +1,120 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + class WC_Retailcrm_Loyalty_Validator extends WC_Retailcrm_Loyalty_Constraint + { + /** @var WC_Retailcrm_Client_V5 */ + protected $apiClient; + + protected $isActiveCorp; + + public $customer; + + public $loyaltyAccount; + + public function __construct($apiClient, $isActiveCorp) + { + $this->apiClient = $apiClient; + $this->isActiveCorp = $isActiveCorp; + } + + public function checkAccount(int $userId): bool + { + try { + $this->checkUser($userId); + $this->checkLoyaltyAccount($this->customer['id']); + $this->checkActiveLoyalty($this->loyaltyAccount['loyalty']['id']); + + return true; + } catch (ValidatorException $exception) { + WC_Admin_Settings::add_error((esc_html__($exception->getMessage(), 'retailcrm')) . "userId: $userId"); + } catch (Throwable $exception) { + WC_Admin_Settings::add_error($exception->getMessage()); + } + + return false; + } + + /** + * @throws ValidatorException + */ + private function checkUser($userId) + { + $responseUser = $this->apiClient->customersGet($userId); + + if (!isset($responseUser['customer']['id'])) { + throw new ValidatorException($this->notFoundCrmUser, 400); + } + + $customer = new WC_Customer($userId); + + if ($this->isActiveCorp && !empty($customer->get_billing_company())) { + throw new ValidatorException($this->isCorporateUser, 400); + } + + $this->customer = $responseUser['customer']; + } + + /** + * @throws ValidatorException + */ + private function checkLoyaltyAccount($customerId) + { + $filter['customerId'] = $customerId; + $responseLoyalty = $this->apiClient->getLoyaltyAccountList($filter); + + if (!$responseLoyalty->isSuccessful() || !$responseLoyalty->offsetExists('loyaltyAccounts')) { + throw new ValidatorException($this->errorFoundLoyalty, 400); + } + + $actualAccount = null; + + foreach ($responseLoyalty['loyaltyAccounts'] as $loyaltyAccount) { + if ($loyaltyAccount['active'] === true) { + $actualAccount = $loyaltyAccount; + } + } + + if (!isset($actualAccount)) { + throw new ValidatorException($this->notFoundActiveParticipation, 400); + } + + if ($actualAccount['amount'] === 0 && $actualAccount['level']['type'] !== 'discount') { + throw new ValidatorException($this->notExistBonuses, 400); + } + + $this->loyaltyAccount = $actualAccount; + } + + /** + * @throws ValidatorException + */ + private function checkActiveLoyalty($idLoyalty) + { + $responseLoyalty = $this->apiClient->getLoyalty($idLoyalty); + + if (!$responseLoyalty->isSuccessful() || !$responseLoyalty->offsetExists('loyalty')) { + throw new ValidatorException($this->notFoundLoyalty, 400); + } + + if ($responseLoyalty['loyalty']['active'] !== true) { + throw new ValidatorException($this->loyaltyInactive, 400); + } + + if ($responseLoyalty['loyalty']['blocked'] === true) { + throw new ValidatorException($this->loyaltyBlocked, 400); + } + } + } +endif; diff --git a/src/languages/retailcrm-es_ES.l10n.php b/src/languages/retailcrm-es_ES.l10n.php index e3fb869..5d58e8e 100644 --- a/src/languages/retailcrm-es_ES.l10n.php +++ b/src/languages/retailcrm-es_ES.l10n.php @@ -199,7 +199,55 @@ return [ "La moneda del sitio web es distinto a la tienda del CRM. Para el funcionamiento correcto de la integración, las monedas del CMS y CRM deben coincid", "Uploading services" => "Descarga de servicios", "Goods with the 'virtual' option enabled will be uploaded to Simla as services" => - "Los bienes con la opción 'virtual' activada se cargarán en Simla como servicios" + "Los bienes con la opción 'virtual' activada se cargarán en Simla como servicios", + "Loyalty program" => "Programa de fidelización", + "Activate program loyalty" => "Activar programa de fidelización", + "Enable this setting for activate program loyalty on site" => "Activa esta configuración para activar el programa de fidelización en la página web", + "Terms of loyalty program" => "Condiciones del programa de fidelización", + "Insert the terms and conditions of the loyalty program" => "Inserte las condiciones del programa de fidelización", + "Conditions of personal data processing" => "Condiciones de procesamiento de datos personales", + "Insert the terms and conditions for processing personal data" => "Inserte las condiciones para el procesamiento de datos personales", + "To activate the loyalty program it is necessary to activate the 'enable use of coupons option'" => + "Para activar el programa de fidelización es necesario activar la opción 'habilitar el uso de cupones'", + "Bonus account" => "Cuenta de bonificación", + "Participation ID: " => "ID de participación: ", + "Current level: " => "Nivel actual: ", + "Bonuses on the account: " => "Bonificaciones en la cuenta: ", + "Bonus card number: " => "Número de tarjeta de bonificación: ", + "Date of registration: " => "Fecha de registro: ", + "Current level rules" => "Reglas del nivel actual", + "Required amount of purchases to move to the next level: " => "Cantidad de compras necesarias para pasar al siguiente nivel: ", + "Activate participation in the loyalty program" => "Activar participación en el programa de fidelización", + "Send" => "Enviar", + "To register in the loyalty program, fill in the form:" => "Para registrarse en el programa de fidelización, rellene el formulario:", + " I agree with " => " Acepto ", + "loyalty program terms" => "términos del programa de fidelización", + "terms of personal data processing" => "términos de procesamiento de datos personales", + "Phone" => "Teléfono", + "Error while registering in the loyalty program. Try again later" => "Error al registrarse en el programa de fidelización. Inténtalo de nuevo más tarde", + "The card is not linked" => "La tarjeta no está vinculada", + "Error while retrieving data. Try again later" => "Error al recuperar los datos. Inténtalo de nuevo más tarde", + "Error when activating the loyalty program. Try again later" => "Error al activar el programa de fidelización. Inténtalo de nuevo más tarde", + "Enter the correct phone number" => "Introduce el número de teléfono correcto", + "Close" => "Cerrar", + "Ordinary products: accrual of 1 bonus for each %s %s" => "Productos ordinarios: acumulación de 1 bonificación por cada %s %s", + "Promotional products: accrual of 1 bonus for each %s %s" => "Productos promocionales: acumulación de 1 bonificación por cada %s %s", + "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount" => "Productos ordinarios: acumulación de bonificación en la cantidad de %s%% del monto de la compra", + "Promotional products: bonus accrual in the amount of %s%% of the purchase amount" => "Productos promocionales: acumulación de bonificación en la cantidad de %s%% del monto de la compra", + "Ordinary products: %s%% discount" => "Productos ordinarios: descuento del %s%%", + "Promotional products: %s%% discount" => "Productos promocionales: descuento del %s%%", + "User not found in the system" => "Usuario no encontrado en el sistema", + "Error when searching for participation in loyalty programs" => "Error al buscar la participación en programas de fidelización", + "No active participation in the loyalty program was detected" => "No se detectó participación activa en el programa de fidelización", + "No bonuses for debiting" => "No hay bonificaciones para debitar", + "Loyalty program not found" => "Programa de fidelización no encontrado", + "Loyalty program is not active" => "El programa de fidelización no está activo", + "Loyalty program blocked" => "Programa de fidelización bloqueado", + "This user is a corporate person" => "Este usuario es una persona jurídica", + "It is possible to write off" => "Es posible debitar", + "bonuses" => "bonificaciones", + "Use coupon:" => "Utiliza el cupón:", + "Points will be awarded upon completion of the order:" => "Los puntos se concederán al finalizar el pedido:", ], "language" => "es", "x-generator" => "GlotPress/2.4.0-alpha", diff --git a/src/languages/retailcrm-es_ES.mo b/src/languages/retailcrm-es_ES.mo index 3b176fa7e3896dce16b8ba6d7e623916cdaae174..051c9cfcec855e0095b33ddae0e6a9503a1de7f9 100644 GIT binary patch literal 18403 zcmbuGdzc+%ea8pnW<(J10$y+;ge0=N*$n~0LV&Q@kcCaMWH$s+F0<#%?oKl2%*o80 z-K=0ypdy8;MNky2jkYRaG=i;&^#c1m_Mc*deY`zS?NcvLA8oa@)mnYD+RyiW-GqxEU$vmViIaS7D zHvY=n)^-7Dhsf!+tpf4W%kHkGC{|Le^RONbNEr^oGMCCV8Uf zBrj7_I;@Oa@?%luVkd>Bv`7itOFQFj4l%fTX5+;)VY4H)zGXGQl)SvH32PTKLJsOq z^|mfNoO9b4i?BYsa3G-smf0|tG`N`ROowG*$Gjw`cqgJ*O>jUIQ7SSlAY0cxnPgQ@ z79OV-mUvr&$taExCU(M_C?V)XqE0$SA>>M)2kCTR>z9yL%1wx8%VaWJ+6~oGM5oq} zC}6_v8l<^LFa+IfaGKO%5KKY)$U`k@w~jDo(r7bK5)Z1%Vueu_NLpA_x0|AS7C|WE z8Ax43<}IW7I*b+Uid`PT5r`Ekw{7^Gaj%pe6tLSmm`$@1yo+<#vTsJd>eikaqg4)3 zG7>}4O7S{>#9Yej9HGwEdQJN;&Hxq5Q8BqtB^wV*US ZO>&g+KhA Rr~D~ zxmoE!+#-3~>^TwXBD1^l7!pt_;&ZsqKA-I_JdFE@jmFiRAyXP^pj;g4Y@LqLCYw?y z!|);zYUPEc7cy)}*y*0j_nmoBY^U9_sp2j1sN<#IeNk|v2@AEZLiUgN@({}J7X>|I z!?{*HFAZeSxp3t?p2?wxyGgagIMm<2oQG9ZCC}1J_J*u(;L3B@KTYkfNC39i^bpg+ z$F|>YDQfxH_>Y#RB3WWpq4lj&pA`x=2^);nkar2JfJrBmv{ZpFV)w({p(cDQtp@a- zOq89L+b(|)A@{ox`=P|ATsoYw%x B{F)& zSa&i~1QK#?M2v7r$|I|9eF;vX64WLmtUPxRaZ?5~5&FA?VM>Hi zQ8A 36_Z3#S$J_28*i-Avv+B zX4uVtvzqptL3 KgltClRCZi=d=Xb*jmweDyT+Lb=Iv2NiYqp8mWK{JL9a|! zG~g&A9YVRCA+eziRHTS$YM4fP`gDG1NqB4|+-$(v6=Q3#g(lzJ9t|UKCdmGsIveXy zL=xn(sLzRmuu|(c;kmH^EUy*ZjM*UGOt5rdVjdSm8+Ntgec10}FOoefW@49%+Nce3 zb@nriU8El=0@8wITxaW~P!(f9d=$7c>Ed&AziSSjVlAX3d6q}jfC5%%OQsTSQNe1Q zdYGWv8`-2TkjVb3)G& C_(h7b)F`H<^q`I^od&z0Ls4GUh$^ZY@q^XRP4}*@-Q+VR>gOQ+ z5nlhjs6W)kCd-hPZej6_ th |@KHAEt91Y9!|iv#^guAod7@7j4&|!%rBdBx~0|=s;Z|NwLpGv|)x1VvaJ#yPF*r z5eq9Lo}n~p=OCmY5I4$OzMe9mG)y^bMa4)|oXK?C8m$>_mn+iF`$dV?@2Jq4PFrn> zcGw(n+j44d3B{cPr6fg$rv08ZE1i;Rq6x2MV}?wE&njU1`BMr(3}j<16BQY~cj+Nx zbFejRj`*fn);M8?t i=nki%({ry#vCprFHS)A(BSf7`axrd@5O|*lw$Ew;hS^R_J wwxHR` zoN`XBd?Z&AjMh;k3vn?{pLmG)=@6r{6u&o~w1Q$Rx|CGsJ6k@EP>B&0j-}Z| 5eZlPRZUR7VwPUM)+|?3_HAviD}OZg}iB zcV`zKc1D-PuMDO$Aw~X?yeRr>Djo=dEk=h+7NJ$hlhs!q2KG!Q9CYdwlZQ=FI!Gxe zyTiW*H3?=dqZG2jj+kn+6#^t#{=nr}`Uu4ORKzo0)KufFiAU}9KetqlHrWZ;|M^by zps2)HQ_h)~X5+s2k0yCQ&50A0EU7{&Rc=+(XZ_e6P2mkiO~Q3=$mt?HA>pUehYrU4 zM-^fu!(z|GnK)q;9gT^TJ9C=j4fmSj^tPnN?*+;-boP5zrZ+Y)H54^GA}uW#?r5?l jO=b700)A|kaAaIG2bxDvf`os^;Eo)HONTLsm6rY&o1WJ; delta 3520 zcmYk;32c*P9LMpebsR&+4j9|mfKJAMaSR5?HLeX9*9MHki2` {rh>|qtEewo~PM&Ew1e= zqvz5R_87`;B8NDdV9e`SoX7`deph2U;R5WBH)0ozB7dftk8ANEOvYABK^wF1l=J)? zUdQ!MI1ZDNjLD`MQ${6|8#UM$H=sJ$hjZ~Ts=;3|9XoU*FdKD07iq{8VMnY$UNws` z4Qrh1wb+a6Cd|MCm`MNT1u7cZ2~>mMqegxi^@U`nm5K$ZffXZ ibR3^*$`;`Y1*VsQgLg7VO{Mn5noG)!-TA&m{MV&$u7z{UXfA zWvJ&(I1C>~P2@BxqrYMiX0UD=@NCrgs!#)srjvg?xSt!Na6fj$50NpM&yhCFFR1t2 zJ>wmvpgNw6YG@(qeIL%kb*K!Sz!p4<+T6=YLuXu%YPTta{L{AC%MG$ pp~$R#YqtC)v{>|mvO5$b&(K7 dO5VP|%tGMS1@-ejVl7otvCB`RaldMab7 zY(eenSCBvR4j)?6HjFhu{%W``#06-fGWP?%h*wYpdY(}#^(Rr8dKY#7OH_v1P{;QV zB-2rIm5N4^NS?HLa!@mvg&I&L>I+`vx5)%B;xc9xs-th1j#7I8)nSe&{=EWJhvQId zJqvYQm!mSi5xeO8@1>##k7EhGf%?Tu c>sFZ(=n$US< zOy*DLelPN`3FV?PIvmsK-;`0|9kUGeg(xZm2e3ariCW{csE)ou4d@E0BTv8hrW=X6 zF2g~%6!pC(RA%>}_SQ+9jaf7j)f)S#+<_L3zz+1PU%D}PJ(l1+Y(x$4O{~N=)WD1L z mkg(|}{~80rV?N36i^1>|2NtLCnbUjyp=K8;G*39P~k z*a^!9$ItUT 4!#7{gZwQ&2C= zMBTp`wWdK##7(H*gPq6$Fs*nkp2W_07DwPGs9m4P`f31u9E pru7~R8-nT}Pcf$m4PfjN$D{1lV$3*;xkv>}r>9T`;@EW!>r z5oteaN~x%0Ke8-lJ8DUeqt@&UYS+7p;tlr3Vy?%d2DlErxEB}V71a0VjWp(C^kYXX zW_`P1DJs*I7(4$ +A+>B{lH>38#W2h9rh3eo#?162lrTGV4sCAxCj3%`C zQiw9*K0-_J0HLDECFuMwCN#qqf{h&8@#;WjR~*xX8iYEGIQIm78Xr+kOd`m(xu3X& zP}|#yu7ozF$~a;vQBPzNdM9?|$Z~A mU+j@g7IgqB8y?EY68sVPG$+9MniL$+i8 z{Y*ub+labYjh|lJ>eSaF*^KpXCU8+pgb8h;8HCn!1+kh~PiWV-mkm_3iB$55mBeIS zIAvyg1$8WMcdjdO6>$eqLnNvH5S1-N1+j# J9`269T`r5ik zZ7676D9*6<_WjM? T_=W+|(_P2S|^703|twjTCvsQb9-hjWpG_+c6nY!CTLBEN3 z17W+{ptUaR(So 1K#@A4hB8`p#Ax%-(6P4m_O3zhQc22$~EDLKkNzcYQ1-rKWux)rMWW8 zUpo>B`FQU0d+NM_kSPtV3ek%7-jGad^Mowx?1WzS#o`i|J$BL%mwj|{mAmyyiL28z X=15<=r_HpZ(|m5LZAQ?FmW}=wwb-5H diff --git a/src/languages/retailcrm-ru_RU.l10n.php b/src/languages/retailcrm-ru_RU.l10n.php index 857338c..5afac2b 100644 --- a/src/languages/retailcrm-ru_RU.l10n.php +++ b/src/languages/retailcrm-ru_RU.l10n.php @@ -198,7 +198,55 @@ return [ "Валюта сайта отличается от валюты магазина в CRM. Для корректной работы интеграции, валюты в CRM и CMS должны совпадать", "Uploading services" => "Выгрузка услуг", "Goods with the 'virtual' option enabled will be uploaded to Simla as services" => - "Товары с включенной опцией 'виртуальные' будут выгружаться в CRM как услуги" + "Товары с включенной опцией 'виртуальные' будут выгружаться в CRM как услуги", + "Loyalty program" => "Программа лояльности", + "Activate program loyalty" => "Активировать программу лояльности", + "Enable this setting for activate program loyalty on site" => "Активируйте эту настройку для активации программы лояльности на сайте", + "Terms of loyalty program" => "Условия программы лояльности", + "Insert the terms and conditions of the loyalty program" => "Вставьте условия участия в программе лояльности", + "Conditions of personal data processing" => "Условия обработки персональных данных", + "Insert the terms and conditions for processing personal data" => "Вставьте условия обработки персональных данных", + "To activate the loyalty program it is necessary to activate the 'enable use of coupons option'" => + "Для активации программы лояльности необходимо активировать опцию 'включить использование купонов'", + "Bonus account" => "Бонусный счет", + "Participation ID: " => "ID участия: ", + "Current level: " => "Текущий уровень: ", + "Bonuses on the account: " => "Бонусов на счету: ", + "Bonus card number: " => "Номер бонусной карты: ", + "Date of registration: " => "Дата регистрации: ", + "Current level rules" => "Правила текущего уровня", + "Required amount of purchases to move to the next level: " => "Необходимая сумма покупок для перехода на следующий уровень: ", + "Activate participation in the loyalty program" => "Активировать участие в программе лояльности", + "Send" => "Отправить", + "To register in the loyalty program, fill in the form:" => "Для регистрации в программе лояльности заполните форму:", + " I agree with " => " Я согласен с ", + "loyalty program terms" => "условиями программы лояльности", + "terms of personal data processing" => "условиями обработки персональных данных", + "Phone" => "Телефон", + "Error while registering in the loyalty program. Try again later" => "Ошибка при регистрации в программе лояльности. Попробуйте позже", + "The card is not linked" => "Карта не привязана", + "Error while retrieving data. Try again later" => "Ошибка при получении данных. Попробуйте позже", + "Error when activating the loyalty program. Try again later" => "Ошибка при активации программы лояльности. Попробуйте позже", + "Enter the correct phone number" => "Введите корректный номер телефона", + "Close" => "Закрыть", + "Ordinary products: accrual of 1 bonus for each %s %s" => "Обычные товары: начисление 1 бонуса за каждые %s %s", + "Promotional products: accrual of 1 bonus for each %s %s" => "Акционные товары: начисление 1 бонуса за каждые %s %s", + "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount" => "Обычные товары: начисление бонуса в размере %s%% от суммы покупки", + "Promotional products: bonus accrual in the amount of %s%% of the purchase amount" => "Акционные товары: начисление бонуса в размере %s%% от суммы покупки", + "Ordinary products: %s%% discount" => "Обычные товары: скидка %s%%", + "Promotional products: %s%% discount" => "Акционные товары: скидка %s%%", + "User not found in the system" => "Пользователь не найден в системе", + "Error when searching for participation in loyalty programs" => "Ошибка при поиске участия в программах лояльности", + "No active participation in the loyalty program was detected" => "Не обнаружено активного участия в программе лояльности", + "No bonuses for debiting" => "Нет бонусов для списания", + "Loyalty program not found" => "Программа лояльности не найдена", + "Loyalty program is not active" => "Программа лояльности не активна", + "Loyalty program blocked" => "Программа лояльности заблокирована", + "This user is a corporate person" => "Этот пользователь является корпоративным лицом", + "It is possible to write off" => "Возможно списать", + "bonuses" => "бонусов", + "Use coupon:" => "Используйте купон:", + "Points will be awarded upon completion of the order:" => "По завершению заказа будет начислено баллов:", ], "language" => "ru", "x-generator" => "GlotPress/2.4.0-alpha", diff --git a/src/languages/retailcrm-ru_RU.mo b/src/languages/retailcrm-ru_RU.mo index a0b0cf3044c0f91b24f60ae87114514694ecd825..5cb7b6941debe4458e6e9b8dbd8bccc9de072cf6 100644 GIT binary patch delta 9086 zcmb`J3v^WVoyY$P2q+MF$oq8@f{|AsK?EXC5d{ns5Ued&oJ=k-HJJ%B6CgeqOrTIf z5XV<_3$B7%ttH72On?NfdOWAv?H$~+t!-D=-P4}6$9B)|wp)*$-Ls$Xy*D9*iLcX} z{O0q&|NDRZ{=e6~f2qxStRXA${-B 2x(a79h#`MlIW cbi4F1XQ|Fe6app)5G^Pn%( zhQ$ygn3Yf)tb>d-LCC+^#*a?;0Mzp*;Yj!b90p&9m&4yc9q4yZiuAe4v&aaj@iX91 z_BShOa5!T_nRGjp$!lRQJnTPDK`HnqTns;iveX2QHydt*Q{WRY1mA>{;k2Qi>()a( z55gPZLogv9y-Q;({3Bcm`*94NWIaD6Fy`|oZ~^xpK`k^9d+TJkL0Mumlx3>nO>mF@ z{2kbj`+xSI{{gau`E)q`mx%_Ug(l8~E8r412`1s4unjJP Mx zV^C3i4C*8=LXCe9>V!Gtj2RE};4F9(Tm^T++3*+84MxjbH!*_7G9FBU^3gVU2-d-# za4-?b;mmN@2hN02cs?Y)%&kzt8-j}Zd!WYcfl}z8fBy{B0bYXBU<=Gw{(nS6rq00+ znlKg;AEp=*kLDKmI4px4(tHZ1!Vw%3W0{*_0kom!JqTry=iqJdH&8KC%x-c~4a5lM zFx<)hrhI;K i97QrsBxDQ2-;{0 zlnYCtT(A*p-6!B^_BY39D2iW$3O)zQw;w|-Jc{E5VKH0{AA>Uezr)k;|DYCZxyEzh zn^2}c57qxKus8e|%F=&^m%*MBvA=wF1r2F<6;xUjz&>y-)JAteDHwyASPOf?eGu=N z2Vp$Rm=jPg$e-*fG7qlhemRr^N1+b%Jd{P-CS!js@C!PaYyK0;0;AAE{j=aL@LO;V zJPsGaPv8tVhd_{JDxnrk`}|i}#QlFm1?L3JskEI1IlNg7EqHe!_K(qciVot$TuErs zG&7(UEQ9i84CcVc;LY$joDcs9RkEi{_X^M~XmMW#(b3dF#XvpO0UfviehTNok_2jM z;T^C9?u7&3PoWfg7fPYOLRsYMVsD|PP$!B)ZEyhI3V#eODD&I^a^VB89Nq>01J=Uz zq{w=hc%Q}*8YQH_V)!1E0%NZ8vZ4@Ha$gQn+MI_vK@Y0SeQ+^U>3t1G;Ky(S+(4*r zgDp@oFnx}f9edy~?w^JnAYqzm$j9fQ(r+wgAfU}$sDb5BKD!sLhR2~S^H;bRj-vi* zgJ %9-PelPsC4(7w9%Ky7*+)l^q@LO;+$CIzN!KLtND4%}> z`8P!iiFUXZj)T94+Mw4W&*X&=9n4mrFT-5!KZ2WJU;IK`nkWn Qt**bVPY=m3kRF17!I0|)wdiXFr@88>)=hxitg@a((GB1d$q3-M9DEK-Y4?lpi zSdZo2yJGxu>@SU{(xCy1pft99#-UtL2W5eG;6(U|&pZN$cr>LjPvc-3J_m8X*-`45 zya^KLX7ma#EmuQXdglu4AEt4Fj{R`#N@MPaFG87U$~X9UfXm@8;c>Vb-nYt&`rpB9 z?gtQ=s+5DF6qyL6cqvp6KMG}$m!TAS6{ ; *owL%ZJyC!qpa*GotWc|kW^gOQ;~A@XhI z|4te*dHc1+Z|Kzb`RyodMs7gJGV>fFYw2o2E<=<-OA%d@k*C}i?+l+mhO$T*5=UM| zw7;8L)E@JMI=K3KaEr(g+KK~R(~y-2S>(QcmGj?2MkBg@fK(&O2)W2cUPgjQ3!-ZQ z(&o0jpTsyg0vYCa7Q@+oTaYhb^zR>mNB#C*xWjM%J$%}4&+th+x|f1fm(uMBQi_x# z{*^E_w0iq@TYb)hm55TY^QC~(RpG(>6z)SFLJlB{5M8r8n1%4~5M>yd>Sj_cJd7ya zua^BErJ=~}i!4XlubXMy;osCjGS#hqJzzd^6*2%}2{%)ALtWqXVCvz$h|;z5mCen1 z lp&8vkB+M&~~0cbtG#$Z4cOebzE7usLem*7i_*i YlB{ zO5Jni;H=?+ZqAab98jJ$TnYvwEEtSiRa+uq+doF~X9GitXfzVFwr{b+oWV5+N@cQF zp|v(zgQWwQvjSD3X32%AF*^_qZqdw5tagd{yO=)KF*Q_ir|loej%s>lUv ?^Y@{P~k{Nv7~x7LMD* znHzys$zDTR63gONC}vef@Ov3*$0OGEXvp>HCR5raIJ?|iWfhU&U3Pibaau4OiF>ZI zFL|m*n+V}@vmy}sPS}ds@i=CRnUxWL%B8-_a29KOAZC@@apDIRm|LcVAoyjsa=VNu zCy?J6@>1d((eh9@K%5ZZ<<-G>ti+lSn=rvD55>Gx$}oV`iB^*s*s;i4* 72YB&qw@th%T=EW43xUKOaRR0_sxsz|<7 z%8}&mp+$*EC>)PbSSl*4GUjim1lZ+Pbro43j8s-t*sgp1eIh)JmY7xE(J@Rc7!6fv zg!(EYYH`l)Op{?u0{gX}v3v7mn8>`$7Wz^KT4Qgm4n;ZWG|ysg{ql_%Q`}ZYwz=`E zSP9!Z{L)1+dTVG-R(7~Nb$;0BtU@=_P`gX^o3$!ODyDw$R)oTYcIxBdMcIih^buN7 zJqxI4RYk}lYP4HE&00Th)s-nwLvD(NZ53CAA-Z;yMvb2vu(m|)P4fyCR0THM^R@@4 zwx>kF`~o{1SgXWjf1Q|s#m^LP*M$Xhrv>I~f3N&X!;G($Db^-Mi~o=WsVqs28g*S( zzQ10HS*PQ5WM=!wSWPT$r%Fdp&F<}IS8~VL?+)o=JIgI!$q&bl%Jm8vdUp3_NqshU zTh?&PIbo&mciNnkbH=Hs)#S9&VWq09x~$oC>AmVq*E-EkljSs8&bjn14d%DrIm;it zXPmb5A?Hl`fYVCL9h_X5JF=kb*mPaDW0U`t`}?xPEZmx|Lx _?d;l!^}KU3d5`6s 6H1$b>`2d8XrOvyR&S4C|*4py=WJNvXs6g;~}k9IsZ(?Q)lC zmVLEP`g`otOiHy*SdpDPQZVRJbMMb+?$oOVOR|&CPcKg9O&pZGeA45IBk6st)gViH z{^iUvk(}PxzSAY1PiJ1f&~lD)N-s {_3adIMf;UH278ix+rG+MMrjTtJIN6 z3olDN?B-Ox)0jTs+L8tT79ne#FId=jL$RCD-S{DM`lo(7Et-{h*me0?eov_~kcmWr zd`v_u^N!+|&ZZ~&GJ5pOjk5@^dZJjlOxmV*$*+X3ioloMOBI6?T?qcKK3658h04(6 z^q`q6Eh LvH@X_#H^Dydi0Ok~C)V-KrfP@d ?+m8$OLA6a&_*o+4P@NG!oUH`w@(mr_uAE8{XI}`S!e#q_Xcyg)%AW zeGEH~xGq9{eE@a&a%q42GQ7jCGFgT
<-ZLzZml&t)WZ|>vx)S50&Xio6o%@9Q zeqj(FqOXvO{)dMbV<=X_N7aaY-!u5aFVn5kgsHB6Xi5!Xpq1)B?^uui7rr(*Rr18b j-6a>ld>O3|L^SDijBdh%=_8W2ESglBX^F4-*=hbCWgP#s delta 3531 zcmYk;dr;M79>?+LcN8xf#zo{J%0b1vtfGjPB`6Re5sI2pmKiGG2|1`7N%C@()bfIs zKd)t06Fb^$Q`9eKTW5^fM$2xy2b|HRw*8~6#@RKdcG}Fbn% z>8O#EqJB_~ z8p$S9$MzvVHODX$PkHx0Ky~DwScE?yBQ-N=^bgpK0elH3@O~3Zt4i?<)B^=rh(S~b zUdOrkAtqxQ!%+uwP$Mit-eKlr308aePa(5l{^H$#7xnu~sDbukSU-$UG^PSGaXdES z!}tsq;0;v6c|*t!Zbyy$DC+r>I0n1D``2+2=dsL_1~e1(!T{#rUR1|BlgPh*_%RnW zqVK#LF++{X P2h)#@vfrP`m3Qs>7e6GW0*p)c)_o<{ig{EYt|+A^%K0hjMJgsn~-W zX)Nhd18Y57aSG=)YR#O-CcKQJu$+T3)` 87F&N<1q^+bJFWSc>Y{ zDx8G}Fb}&?8HnS1d iSG+sLvqS1<#8Y`KLv62pEf%~T5U2=2yD zupVnzkq=-Op1_G?-7nfls29f3`ZUbM2CTx#cpf+7_oxAdIB2mwk6OHEP#x+XPyUtS zt6X56nA<0~yI?Wu!7a$U%wg2x>O_q&o(1s)PD9P{S^PI9u&18H?{Nc$NdtX1KcL$8 zvqu+UKF+~|+2o&2n$Nro{po%w=as0GAH@*<1DSqP!9)kJ5!I0% v2+!yZD|# z`f5(125=FzwtmE7OkqK(Ll1?i#8KIVdO;f=z}LL An-TFm03Kwr#W zB sfGW_Bv-d?^mV)oAg4vz*vK zXdN6RR5XGigc{sLln`oEWxh*v%aP^ix_Q*QrZ4FjqCatnunCoBm*}UNnKU(op9m|p zDa2Z0GjTVuig=9JMv$fGpU_lPnd|b~v9DK~hieGUt;*Ku(fwkos)1_MER*QY0!>fY zJV<2`u~1hi4-@-|9mFHVQbK9gqEgzEi9%vHv7V?PRJ7Xn5nG6ALZuH;N^B=Iby}z@ zmBa#Kth$v>g{g>sI37opdSViBAF-PlMQCxdRLn!fuceksi+93$i~dd59J7|AA1Lou zl-V3xe|O<&>f@A_UyD|;%6J#^6yEFAh4%G2f}Lqt{HBVyimh1PQ)l(Xx|cnpY3u zRHDf{&p>T?mFW8$sl*V=h@phaNSEmMeGv5`Vl~l5#1gxR0HIPArH7TicZhgd?4 zCT=EFMz} P39LZLvguF1|F{-@Z=>XwF@V8|b8SyR(wzdT|} z!n($l>sN&QD{GootP89O1sa1+fB#_1Ny|*NoE0OpeNJ6guH~E?GuCH+H#U&kP#vtU zt65jn*r0~YnpKU#8WXCnYjR#4*K9d?6Iv~M;lwZP m{hxv?r@e5uWfv5u+pCL9(oaM>BX4zWi*!WJM&62? z_II^L-j2Kx>FC-T>2OYzp0}Llvdflpvb?>I^HpW8&+eK$!a2IIy^md9)oKs8ukaVU Cv#h=V diff --git a/src/readme.txt b/src/readme.txt index 3fe3638..e4df86d 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -5,7 +5,7 @@ Tags: Интеграция, Simla.com, simla Requires PHP: 7.0 Requires at least: 5.3 Tested up to: 6.5 -Stable tag: 4.7.9 +Stable tag: 4.8.0 License: GPLv1 or later License URI: http://www.gnu.org/licenses/gpl-1.0.html @@ -82,6 +82,9 @@ Asegúrate de tener una clave API específica para cada tienda. Las siguientes i == Changelog == += 4.8.0 = +* Added loyalty program + = 4.7.9 = * Fixed undefined array key number in order history diff --git a/src/retailcrm.php b/src/retailcrm.php index cfbc301..0d5ec36 100644 --- a/src/retailcrm.php +++ b/src/retailcrm.php @@ -5,7 +5,7 @@ * Description: Integration plugin for WooCommerce & Simla.com * Author: RetailDriver LLC * Author URI: http://retailcrm.pro/ - * Version: 4.7.9 + * Version: 4.8.0 * Tested up to: 6.5 * Requires Plugins: woocommerce * WC requires at least: 5.4 @@ -121,6 +121,7 @@ if (!class_exists( 'WC_Integration_Retailcrm')) : require_once(self::checkCustomFile('include/icml/class-wc-retailcrm-icml-writer.php')); require_once(self::checkCustomFile('include/class-wc-retailcrm-orders.php')); require_once(self::checkCustomFile('include/class-wc-retailcrm-cart.php')); + require_once(self::checkCustomFile('include/class-wc-retailcrm-loyalty.php')); require_once(self::checkCustomFile('include/class-wc-retailcrm-customers.php')); require_once(self::checkCustomFile('include/class-wc-retailcrm-inventories.php')); require_once(self::checkCustomFile('include/class-wc-retailcrm-history.php')); @@ -132,6 +133,9 @@ if (!class_exists( 'WC_Integration_Retailcrm')) : require_once(self::checkCustomFile('include/validators/url-validator/class-wc-retailcrm-url-constraint.php')); require_once(self::checkCustomFile('include/validators/url-validator/class-wc-retailcrm-url-validator.php')); require_once(self::checkCustomFile('include/validators/class-wc-retailcrm-validator-exception.php')); + require_once(self::checkCustomFile('include/components/class-wc-retailcrm-loyalty-form.php')); + require_once(self::checkCustomFile('include/validators/loyalty-validator/class-wc-retailcrm-loyalty-constraint.php')); + require_once(self::checkCustomFile('include/validators/loyalty-validator/class-wc-retailcrm-loyalty-validator.php')); } /** diff --git a/src/uninstall.php b/src/uninstall.php index 9188feb..72a6398 100644 --- a/src/uninstall.php +++ b/src/uninstall.php @@ -16,7 +16,7 @@ * * @link https://wordpress.org/plugins/woo-retailcrm/ * - * @version 4.7.9 + * @version 4.8.0 * * @package RetailCRM */ diff --git a/tests/datasets/data-loyalty-retailcrm.php b/tests/datasets/data-loyalty-retailcrm.php new file mode 100644 index 0000000..6793410 --- /dev/null +++ b/tests/datasets/data-loyalty-retailcrm.php @@ -0,0 +1,345 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +class DataLoyaltyRetailCrm +{ + public static function getDataLoyalty() + { + return [ + [ + 'isSuccessful' => true, + 'body' => json_encode(['loyaltyAccounts' => []]), + 'expected' => 'id="loyaltyRegisterForm"' + ], + [ + 'isSuccessful' => true, + 'body' => json_encode( + [ + 'loyaltyAccounts' => [ + 0 => [ + 'active' => false, + 'customer' => [ + 'externalId' => 1 + ], + 'id' => 1 + ] + ] + ] + ), + 'expected' => 'id="loyaltyActivateForm"' + ], + [ + 'isSuccessful' => true, + 'body' => json_encode( + [ + 'loyaltyAccounts' => [ + 0 => [ + 'id' => 1, + 'level' => [ + 'name' => 'Test level', + 'privilegeSize' => 5, + 'privilegeSizePromo' => 3, + 'type' => 'bonus_converting' + ], + 'amount' => 1000, + 'cardNumber' => '12345', + 'activatedAt' => '2024-04-10 15:00:00', + 'nextLevelSum' => 15000, + 'loyalty' => [ + 'currency' => 'USD' + ], + 'customer' => [ + 'externalId' => 1 + ], + 'active' => true + ] + ] + ] + ), + 'expected' => 'Ordinary products: accrual of 1 bonus for each' + ], + ]; + } + + public static function dataCheckUser() + { + return [ + [ + 'responseApiMethod' => [], + 'wcUserType' => 'individual', + 'throwMessage' => 'User not found in the system', + 'isCorpActive' => false + ], + [ + 'responseApiMethod' => ['customer' => ['id' => 1]], + 'wcUserType' => 'corp', + 'throwMessage' => 'This user is a corporate person', + 'isCorpActive' => true + ], + [ + 'responseApiMethod' => ['customer' => ['id' => 1]], + 'wcUserType' => 'corp', + 'throwMessage' => null, + 'isCorpActive' => false + ], + [ + 'responseApiMethod' => ['customer' => ['id' => 1]], + 'wcUserType' => 'individual', + 'throwMessage' => null, + 'isCorpActive' => true + ], + ]; + } + + public static function dataLoyaltyAccount() + { + return [ + [ + 'responseMock' => ['success' => true], + 'throwMessage' => 'Error when searching for participation in loyalty programs' + ], + [ + 'responseMock' => ['success' => true, 'loyaltyAccounts' => []], + 'throwMessage' => 'No active participation in the loyalty program was detected' + ], + [ + 'responseMock' => ['success' => true, 'loyaltyAccounts' => [['active' => true, 'amount' => 0, 'level' => ['type' => 'bonus_converting']]]], + 'throwMessage' => 'No bonuses for debiting' + ], + [ + 'responseMock' => ['success' => true, 'loyaltyAccounts' => [['active' => true, 'amount' => 0, 'level' => ['type' => 'discount']]]], + 'throwMessage' => null + ], + [ + 'responseMock' => ['success' => true, 'loyaltyAccounts' => [['active' => true, 'amount' => 100, 'level' => ['type' => 'bonus_converting']]]], + 'throwMessage' => null + ], + ]; + } + + public static function dataCheckActiveLoyalty() + { + return [ + [ + 'responseMock' => ['success' => true], + 'throwMessage' => 'Loyalty program not found' + ], + [ + 'responseMock' => ['success' => true, 'loyalty' => ['active' => false]], + 'throwMessage' => 'Loyalty program is not active' + ], + [ + 'responseMock' => ['success' => true, 'loyalty' => ['active' => true, 'blocked' => true]], + 'throwMessage' => 'Loyalty program blocked' + ], + [ + 'responseMock' => ['success' => true, 'loyalty' => ['active' => true, 'blocked' => false]], + 'throwMessage' => null + ] + ]; + } + + public static function createProducts() + { + $product1 = new WC_Product(); + $product1->set_name('Test product 1'); + $product1->set_sku('test1' . mt_rand()); + $product1->set_regular_price('200'); + $product1->set_sale_price('200'); + $product1->save(); + + $product2 = new WC_Product(); + $product2->set_name('Test product 2'); + $product2->set_sku('test2' . mt_rand()); + $product2->set_regular_price('100'); + $product2->set_sale_price('50'); + $product2->save(); + + return [$product1, $product2]; + } + + public static function getDataCalculation() + { + return [ + [ + 'response' => [ + 'calculations' => [], + 'order' => [ + 'items' => [ + [ + 'discounts' => [ + [ + 'type' => 'loyalty_level', + 'amount' => 20 + ], + ] + ], + [ + 'discounts' => [ + [ + 'type' => 'loyalty_level', + 'amount' => 20 + ], + ] + ] + ] + ] + ], + 'expected' => 40 + ], + [ + 'response' => [ + 'calculations' => [ + [ + 'privilegeType' => 'test' + ], + [ + 'privilegeType' => 'loyalty_level', + 'maxChargeBonuses' => 50 + ] + ], + 'order' => [ + 'items' => [['discounts' => null], ['discounts' => null]], + ] + ], + 'expected' => 50 + ] + ]; + } + + public static function dataCheckLoyaltyCoupon() + { + return [ + [ + 'code' => 'loyalty49844894548', + 'expected' => true + ], + [ + 'code' => '56556446548484', + 'expected' => false + ], + [ + 'code' => 'dfhdfh54655pl', + 'expected' => false + ], + [ + 'code' => '654844pl18498', + 'expected' => false + ] + ]; + } + + public static function dataGetEmailsForPersonalCoupon() + { + return [ + [ + 'email' => 'test1@gmail.com', + 'code' => 'loyalty' . mt_rand() + ], + [ + 'email' => 'test2@gmail.com', + 'code' => 'loyalty' . mt_rand() + ], + [ + 'email' => 'test3@gmail.com', + 'expectedCode' => false + ] + ]; + } + + public static function dataValidUser() + { + $users = self::createUsers(); + + return [ + [ + 'customer' => $users[0], + 'corporate_enabled' => 'yes', + 'expected' => true, + 'orderCorporate' => false + ], + [ + 'customer' => $users[1], + 'corporate_enabled' => 'yes', + 'expected' => false, + 'orderCorporate' => false + ], + [ + 'customer' => $users[1], + 'corporate_enabled' => 'no', + 'expected' => true, + 'orderCorporate' => false + ], + [ + 'customer' => null, + 'corporate_enabled' => 'yes', + 'expected' => false, + 'orderCorporate' => false + + ], + [ + 'customer' => $users[0], + 'corporate_enabled' => 'yes', + 'expected' => false, + 'orderCorporate' => true + ] + ]; + } + + public static function createUsers() + { + $customer = new WC_Customer(); + + $customer->set_first_name('Tester 1'); + $customer->set_last_name('Tester 1'); + $customer->set_email(uniqid(md5(date('Y-m-d H:i:s'))) . '@mail.com'); + $customer->set_password('password'); + $customer->set_billing_phone('89000000000'); + $customer->set_date_created(date('Y-m-d H:i:s')); + $customer->save(); + + $customer1 = new WC_Customer(); + + $customer1->set_first_name('Tester 1'); + $customer1->set_last_name('Tester 1'); + $customer1->set_email(uniqid(md5(date('Y-m-d H:i:s'))) . '@mail.com'); + $customer1->set_password('password'); + $customer1->set_billing_phone('89000000000'); + $customer1->set_date_created(date('Y-m-d H:i:s')); + $customer1->set_billing_company('OOO TEST'); + $customer1->save(); + + return [$customer, $customer1]; + } + + public static function createCoupons($email1 = 'test1@gmail.com', $email2 = 'test2@gmail.com') + { + $coupon = new WC_Coupon(); + + $coupon->set_usage_limit(0); + $coupon->set_amount(100); + $coupon->set_email_restrictions($email1); + $coupon->set_code('loyalty' . mt_rand()); + $coupon->save(); + + $coupon1 = new WC_Coupon(); + + $coupon1->set_usage_limit(0); + $coupon1->set_amount(100); + $coupon1->set_email_restrictions($email2); + $coupon1->set_code('loyalty' . mt_rand()); + $coupon1->save(); + + return [$coupon, $coupon1]; + } +} diff --git a/tests/helpers/class-wc-retailcrm-test-case-helper.php b/tests/helpers/class-wc-retailcrm-test-case-helper.php index 2c09d1c..3ce02f1 100644 --- a/tests/helpers/class-wc-retailcrm-test-case-helper.php +++ b/tests/helpers/class-wc-retailcrm-test-case-helper.php @@ -80,7 +80,10 @@ class WC_Retailcrm_Test_Case_Helper extends WC_Unit_Test_Case 'product_description' => 'full', 'stores_for_uploading' => ['woocommerce', 'main'], 'woo_coupon_apply_field' => 'testField', - 'icml_unload_services' => 'yes' + 'icml_unload_services' => 'yes', + 'loyalty' => 'yes', + 'loyalty_terms' => 'Test terms', + 'loyalty_personal' => 'Test privacy' ]; update_option(WC_Retailcrm_Base::$option_key, $options); diff --git a/tests/loyalty/test-wc-retailcrm-client-v5.php b/tests/loyalty/test-wc-retailcrm-client-v5.php new file mode 100644 index 0000000..0db27a9 --- /dev/null +++ b/tests/loyalty/test-wc-retailcrm-client-v5.php @@ -0,0 +1,131 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +class WC_Retailcrm_Loyalty_Client_Test extends WC_Retailcrm_Test_Case_Helper +{ + protected $responseMock; + protected $apiMock; + + /** @var \WC_Retailcrm_Client_V5 */ + protected $clientMock; + + public function setUp() + { + $this->responseMock = $this->getMockBuilder('\WC_Retailcrm_Response_Helper') + ->disableOriginalConstructor() + ->setMethods(['isSuccessful']) + ->getMock() + ; + + $this->responseMock->setResponse(['success' => true]); + $this->setMockResponse($this->responseMock, 'isSuccessful', true); + + $this->apiMock = $this->getMockBuilder('\WC_Retailcrm_Request') + ->disableOriginalConstructor() + ->setMethods(['makeRequest']) + ->getMock() + ; + + $this->setMockResponse($this->apiMock, 'makeRequest', $this->responseMock); + + $this->clientMock = new \WC_Retailcrm_Client_V5('https://test@retailcrm.ru', 'test', 'test'); + + $reflection = new ReflectionClass($this->clientMock); + $reflection_property = $reflection->getProperty('client'); + $reflection_property->setAccessible(true); + + $reflection_property->setValue($this->clientMock, $this->apiMock); + } + + /** + * @dataProvider requestLoyaltyData + */ + public function testLoyaltyRequest($method, $parameters) + { + /** @var WC_Retailcrm_Response $test */ + $test = call_user_func([$this->clientMock, $method], ...$parameters); + + $this->assertTrue($test->isSuccessful()); + } + + public function requestLoyaltyData() + { + return [ + [ + 'method' => 'createLoyaltyAccount', + 'parameters' => [['test'], 'testSite'] + ], + [ + 'method' => 'getLoyaltyClientInfo', + 'parameters' => [1] + ], + [ + 'method' => 'activateLoyaltyAccount', + 'parameters' => [1] + ], + [ + 'method' => 'editLoyaltyAccount', + 'parameters' => [1, ['test']] + ], + [ + 'method' => 'getLoyaltyAccountList', + 'parameters' => [['filter'], 20, 1] + ], + [ + 'method' => 'getListLoyalty', + 'parameters' => [['filter'], 20, 1] + ], + [ + 'method' => 'getLoyalty', + 'parameters' => [1] + ], + [ + 'method' => 'chargeBonusLoyalty', + 'parameters' => [1, 100, 'test'] + ], + [ + 'method' => 'creditBonusLoyalty', + 'parameters' => [1, ['amount' => 100]] + ], + [ + 'method' => 'getClientBonusHistory', + 'parameters' => [1, ['filter'], 20, 1] + ], + [ + 'method' => 'getDetailClientBonus', + 'parameters' => [1, 'status', ['filter'], 20, 1] + ], + [ + 'method' => 'getBonusHistory', + 'parameters' => ['cursor', ['filter'], 20] + ], + [ + 'method' => 'calculateDiscountLoyalty', + 'parameters' => ['site', ['order']] + ], + [ + 'method' => 'applyBonusToOrder', + 'parameters' => ['site', ['order'], 100] + ], + [ + 'method' => 'cancelBonusOrder', + 'parameters' => [['order']] + ], + ]; + } +} diff --git a/tests/test-wc-retailcrm-base.php b/tests/test-wc-retailcrm-base.php index aa280df..c24c3c9 100644 --- a/tests/test-wc-retailcrm-base.php +++ b/tests/test-wc-retailcrm-base.php @@ -132,6 +132,11 @@ class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper $this->assertArrayHasKey('bind_by_sku', $this->baseRetailcrm->form_fields); $this->assertArrayHasKey('update_number', $this->baseRetailcrm->form_fields); $this->assertArrayHasKey('product_description', $this->baseRetailcrm->form_fields); + + //loyalty + $this->assertArrayHasKey('loyalty', $this->baseRetailcrm->form_fields); + $this->assertArrayHasKey('loyalty_terms', $this->baseRetailcrm->form_fields); + $this->assertArrayHasKey('loyalty_personal', $this->baseRetailcrm->form_fields); } public function test_retailcrm_form_fields_value() diff --git a/tests/test-wc-retailcrm-loyalty.php b/tests/test-wc-retailcrm-loyalty.php new file mode 100644 index 0000000..8e53969 --- /dev/null +++ b/tests/test-wc-retailcrm-loyalty.php @@ -0,0 +1,495 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +class WC_Retailcrm_Loyalty_Test extends WC_Retailcrm_Test_Case_Helper +{ + protected $responseMock; + protected $apiMock; + + /** @var \WC_Retailcrm_Loyalty */ + protected $loyalty; + + public function setUp() + { + $this->responseMock = $this->getMockBuilder('\WC_Retailcrm_Response_Helper') + ->disableOriginalConstructor() + ->setMethods(['isSuccessful']) + ->getMock() + ; + + $this->responseMock->setResponse(['success' => true]); + $this->setMockResponse($this->responseMock, 'isSuccessful', true); + + $this->apiMock = $this->getMockBuilder('\WC_Retailcrm_Client_V5') + ->disableOriginalConstructor() + ->setMethods(['customersGet', 'getLoyaltyAccountList', 'createLoyaltyAccount', 'activateLoyaltyAccount', 'calculateDiscountLoyalty', 'getSingleSiteForKey', 'applyBonusToOrder']) + ->getMock() + ; + + $this->setMockResponse($this->apiMock, 'customersGet', ['customer' => ['id' => 1]]); + $this->setMockResponse($this->apiMock, 'createLoyaltyAccount', $this->responseMock); + $this->setMockResponse($this->apiMock, 'activateLoyaltyAccount', $this->responseMock); + $this->setMockResponse($this->apiMock, 'getSingleSiteForKey', 'woo'); + + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + } + + /** + * @dataProvider DataLoyaltyRetailCrm::getDataLoyalty() + */ + public function testGetForm($isSuccessful, $body, $expected) + { + $response = new WC_Retailcrm_Response($isSuccessful ? 200 : 400, $body); + + $this->setMockResponse($this->apiMock, 'getLoyaltyAccountList', $response); + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + + $result = $this->loyalty->getForm(1); + + if (isset($result['form'])) { + $this->assertTrue((bool) stripos($result['form'], $expected)); + } + } + + public function testRegistrationLoyalty() + { + $result = $this->loyalty->registerCustomer(1, '89999999999', 'test'); + + $this->assertTrue($result); + } + + public function testActivateLoyalty() + { + $result = $this->loyalty->activateLoyaltyCustomer(1); + + $this->assertTrue($result); + } + + /** + * @dataProvider DataLoyaltyRetailCrm::getDataCalculation() + */ + public function testGetDiscountLoyalty($response, $expected) + { + $responseMock = new WC_Retailcrm_Response(200, json_encode($response)); + $this->setMockResponse($this->apiMock, 'calculateDiscountLoyalty', $responseMock); + + $cartItems = $this->createCart(); + + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + + $method = $this->getPrivateMethod('getDiscountLoyalty', $this->loyalty); + + $discount = $method->invokeArgs($this->loyalty, [$cartItems, 'test', 1]); + $this->assertEquals($expected, $discount); + + foreach ($cartItems as $items) { + $items['data']->delete(true); + } + } + + /** + * @dataProvider DataLoyaltyRetailCrm::dataCheckLoyaltyCoupon() + */ + public function testIsLoyaltyCoupon($code, $expected) + { + $this->assertEquals($expected, $this->loyalty->isLoyaltyCoupon($code)); + } + + /** + * @dataProvider DataLoyaltyRetailCrm::dataValidUser(); + */ + public function testIsValidOrder($customer, $corporate_enabled, $expected, $orderCorporate) + { + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, ['corporate_enabled' => $corporate_enabled]); + $wcOrder = new WC_Order(); + + if ($orderCorporate) { + $wcOrder->set_billing_company('OOO TEST'); + } + + $this->assertEquals($expected, $this->loyalty->isValidOrder($customer, $wcOrder)); + } + + /** + * @dataProvider DataLoyaltyRetailCrm::dataGetEmailsForPersonalCoupon() + */ + public function testGetCouponLoyalty($email, $code) + { + if ($code) { + $coupon = new WC_Coupon(); + + $coupon->set_usage_limit(0); + $coupon->set_amount(100); + $coupon->set_email_restrictions($email); + $coupon->set_code($code); + $coupon->save(); + } + + $coupons = $this->loyalty->getCouponLoyalty($email); + + if (!$coupons && $code === false) { + $this->assertTrue(true); + } else { + $result = false; + + foreach ($coupons as $item) { + $coupon = new WC_Coupon($item['code']); + $result = true; + + $this->assertTrue($code === $item['code']); + $coupon->delete(true); + } + + $this->assertTrue($result); + } + } + + public function testCreateLoyaltyCouponWithoutAppliedCoupon() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + + $cart = new WC_Cart(); + $cart->add_to_cart($products[0]->get_id()); + $cart->add_to_cart($products[1]->get_id()); + + $woocommerce = wc(); + $woocommerce->cart = $cart; + $woocommerce->customer = $user; + + $validatorMock = $this->getMockBuilder('\WC_Retailcrm_Loyalty_Validator') + ->disableOriginalConstructor() + ->getMock() + ; + $this->setMockResponse($validatorMock, 'checkAccount', true); + $validatorMock->loyaltyAccount['level']['type'] = 'loyalty_level'; + + $responseCalculation = DataLoyaltyRetailCrm::getDataCalculation()[1]; + $responseMock = new WC_Retailcrm_Response(200, json_encode($responseCalculation['response'])); + $this->setMockResponse($this->apiMock, 'calculateDiscountLoyalty', $responseMock); + + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + $reflection = new \ReflectionClass($this->loyalty); + $reflection_property = $reflection->getProperty('validator'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->loyalty, $validatorMock); + + $GLOBALS['woocommerce'] = $woocommerce; + $result = $this->loyalty->createLoyaltyCoupon(); + + $this->assertNotEmpty($result); + $this->assertNotEmpty($this->loyalty->getCouponLoyalty($woocommerce->customer->get_email())); + } + + public function testCreateLoyaltyCouponWithPercentDiscount() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + + $cart = new WC_Cart(); + $cart->add_to_cart($products[0]->get_id()); + $cart->add_to_cart($products[1]->get_id()); + + $woocommerce = wc(); + $woocommerce->cart = $cart; + $woocommerce->customer = $user; + + $validatorMock = $this->getMockBuilder('\WC_Retailcrm_Loyalty_Validator') + ->disableOriginalConstructor() + ->getMock() + ; + $this->setMockResponse($validatorMock, 'checkAccount', true); + $validatorMock->loyaltyAccount['level']['type'] = 'discount'; + + $responseCalculation = DataLoyaltyRetailCrm::getDataCalculation()[1]; + $responseMock = new WC_Retailcrm_Response(200, json_encode($responseCalculation['response'])); + $this->setMockResponse($this->apiMock, 'calculateDiscountLoyalty', $responseMock); + + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + $reflection = new \ReflectionClass($this->loyalty); + $reflection_property = $reflection->getProperty('validator'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->loyalty, $validatorMock); + + $GLOBALS['woocommerce'] = $woocommerce; + $result = $this->loyalty->createLoyaltyCoupon(); + + $this->assertEmpty($result); + $this->assertNotEmpty($this->loyalty->getCouponLoyalty($woocommerce->customer->get_email())); + $this->assertNotEmpty($woocommerce->cart->get_coupons()); + } + + public function testCreateLoyaltyCouponWithRefreshCoupon() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + + $cart = new WC_Cart(); + $cart->add_to_cart($products[0]->get_id()); + $cart->add_to_cart($products[1]->get_id()); + + $coupon = new WC_Coupon(); + $coupon->set_usage_limit(0); + $coupon->set_amount('50'); + $coupon->set_email_restrictions($user->get_email()); + $coupon->set_code('loyalty' . mt_rand()); + $coupon->save(); + $cart->apply_coupon($coupon->get_code()); + + $couponCode = $coupon->get_code(); + $woocommerce = wc(); + $woocommerce->cart = $cart; + $woocommerce->customer = $user; + + $validatorMock = $this->getMockBuilder('\WC_Retailcrm_Loyalty_Validator') + ->disableOriginalConstructor() + ->getMock() + ; + $this->setMockResponse($validatorMock, 'checkAccount', true); + $validatorMock->loyaltyAccount['level']['type'] = 'discount'; + + $responseCalculation = DataLoyaltyRetailCrm::getDataCalculation()[1]; + $responseMock = new WC_Retailcrm_Response(200, json_encode($responseCalculation['response'])); + $this->setMockResponse($this->apiMock, 'calculateDiscountLoyalty', $responseMock); + + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + $reflection = new \ReflectionClass($this->loyalty); + $reflection_property = $reflection->getProperty('validator'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->loyalty, $validatorMock); + + $GLOBALS['woocommerce'] = $woocommerce; + $result = $this->loyalty->createLoyaltyCoupon(true); + + $this->assertEmpty($result); + $this->assertNotEmpty($this->loyalty->getCouponLoyalty($woocommerce->customer->get_email())); + + $coupons = $woocommerce->cart->get_coupons(); + + $this->assertNotEmpty($coupons); + $this->assertFalse(isset($coupons[$couponCode])); + } + + public function testDeleteLoyaltyCouponInOrder() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + + $coupon = new WC_Coupon(); + $coupon->set_usage_limit(0); + $coupon->set_amount('50'); + $coupon->set_email_restrictions($user->get_email()); + $coupon->set_code('loyalty' . mt_rand()); + $coupon->save(); + + $wcOrder = wc_create_order([ + 'status' => null, + 'customer_id' => $user->get_id(), + 'customer_note' => null, + 'parent' => null, + 'created_via' => null, + 'cart_hash' => null, + 'order_id' => 0, + ]); + + $wcOrder->add_product($products[0]); + $wcOrder->add_product($products[1]); + $wcOrder->apply_coupon($coupon->get_code()); + $wcOrder->calculate_totals(); + $wcOrder->save(); + + $this->assertNotEmpty($wcOrder->get_coupons()); + $this->loyalty->deleteLoyaltyCouponInOrder($wcOrder); + $this->assertEmpty($wcOrder->get_coupons()); + + $wcOrder->delete(true); + } + + public function testApplyLoyaltyDiscountWithBonuses() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + $discountLoyalty = 50; + + $wcOrder = wc_create_order([ + 'status' => null, + 'customer_id' => $user->get_id(), + 'customer_note' => null, + 'parent' => null, + 'created_via' => null, + 'cart_hash' => null, + 'order_id' => 0, + ]); + + $wcOrder->add_product($products[0]); + $wcOrder->add_product($products[1]); + $wcOrder->calculate_totals(); + $wcOrder->save(); + + foreach ($wcOrder->get_items() as $id => $item) { + $currentItemsPrice[$id] = $item->get_total(); + $itemIds[] = $id; + } + + $createdCrmOrderResponse = ['site' => 'test', 'externalId' => 1, 'items' => [['discounts' => [],], ['discounts' => []]]]; + $response = new WC_Retailcrm_Response( + 200, + json_encode([ + 'order' => [ + 'items' => [ + [ + 'externalIds' => [ + [ + 'value' => '11_' . $itemIds[0] + ] + ], + 'discounts' => [ + [ + 'type' => 'bonus_charge', + 'amount' => 25 + ] + ] + ], + [ + 'externalIds' => [ + [ + 'value' => '22_' . $itemIds[1] + ] + ], + 'discounts' => [ + [ + 'type' => 'bonus_charge', + 'amount' => 25 + ] + ] + ] + ] + ] + ]) + ); + + $this->setMockResponse($this->apiMock, 'applyBonusToOrder', $response); + $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); + + $this->loyalty->applyLoyaltyDiscount($wcOrder, $createdCrmOrderResponse, $discountLoyalty); + + foreach ($wcOrder->get_items() as $id => $item) { + $this->assertNotEquals($item->get_total(), $currentItemsPrice[$id]); + } + } + + public function testApplyLoyaltyDiscountWithPercentDiscount() + { + $products = DataLoyaltyRetailCrm::createProducts(); + $user = DataLoyaltyRetailcrm::createUsers()[0]; + + $wcOrder = wc_create_order([ + 'status' => null, + 'customer_id' => $user->get_id(), + 'customer_note' => null, + 'parent' => null, + 'created_via' => null, + 'cart_hash' => null, + 'order_id' => 0, + ]); + + $wcOrder->add_product($products[0]); + $wcOrder->add_product($products[1]); + $wcOrder->calculate_totals(); + $wcOrder->save(); + + foreach ($wcOrder->get_items() as $id => $item) { + $currentItemsPrice[$id] = $item->get_total(); + $itemIds[] = $id; + } + + $createdCrmOrderResponse = [ + 'site' => 'test', + 'externalId' => 1, + 'items' => [ + [ + 'externalIds' => [ + [ + 'value' => '11_' . $itemIds[0] + ] + ], + 'discounts' => [ + [ + 'type' => 'loyalty_level', + 'amount' => 25 + ] + ] + ], + [ + 'externalIds' => [ + [ + 'value' => '22_' . $itemIds[1] + ] + ], + 'discounts' => [ + [ + 'type' => 'loyalty_level', + 'amount' => 25 + ] + ] + ] + ] + ]; + + $this->loyalty->applyLoyaltyDiscount($wcOrder, $createdCrmOrderResponse); + + foreach ($wcOrder->get_items() as $id => $item) { + $this->assertNotEquals($item->get_total(), $currentItemsPrice[$id]); + } + } + + private function getPrivateMethod($method, $class) + { + $reflection = new ReflectionClass($class); + $method = $reflection->getMethod($method); + $method->setAccessible(true); + + return $method; + } + + private function createCart() + { + $products = DataLoyaltyRetailCrm::createProducts(); + + return [ + [ + 'data' => $products[0], + 'quantity' => 1, + 'line_subtotal' => $products[0]->get_regular_price(), + 'line_total' => $products[0]->get_regular_price(),//When sale_price identical regular price, sale is empty + ], + [ + 'data' => $products[1], + 'quantity' => 3, + 'line_subtotal' => $products[1]->get_regular_price() * 3, + 'line_total' => $products[1]->get_sale_price() * 3, + ], + ]; + } +} diff --git a/tests/test-wc-retailcrm-orders.php b/tests/test-wc-retailcrm-orders.php index d8527f1..5c606d4 100644 --- a/tests/test-wc-retailcrm-orders.php +++ b/tests/test-wc-retailcrm-orders.php @@ -642,9 +642,12 @@ class WC_Retailcrm_Orders_Test extends WC_Retailcrm_Test_Case_Helper */ private function getRetailcrmOrders($retailcrm) { + $options = $this->getOptions(); + unset($options['loyalty']); + return new WC_Retailcrm_Orders( $retailcrm, - $this->getOptions(), + $options, new WC_Retailcrm_Order_Item($this->getOptions()), new WC_Retailcrm_Order_Address(), new WC_Retailcrm_Customers( @@ -669,4 +672,3 @@ class WC_Retailcrm_Orders_Test extends WC_Retailcrm_Test_Case_Helper ->getMock(); } } - diff --git a/tests/validators/test-wc-retailcrm-loyalty-validator.php b/tests/validators/test-wc-retailcrm-loyalty-validator.php new file mode 100644 index 0000000..5640f69 --- /dev/null +++ b/tests/validators/test-wc-retailcrm-loyalty-validator.php @@ -0,0 +1,142 @@ +individualClient = new WC_Customer(); + $this->individualClient->set_first_name('Test'); + $this->individualClient->set_last_name('Test'); + $this->individualClient->set_email(uniqid(md5(date('Y-m-d H:i:s'))) . '@mail.com'); + $this->individualClient->set_billing_email( $this->individualClient->get_email()); + $this->individualClient->set_password('password'); + $this->individualClient->set_billing_phone('89000000000'); + $this->individualClient->set_date_created(date('Y-m-d H:i:s')); + $this->individualClient->save(); + + $this->corpClient = new WC_Customer(); + $this->corpClient->set_first_name('Test'); + $this->corpClient->set_last_name('Test'); + $this->corpClient->set_email(uniqid(md5(date('Y-m-d H:i:s'))) . '@mail.com'); + $this->corpClient->set_billing_email($this->corpClient->get_email()); + $this->corpClient->set_password('password'); + $this->corpClient->set_billing_phone('89000000000'); + $this->corpClient->set_date_created(date('Y-m-d H:i:s')); + $this->corpClient->set_billing_company('TEST COMPANY'); + $this->corpClient->save(); + } + + /** + * @dataProvider DataLoyaltyRetailCrm::dataCheckUser() + */ + public function testCheckUser($responseApiMethod, $wcUserType, $throwMessage, $isCorpActive) + { + $this->setResponseMock(); + $this->setApiMock('customersGet', $responseApiMethod); + + $validator = new WC_Retailcrm_Loyalty_Validator($this->apiMock, $isCorpActive); + $method = $this->getPrivateMethod('checkUser', $validator); + + $wcUserId = $wcUserType === 'individual' ? $this->individualClient->get_id() : $this->corpClient->get_id(); + + try { + $method->invokeArgs($validator, [$wcUserId]); + + if ($throwMessage) { + $this->fail('ValidatorException was not thrown'); + } else { + $this->assertTrue(true); + } + } catch (ValidatorException $exception) { + $this->assertEquals($throwMessage, $exception->getMessage()); + } + } + + /** @dataProvider DataLoyaltyRetailCrm::dataLoyaltyAccount() */ + public function testGetLoyaltyAccount($responseMock, $throwMessage) + { + $this->setResponseMock($responseMock); + $this->setApiMock('getLoyaltyAccountList', $this->responseMock); + + $validator = new WC_Retailcrm_Loyalty_Validator($this->apiMock, true); + $method = $this->getPrivateMethod('checkLoyaltyAccount', $validator); + + try { + $method->invokeArgs($validator, [777]); + + if ($throwMessage) { + $this->fail('ValidatorException was not thrown'); + } else { + $this->assertTrue(true); + } + } catch (ValidatorException $exception) { + $this->assertEquals($throwMessage, $exception->getMessage()); + } + } + + /** @dataProvider DataLoyaltyRetailCrm::dataCheckActiveLoyalty() */ + public function testCheckActivateLoyalty($responseMock, $throwMessage) + { + $this->setResponseMock($responseMock); + $this->setApiMock('getLoyalty', $this->responseMock); + + $validator = new WC_Retailcrm_Loyalty_Validator($this->apiMock, true); + $method = $this->getPrivateMethod('checkActiveLoyalty', $validator); + + try { + $method->invokeArgs($validator, [1]); + + if ($throwMessage) { + $this->fail('ValidatorException was not thrown'); + } else { + $this->assertTrue(true); + } + } catch (ValidatorException $exception) { + $this->assertEquals($throwMessage, $exception->getMessage()); + } + } + + private function setResponseMock($response = ['success' => true]) + { + $this->responseMock = $this->getMockBuilder('\WC_Retailcrm_Response_Helper') + ->disableOriginalConstructor() + ->setMethods(['isSuccessful']) + ->getMock() + ; + + $this->responseMock->setResponse($response); + $this->setMockResponse($this->responseMock, 'isSuccessful', true); + } + + private function setApiMock($method, $response) + { + $this->apiMock = $this->getMockBuilder('\WC_Retailcrm_Client_V5') + ->disableOriginalConstructor() + ->setMethods([$method]) + ->getMock() + ; + $this->setMockResponse($this->apiMock, $method, $response); + } + + private function getPrivateMethod($method, $class) + { + $reflection = new ReflectionClass($class); + $method = $reflection->getMethod($method); + $method->setAccessible(true); + + return $method; + } + + public function tearDown() + { + $this->individualClient->delete(); + $this->corpClient->delete(); + } +}