We will tell you what data we needed to integrate a store on Bitrix with the Mindbox platform, how it can be obtained using the API and SDK, and how to use a combined approach with asynchronous data sending.
With the help of Customer Data Platform services, retailers βrecognizeβ the portrait of their customer, including behavioral data. This information is stored securely in CDP and assists retailers with marketing campaigns and analytics.
When a customer adds a TV or any other product to the cart, CDP stores this data. Based on them, retailers can expand their interaction with users, for example, offer recommendations and discounts on similar products.
One of our clients - a chain of electronics stores - decided to connect to the Mindbox CDP platform and turned to us for help in the integration. We carried out integration for key user scenarios: authorization, adding to the cart, payment, etc.
Background
Online stores can connect to Mindbox in two main ways: using the API or the JavaScript SDK (we'll explain the differences later).
To choose the best way, we turned to the Mindbox documentation, and if there was not enough information, we asked the manager questions. We found that our cooperation coincided with a period of rapid growth of the Mindbox platform: the average daily load on Mindbox API calls has doubled (up to 120 thousand requests per minute, at the peak - up to 250 thousand). This meant that during Black Friday and other sales, due to an additional increase in load, there was a risk that the CDP service would be unavailable and would not receive data from the online store that was integrated with it.
Mindbox responded quickly to this problem and began to improve the architecture and infrastructure of its IT systems to achieve a fourfold safety factor. We, in turn, needed to ensure that purchase data was sent to Mindbox smoothly. This required choosing the most reliable integration method.
Mindbox integration methods
As noted above, Mindbox suggests using an API or JavaScript SDK to connect. Next, we will consider their features.
JavaScript SDK
The library is a "wrapper" over the API provided by the service. Its advantages are ease of integration and the possibility of asynchronous data transfer. Best suited for cases where only the web platform needs to be supported.
Limitations: Potential data loss if Mindbox is not available at the time of sending. Platform scripts will not be loaded if there are js errors on the side of the online store.
API Integration
The integration of the store with Mindbox can be done through the API. This method reduces dependence on JavaScript and is also suitable for setting up asynchronous data submission.
Limitations: we were faced with the fact that some cookie data was not received, namely the unique user identifier on the device (mindboxDeviceUUID). It needs to be passed in most Mindbox operations to glue user information.
In the documentation, these cookies are not required for all operations. And yet, striving for uninterrupted data transfer, we discussed this issue with the manager of Mindbox. We found out that it is advisable to always send a cookie for maximum reliability. However, you need to use the JavaScript SDK to receive cookies.
Combined method
We have considered the methods of integration described above, but in their pure form they were not suitable for our project. To solve business problems of a retailer and build a loyalty system, it was necessary to transfer to Mindbox a complete set of data on user actions, including the identifier from the cookie. At the same time, we aimed to reduce dependence on JavaScript and the risk of data loss if Mindbox is temporarily unavailable.
Therefore, we turned to the third, combined method: we work with both the API and the JavaScript SDK using our queue module.
Using the Javascript SDK, we identify the user on the site (mindboxDeviceUUID). Then, on the server side, we form a request with all the necessary data and put it into the queue. Requests from the queue through the API are sent to the Mindbox service. If the answer is no, the request is re-queued. Thus, when sending data, Mindbox receives a complete set of necessary information.
In the example below, the Sender class allows you to collect and send a request by doing the initial processing of the response. The class uses data from the command itself (request / response type, deviceUUID, etc.) and from the module settings (parameters for working with the API, tokens, etc.).
<?php
declare(strict_types=1);
namespace Simbirsoft\MindBox;
use Bitrix\Main\Web\Uri;
use Bitrix\Main\Web\HttpClient;
use Simbirsoft\Base\Converters\ConverterFactory;
use Simbirsoft\MindBox\Contracts\SendableCommand;
class Sender
{
/** @var Response */
protected $response;
/** @var SendableCommand */
protected $command;
/**
* Sender constructor.
*
* @param SendableCommand $command
*/
public function __construct(SendableCommand $command)
{
$this->command = $command;
}
/**
* .
*
* @return array
*/
protected function getHeaders(): array
{
return [
'Accept' => Type\ContentType::REQUEST[$this->command->getRequestType()],
'Content-Type' => Type\ContentType::RESPONSE[$this->command->getResponseType()],
'Authorization' => 'Mindbox secretKey="'. Options::get('secretKey') .'"',
'User-Agent' => $this->command->getHttpInfo('HTTP_USER_AGENT'),
'X-Customer-IP' => $this->command->getHttpInfo('REMOTE_ADDR'),
];
}
/**
* .
*
* @return string
*/
protected function getUrl(): string
{
$uriParts = [
Options::get('apiUrl'),
$this->command->getOperationType(),
];
$uriParams = [
'operation' => $this->command->getOperation(),
'endpointId' => Options::get('endpointId'),
];
$deviceUUID = $this->command->getHttpInfo('deviceUUID');
if (!empty($deviceUUID)) {
$uriParams['deviceUUID'] = $deviceUUID;
}
return (new Uri(implode('/', $uriParts)))
->addParams($uriParams)
->getUri();
}
/**
* .
*
* @return bool
*/
public function send(): bool
{
$httpClient = new HttpClient();
$headers = $this->getHeaders();
foreach ($headers as $name => $value) {
$httpClient->setHeader($name, $value, false);
}
$encodedData = null;
$request = $this->command->getRequestData();
if (!empty($request)) {
$converter = ConverterFactory::factory($this->command->getRequestType());
$encodedData = $converter->encode($request);
}
$url = $this->getUrl();
if ($httpClient->query($this->command->getMethod(), $url, $encodedData)) {
$converter = ConverterFactory::factory($this->command->getResponseType());
$response = $converter->decode($httpClient->getResult());
$this->response = new Response($response);
return true;
}
return false;
}
/**
* @return Response
*/
public function getResponse(): Response
{
return $this->response;
}
}
The Sendable trait contains all possible command settings for sending a request to Mindbox, including predefined ones, such as the type of request / response, the request method, and the sync / async parameter. It also contains methods common to all commands.
<?php
declare(strict_types=1);
namespace Simbirsoft\MindBox\Traits;
use RuntimeException;
use Bitrix\Main\Context;
use Simbirsoft\MindBox\Type;
use Simbirsoft\MindBox\Sender;
use Simbirsoft\MindBox\Response;
use Bitrix\Main\Localization\Loc;
use Simbirsoft\MindBox\Contracts\SendableCommand;
Loc::loadMessages($_SERVER['DOCUMENT_ROOT'] .'/local/modules/simbirsoft.base/lib/Contracts/Command.php');
trait Sendable
{
/** @var string (GET/POST) */
protected $method = Type\OperationMethod::POST;
/** @var string (sync/async) */
protected $operationType = Type\OperationType::ASYNC;
/** @var string (json/xml) */
protected $requestType = Type\ContentType::JSON;
/** @var string (json/xml) */
protected $responseType = Type\ContentType::JSON;
/** @var array */
protected $data = [];
/**
* .
* @return string
*/
abstract public function getOperation(): string;
/**
* .
*
* @return array
*/
abstract public function getRequestData(): array;
/**
* HTTP
*
* @return string
*/
public function getMethod(): string
{
return $this->method;
}
/**
*
*
* @return string
*
* @noinspection PhpUnused
*/
public function getOperationType(): string
{
return $this->operationType;
}
/**
* .
*
* @return string
*
* @noinspection PhpUnused
*/
public function getRequestType(): string
{
return $this->requestType;
}
/**
* .
*
* @return string
*
* @noinspection PhpUnused
*/
public function getResponseType(): string
{
return $this->responseType;
}
/**
*
*
* @return void
*/
public function initHttpInfo(): void
{
$server = Context::getCurrent()->getServer();
$request = Context::getCurrent()->getRequest();
$this->data = [
'X-Customer-IP' => $server->get('REMOTE_ADDR'),
'User-Agent' => $server->get('HTTP_USER_AGENT'),
'deviceUUID' => $request->getCookieRaw('mindboxDeviceUUID'),
];
}
/**
*
*
* @param string $key
* @param string $default
*
* @return string
*
* @noinspection PhpUnused
*/
public function getHttpInfo(string $key, string $default = ''): string
{
return $this->data[$key] ?? $default;
}
/**
* .
*
* @return void
*
* @throws RuntimeException
*/
public function execute(): void
{
/** @var SendableCommand $thisCommand */
$thisCommand = $this;
$sender = new Sender($thisCommand);
if ($sender->send()) {
throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));
}
$response = $sender->getResponse();
if (!$response->isSuccess()) {
throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));
}
if (!$this->prepareResponse($response)) {
throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));
}
}
/**
* .
*
* @param Response $response
*
* @return bool
*/
public function prepareResponse(Response $response): bool
{
// $body = $response->getBody();
// $status = $body['customer']['processingStatus'];
/**
* :
* AuthenticationSucceeded -
* AuthenticationFailed -
* NotFound -
*/
return true;
}
}
As an example, consider the user authorization event. In the authorization event handler, we add an object of the AuthorizationCommand class to our queue. In this class, the minimum necessary preparation of information takes place, since at the moment the command is executed, the data in the database may change, and you need to save them. Also, the corresponding parameters for the request in Mindbox are set, in this case this is the name of the operation (we will find it in the Mindbox admin panel). Additionally, you can specify the type of request / response, the request method, and the sync / async parameter according to the Sendable trait.
<?php
declare(strict_types=1);
namespace Simbirsoft\MindBox\Commands;
use Simbirsoft\Queue\Traits\Queueable;
use Simbirsoft\MindBox\Traits\Sendable;
use Simbirsoft\Queue\Contracts\QueueableCommand;
use Simbirsoft\MindBox\Contracts\SendableCommand;
final class AuthorizationCommand implements QueueableCommand, SendableCommand
{
use Queueable, Sendable;
/** @var array */
protected $user;
/**
* AuthorizationCommand constructor.
*
* @param array $user
*/
public function __construct(array $user)
{
$keys = ['ID', 'EMAIL', 'PERSONAL_MOBILE'];
$this->user = array_intersect_key($user, array_flip($keys));
$this->initHttpInfo();
}
/**
* .
*
* @return string
*/
public function getOperation(): string
{
return 'AuthorizationOnWebsite';
}
/**
* .
*
* @return array
*/
public function getRequestData(): array
{
return [
'customer' => [
'email' => $this->user['EMAIL'],
],
];
}
}
Module interaction scheme
In our project, we have identified three modules:
Base
Stores common utility classes and interfaces (such as command interfaces) that can then be used throughout the project.
Queue module
Interaction with Mindbox is implemented through commands. To execute them sequentially, we use our queue module. This module saves the incoming commands and executes them when the execution time comes.
Mindbox integration module
This module "catches" events on the site, such as authorization, registration, creating an order, adding to the cart and others, and then generates commands and sends them to the queue module.
The Mindbox module monitors events and related information on the site, including from cookies, forms a command from them and puts it into a queue. When the queue module retrieves a command from the queue and executes it, data is sent. If the answer from Mindbox is negative - the unsuccessfully executed command is moved to the end of the queue, if positive - the successfully executed command is removed from the queue.
Thus, using the combined method described above, we were able to ensure a smooth data transfer to Mindbox.
Summing up
In this article, we examined the ways in which an online store can connect to the Customer Data Platform to develop loyalty systems.
In our example, the Mindbox documentation described two main connection methods: via the Javascript SDK and via the API. To improve the reliability of data transmission, even in the case of temporary unavailability of the CDP service, we have chosen and implemented a third, combined method: using the API and Javascript SDK, with asynchronous data sending.
Thank you for attention! We hope this article was helpful to you.