将1C-Bitrix上的在线商店与Mindbox集成

为了开发忠诚度系统,在线商店正在转向市场营销自动化平台,即客户数据平台(CDP)。同时,有时为了成功集成,您需要保存的数据比API文档中指示的要多。



我们将告诉您将Bitrix上的商店与Mindbox平台集成所需的数据,如何使用API​​和SDK获取数据,以及如何将组合方法与异步数据发送结合使用。







借助客户数据平台服务,零售商可以“识别”其客户的肖像,包括行为数据。此信息安全地存储在CDP中,并协助零售商进行市场营销活动和分析。



当客户将电视或任何其他产品添加到购物车时,CDP会存储此数据。基于它们,零售商可以扩展与用户的互动,例如,提供类似产品的推荐和折扣。



我们的客户之一-一家电子商店连锁店-决定连接到Mindbox CDP平台,并向我们寻求集成方面的帮助。我们针对关键用户场景进行了整合:授权,添加到购物车,付款等。



背景



网上商店可以通过两种主要方式连接到Mindbox:使用API​​或JavaScript SDK(我们将在后面讨论差异)。



为了选择最佳方式,我们转向Mindbox文档,如果没有足够的信息,我们向经理提出了问题。我们发现我们的合作恰逢Mindbox平台飞速发展的时期:Mindbox API调用的平均每日负载增加一倍(每分钟最多12万个请求,高峰时最多-25万个)。这意味着在黑色星期五和其他销售期间,由于负载的额外增加,存在CDP服务不可用且无法从与其集成的在线商店接收数据的风险。



Mindbox对这一问题做出了快速反应,并开始改进其IT系统的体系结构和基础架构,以实现四倍的安全系数。反过来,我们需要确保将购买数据顺利发送到Mindbox。这要求选择最可靠的集成方法。



Mindbox集成方法



如上所述,Mindbox建议使用API​​或JavaScript SDK进行连接。接下来,我们将考虑它们的功能。



  • JavaScript SDK



该库是服务提供的API的“包装器”。它的优点是易于集成和异步数据传输的可能性。最适合仅需要支持Web平台的情况。



限制:如果在发送时Mindbox不可用,则可能会丢失数据。如果在线商店的侧面存在js错误,则不会加载平台脚本。



  • API整合



商店与Mindbox的集成可以通过API完成。此方法减少了对JavaScript的依赖,也适用于设置异步数据提交。



局限性:我们面对这样一个事实,即未收到某些cookie数据,即设备上的唯一用户标识符(mindboxDeviceUUID)。它需要在大多数Mindbox操作中传递,以粘合用户信息。



在文档中,并非所有操作都需要这些cookie。但是,为了实现不间断的数据传输,我们与Mindbox的经理讨论了这个问题。我们发现,建议始终发送cookie以获取最大的可靠性。但是,您需要使用JavaScript SDK来接收Cookie。



组合方法



我们检查了上述集成方法,但以它们的纯粹形式,它们不适合我们的项目。为了解决零售商的业务问题并建立忠诚度系统,有必要将有关用户操作的完整数据集(包括cookie中的标识符)传输到Mindbox。同时,如果Mindbox暂时不可用,我们的目标是减少对JavaScript的依赖以及数据丢失的风险。



因此,我们转向第三个组合方法:使用队列模块同时使用API​​和JavaScript SDK。



使用Javascript SDK,我们在站点上标识用户(mindboxDeviceUUID)。然后,在服务器端,我们用所有必要的数据组成一个请求,并将其放入队列中。通过API的排队请求被发送到Mindbox服务。如果答案为否,则重新排队该请求。因此,在发送数据时,Mindbox会接收一整套必要的信息。



在下面的示例中,Sender允许您通过对响应进行初始处理来收集和发送请求。该类使用来自命令本身(请求/响应的类型,deviceUUID等)和模块设置(用于API,令牌等的参数)的数据。



<?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;
    }
}


Sendable特征包含用于将请求发送到Mindbox的所有可能的命令设置,包括预定义的命令设置,例如请求/响应的类型,请求方法以及sync / async参数。它还包含所有命令通用的方法。



<?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;
    }
}


例如,考虑用户授权事件。在授权事件处理程序中,我们将AuthorizationCommand类的对象添加到队列中。在此类中,信息的准备工作最少,因为在执行命令的那一刻,数据库中的数据可能会更改,因此您需要保存它们。同样,在Mindbox中为请求设置了相应的参数,在这种情况下,这就是操作的名称(我们将在Mindbox管理面板中找到它)。此外,您可以根据Sendable特征指定请求/响应的类型,请求方法以及sync / async参数。



<?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'],
            ],
        ];
    }
}


模块交互方案



在我们的项目中,我们确定了三个模块:



  • 基础



存储可以在整个项目中使用的通用实用程序类和接口(例如命令接口)。



  • 队列模块



与Mindbox的交互是通过命令实现的。要顺序执行它们,我们使用队列模块。该模块保存输入的命令并在执行时间到来时执行它们。



  • Mindbox集成模块



该模块在站点上“捕获”事件,例如授权,注册,创建订单,添加到购物车等,然后生成命令并将其发送到队列模块。







Mindbox模块监视站点上的事件和相关信息(包括来自cookie的事件),并根据事件形成命令并将其放入队列中。当队列模块从队列中检索命令并执行该命令时,将发送数据。如果来自Mindbox的答案是否定的,则将未成功执行的命令移至队列的末尾;如果为肯定的,则将成功执行的命令从队列中移出。



因此,使用上述组合方法,我们能够确保将数据平稳地传输到Mindbox。



加起来



在本文中,我们研究了在线商店可以连接到客户数据平台以开发忠诚度系统的方式。



在我们的示例中,Mindbox文档描述了两种主要的连接方法:通过Javascript SDK和通过API。为了提高数据传输的可靠性,即使在CDP服务暂时不可用的情况下,我们也选择并实现了第三种组合方法:使用API​​和Javascript SDK以及异步数据发送。



感谢您的关注!希望本文对您有所帮助。



All Articles