Added functionality of abandoned carts
This commit is contained in:
parent
aed62d49f9
commit
d3f298645d
@ -1,3 +1,6 @@
|
||||
## 2022-03-17 4.6.0
|
||||
* Added functionality of abandoned carts
|
||||
|
||||
## 2023-03-02 4.5.4
|
||||
* Fix display payment methods
|
||||
|
||||
|
@ -2,4 +2,44 @@
|
||||
|
||||
В версии 4.6.0 добавлен функционал выгрузки брошенных корзин.
|
||||
|
||||
Для активации необходимо включить опцию ***Выгружать брошенные корзины***
|
||||
Для активации необходимо включить опцию ***Выгружать брошенные корзины***
|
||||
|
||||
### Брошенные корзины
|
||||
|
||||
Брошенная корзина - клиент заходит на сайт, добавляет/удаляет товары в корзине, а затем завершает визит без оформления заказа.
|
||||
|
||||
> Важно:
|
||||
> * Корзины выгружаются только для зарегестрированных клиентов;
|
||||
> * Для корректной работы корзин, один API ключ = один магизн в CRM;
|
||||
|
||||
При разработке функционала, ориентировались на хуки корзины в WooCommerce:
|
||||
* Хуки для метода **set_cart**:
|
||||
* **woocommerce_add_to_cart** - добавление товара в корзину;
|
||||
* **woocommerce_after_cart_item_quantity_update** - изменение кол-во товара в корзине;
|
||||
* **woocommerce_cart_item_removed** - удаление товара с корзины;
|
||||
* Хуки для метода **clear_cart**:
|
||||
* **woocommerce_cart_emptied** - полная очистка корзины. Также срабатывает при создании заказа;
|
||||
|
||||
Корзина создается в CRM, при первом добавлении товара.
|
||||
|
||||
**Фильтры:**
|
||||
|
||||
> retailcrm_process_cart - позволяет кастомизировать данные корзины.
|
||||
|
||||
**Пример использования:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter('retailcrm_process_cart', 'process_crm_cart');
|
||||
|
||||
function process_crm_cart($crmCart, $cartItems)
|
||||
{
|
||||
$crmCart['updatedAt'] = null;
|
||||
|
||||
return $crmCart;
|
||||
}
|
||||
```
|
||||
/
|
||||
**Возможные API ошибки:**
|
||||
* WC_Retailcrm_Client_V5::cartGet : Error: [HTTP-code 404] - корзина не найдена в CRM;
|
@ -30,6 +30,9 @@ if (!class_exists('WC_Retailcrm_Base')) {
|
||||
/** @var WC_Retailcrm_Uploader */
|
||||
protected $uploader;
|
||||
|
||||
/** @var WC_Retailcrm_Cart */
|
||||
protected $cart;
|
||||
|
||||
/**
|
||||
* Init and hook in the integration.
|
||||
*
|
||||
@ -103,6 +106,15 @@ if (!class_exists('WC_Retailcrm_Base')) {
|
||||
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);
|
||||
|
||||
add_action('woocommerce_add_to_cart', [$this, 'set_cart']);
|
||||
add_action('woocommerce_after_cart_item_quantity_update', [$this, 'set_cart']);
|
||||
add_action('woocommerce_cart_item_removed', [$this, 'set_cart']);
|
||||
add_action('woocommerce_cart_emptied', [$this, 'clear_cart']);
|
||||
}
|
||||
|
||||
// Deactivate hook
|
||||
add_action('retailcrm_deactivate', [$this, 'deactivate']);
|
||||
}
|
||||
@ -385,6 +397,71 @@ if (!class_exists('WC_Retailcrm_Base')) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and update cart in CRM
|
||||
*
|
||||
* @codeCoverageIgnore Check in another tests
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_cart()
|
||||
{
|
||||
global $woocommerce;
|
||||
|
||||
$site = $this->apiClient->getSingleSiteForKey();
|
||||
$cartItems = $woocommerce->cart->get_cart();
|
||||
$customerId = $woocommerce->customer->get_id();
|
||||
|
||||
if (empty($site)) {
|
||||
writeBaseLogs('Error with CRM credentials: need an valid apiKey assigned to one certain site');
|
||||
} elseif (empty($customerId)) {
|
||||
writeBaseLogs('Abandoned carts work only for registered customers');
|
||||
} else {
|
||||
$isCartExist = $this->cart->isCartExist($customerId, $site);
|
||||
$isSuccessful = $this->cart->processCart($customerId, $cartItems, $site, $isCartExist);
|
||||
|
||||
if ($isSuccessful) {
|
||||
writeBaseLogs('Cart for customer ID: ' . $customerId . ' processed. Hook: ' . current_filter());
|
||||
} else {
|
||||
writeBaseLogs('Cart for customer ID: ' . $customerId . ' not processed. Hook: ' . current_filter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cart in CRM for 2 cases:
|
||||
* 1. Delete all items from the basket;
|
||||
* 2. Create an order, items from the cart are automatically deleted.
|
||||
*
|
||||
* The hook is called 3 times.
|
||||
*
|
||||
* @codeCoverageIgnore Check in another tests
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cart()
|
||||
{
|
||||
global $woocommerce;
|
||||
|
||||
$site = $this->apiClient->getSingleSiteForKey();
|
||||
$customerId = $woocommerce->customer->get_id();
|
||||
|
||||
if (empty($site)) {
|
||||
writeBaseLogs('Error with CRM credentials: need an valid apiKey assigned to one certain site');
|
||||
} elseif (empty($customerId)) {
|
||||
writeBaseLogs('Abandoned carts work only for registered customers');
|
||||
} else {
|
||||
$isCartExist = $this->cart->isCartExist($customerId, $site);
|
||||
$isSuccessful = $this->cart->clearCart($customerId, $site, $isCartExist);
|
||||
|
||||
if ($isSuccessful) {
|
||||
writeBaseLogs('Cart for customer ID: ' . $customerId . ' cleared. Hook: ' . current_filter());
|
||||
} elseif ($isCartExist) {
|
||||
writeBaseLogs('Cart for customer ID: ' . $customerId . ' not cleared. Hook: ' . current_filter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit order in retailCRM
|
||||
*
|
||||
|
99
src/include/class-wc-retailcrm-cart.php
Normal file
99
src/include/class-wc-retailcrm-cart.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Carts')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Cart - Allows transfer data carts with CMS.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Cart
|
||||
{
|
||||
protected $apiClient;
|
||||
protected $dateFormat;
|
||||
|
||||
public function __construct($apiClient)
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
$this->dateFormat = 'Y-m-d H:i:sP';
|
||||
}
|
||||
|
||||
public function isCartExist($customerId, $site): bool
|
||||
{
|
||||
$getCart = $this->apiClient->cartGet($customerId, $site);
|
||||
|
||||
return !empty($getCart['cart']['externalId']);
|
||||
}
|
||||
|
||||
public function processCart($customerId, $cartItems, $site, $isCartExist): bool
|
||||
{
|
||||
$isSuccessful = false;
|
||||
|
||||
try {
|
||||
$crmCart = [
|
||||
'customer' => ['externalId' => $customerId],
|
||||
'clearAt' => null,
|
||||
'updatedAt' => date($this->dateFormat)
|
||||
];
|
||||
|
||||
// If new cart, need set createdAt and externalId
|
||||
if (!$isCartExist) {
|
||||
$crmCart['createdAt'] = date($this->dateFormat);
|
||||
$crmCart['externalId'] = $customerId . uniqid('_', true);
|
||||
}
|
||||
|
||||
// If you delete one by one
|
||||
if (empty($cartItems)) {
|
||||
return $this->clearCart($customerId, $site, $isCartExist);
|
||||
}
|
||||
|
||||
foreach ($cartItems as $item) {
|
||||
$product = $item['data'];
|
||||
|
||||
$crmCart['items'][] = [
|
||||
'offer' => ['externalId' => $product->get_id()],
|
||||
'quantity' => $item['quantity'],
|
||||
'createdAt' => $product->get_date_created()->date($this->dateFormat) ?? date($this->dateFormat),
|
||||
'updatedAt' => $product->get_date_modified()->date($this->dateFormat) ?? date($this->dateFormat),
|
||||
'price' => wc_get_price_including_tax($product),
|
||||
];
|
||||
}
|
||||
|
||||
$crmCart = apply_filters(
|
||||
'retailcrm_process_cart',
|
||||
WC_Retailcrm_Plugin::clearArray($crmCart),
|
||||
$cartItems
|
||||
);
|
||||
|
||||
$setResponse = $this->apiClient->cartSet($crmCart, $site);
|
||||
$isSuccessful = $setResponse->isSuccessful() && !empty($setResponse['success']);
|
||||
} catch (Throwable $exception) {
|
||||
writeBaseLogs('Error process cart: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return $isSuccessful;
|
||||
}
|
||||
|
||||
public function clearCart($customerId, $site, $isCartExist): bool
|
||||
{
|
||||
$isSuccessful = false;
|
||||
|
||||
try {
|
||||
if ($isCartExist) {
|
||||
$crmCart = ['customer' => ['externalId' => $customerId], 'clearedAt' => date($this->dateFormat)];
|
||||
$clearResponse = $this->apiClient->cartClear($crmCart, $site);
|
||||
$isSuccessful = $clearResponse->isSuccessful() && !empty($clearResponse['success']);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
writeBaseLogs('Error clear cart: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return $isSuccessful;
|
||||
}
|
||||
}
|
||||
endif;
|
@ -5,7 +5,7 @@ Tags: Интеграция, Simla.com, simla
|
||||
Requires PHP: 7.0
|
||||
Requires at least: 5.3
|
||||
Tested up to: 6.0
|
||||
Stable tag: 4.5.4
|
||||
Stable tag: 4.6.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.6.0 =
|
||||
* Added functionality of abandoned carts
|
||||
|
||||
= 4.5.4 =
|
||||
* Fix display payment methods
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Description: Integration plugin for WooCommerce & Simla.com
|
||||
* Author: RetailDriver LLC
|
||||
* Author URI: http://retailcrm.pro/
|
||||
* Version: 4.5.4
|
||||
* Version: 4.6.0
|
||||
* Tested up to: 6.0
|
||||
* WC requires at least: 5.4
|
||||
* WC tested up to: 6.9
|
||||
@ -119,6 +119,7 @@ if (!class_exists( 'WC_Integration_Retailcrm')) :
|
||||
require_once(self::checkCustomFile('include/class-wc-retailcrm-icml.php'));
|
||||
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-customers.php'));
|
||||
require_once(self::checkCustomFile('include/class-wc-retailcrm-inventories.php'));
|
||||
require_once(self::checkCustomFile('include/class-wc-retailcrm-history.php'));
|
||||
|
@ -16,7 +16,7 @@
|
||||
*
|
||||
* @link https://wordpress.org/plugins/woo-retailcrm/
|
||||
*
|
||||
* @version 4.5.4
|
||||
* @version 4.6.0
|
||||
*
|
||||
* @package RetailCRM
|
||||
*/
|
||||
|
@ -15,58 +15,76 @@ use datasets\DataCartRetailCrm;
|
||||
*/
|
||||
class WC_Retailcrm_Cart_Test extends WC_Retailcrm_Test_Case_Helper
|
||||
{
|
||||
protected $apiClientMock;
|
||||
protected $cart;
|
||||
protected $apiMock;
|
||||
protected $responseMock;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->responseMock = $this->getMockBuilder('\WC_Retailcrm_Response_Helper')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(
|
||||
[
|
||||
'isSuccessful',
|
||||
'offsetExists',
|
||||
]
|
||||
)
|
||||
->setMethods(['isSuccessful'])
|
||||
->getMock();
|
||||
|
||||
$this->responseMock->setResponse(['id' => 1]);
|
||||
|
||||
$this->apiClientMock = $this->getMockBuilder('\WC_Retailcrm_Client_V5')
|
||||
$this->apiMock = $this->getMockBuilder('\WC_Retailcrm_Client_V5')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(
|
||||
[
|
||||
'cartGet',
|
||||
'cartSet',
|
||||
'cartClear',
|
||||
]
|
||||
)
|
||||
->setMethods(['cartGet', 'cartSet', 'cartClear'])
|
||||
->getMock();
|
||||
|
||||
$this->responseMock->setResponse(['success' => true, ]);
|
||||
$this->setMockResponse($this->responseMock, 'isSuccessful', true);
|
||||
$this->setMockResponse($this->responseMock, 'offsetExists', true);
|
||||
$this->setMockResponse($this->apiClientMock, 'cartSet', $this->responseMock);
|
||||
$this->setMockResponse($this->apiClientMock, 'cartClear', $this->responseMock);
|
||||
$this->setMockResponse($this->apiClientMock, 'cartGet',$this->responseMock);
|
||||
$this->setMockResponse($this->apiMock, 'cartGet', ['cart' => ['externalId' => 1]]);
|
||||
$this->setMockResponse($this->apiMock, 'cartSet', $this->responseMock);
|
||||
$this->setMockResponse($this->apiMock, 'cartClear', $this->responseMock);
|
||||
|
||||
$this->cart = new WC_Retailcrm_Cart($this->apiMock);
|
||||
}
|
||||
|
||||
public function testGetCart()
|
||||
public function testApiGetCart()
|
||||
{
|
||||
$this->responseMock->setResponse(DataCartRetailCrm::dataGetCart());
|
||||
$response = $this->apiClientMock->cartGet(1, 'test-site');
|
||||
$this->assertNotEmpty($response->__get('cart'));
|
||||
$this->assertTrue($response->__get('success'));
|
||||
|
||||
$response = $this->apiMock->cartGet(1, 'test-site');
|
||||
|
||||
$this->assertNotEmpty($response['cart']);
|
||||
$this->assertNotEmpty($response['cart']['externalId']);
|
||||
$this->assertEquals(1, $response['cart']['externalId']);
|
||||
}
|
||||
|
||||
public function testApiSetCart()
|
||||
{
|
||||
$response = $this->apiMock->cartSet(DataCartRetailCrm::dataSetCart(), 'test-site');
|
||||
|
||||
$this->assertNotEmpty($response['success']);
|
||||
$this->assertTrue($response['success']);
|
||||
}
|
||||
|
||||
public function testApiClearCart()
|
||||
{
|
||||
$response = $this->apiMock->cartClear(DataCartRetailCrm::dataClearCart(), 'test-site');
|
||||
|
||||
$this->assertNotEmpty($response['success']);
|
||||
$this->assertTrue($response['success']);
|
||||
}
|
||||
|
||||
public function testSetCart()
|
||||
{
|
||||
$response = $this->apiClientMock->cartSet(DataCartRetailCrm::dataSetCart(), 'test-site');
|
||||
$this->assertEquals(1, $response->__get('id'));
|
||||
$wcCart = new WC_Cart();
|
||||
$product = WC_Helper_Product::create_simple_product();
|
||||
$customerId = wc_create_new_customer('mail_test@mail.es', 'test');
|
||||
|
||||
$wcCart->add_to_cart($product->get_id(), 1, 0, [], []);
|
||||
|
||||
$this->assertTrue($this->cart->processCart($customerId, $wcCart->get_cart(), 'woo', true));
|
||||
}
|
||||
|
||||
public function testGetCart()
|
||||
{
|
||||
$this->assertTrue($this->cart->isCartExist(1, 'woo'));
|
||||
}
|
||||
|
||||
public function testClearCart()
|
||||
{
|
||||
$response = $this->apiClientMock->cartClear(DataCartRetailCrm::dataClearCart(), 'test-site');
|
||||
$this->assertEquals(1, $response->__get('id'));
|
||||
$this->assertTrue($this->cart->clearCart(1, 'woo', true));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user