Integración de una tienda online en 1C-Bitrix con Mindbox

Para desarrollar sistemas de fidelización, las tiendas online están recurriendo a plataformas de automatización de marketing, Customer Data Platform (CDP). Al mismo tiempo, a veces, para una integración exitosa, es necesario guardar más datos de los indicados en la documentación de la API.



Le diremos qué datos necesitamos para integrar una tienda en 1C-Bitrix con la plataforma Mindbox, cómo puede obtenerlos usando la API y el SDK, y cómo usar un enfoque combinado con el envío de datos asíncronos.







Con la ayuda de los servicios de la Plataforma de datos del cliente, los minoristas "reconocen" el retrato de su cliente, incluidos los datos de comportamiento. Esta información se almacena de forma segura en CDP y ayuda a los minoristas con campañas de marketing y análisis.



Cuando un cliente agrega un televisor o cualquier otro producto al carrito, CDP almacena estos datos. A partir de ellos, los minoristas pueden ampliar su interacción con los usuarios, por ejemplo, ofrecer recomendaciones y descuentos en productos similares.



Uno de nuestros clientes, una cadena de tiendas de electrónica, decidió conectarse a la plataforma Mindbox CDP y acudió a nosotros en busca de ayuda para la integración. Realizamos la integración para escenarios de usuarios clave: autorización, adición al carrito, pago, etc.



Antecedentes



Las tiendas online pueden conectarse a Mindbox de dos formas principales: utilizando la API o el SDK de JavaScript (explicaremos las diferencias más adelante).



Para elegir la mejor manera, recurrimos a la documentación de Mindbox y, si no había suficiente información, le hacíamos preguntas al gerente. Descubrimos que nuestra cooperación coincidió con un período de rápido crecimiento de la plataforma Mindbox: la carga diaria promedio en las llamadas API de Mindbox se ha duplicado (hasta 120 mil solicitudes por minuto, en el pico, hasta 250 mil). Esto significaba que durante el Black Friday y otras ventas, debido al aumento adicional de carga, existía el riesgo de que el servicio CDP no estuviera disponible y no recibiera datos de la tienda en línea que estaba integrada con él.



Mindbox respondió rápidamente a este problema y comenzó a mejorar la arquitectura y la infraestructura de sus sistemas de TI para lograr un factor de seguridad cuádruple. Nosotros, a su vez, necesitábamos asegurarnos de que los datos de compra se enviaran a Mindbox sin problemas. Esto requirió elegir el método de integración más confiable.



Métodos de integración de Mindbox



Como se señaló anteriormente, Mindbox sugiere usar una API o un SDK de JavaScript para conectarse. A continuación, consideraremos sus características.



  • SDK de JavaScript



La biblioteca es un "contenedor" sobre la API proporcionada por el servicio. Sus ventajas son la facilidad de integración y la posibilidad de transferencia de datos asincrónica. Se adapta mejor a los casos en los que solo es necesario admitir la plataforma web.



Limitaciones: Posible pérdida de datos si Mindbox no está disponible en el momento del envío. Los scripts de la plataforma no se cargarán si hay errores js en el lado de la tienda en línea.



  • Integración API



La integración de la tienda con Mindbox se puede realizar a través de la API. Este método reduce la dependencia de JavaScript y también es adecuado para configurar el envío de datos asincrónico.



Limitaciones: nos enfrentamos al hecho de que algunos de los datos de las cookies no se recibieron, es decir, el identificador de usuario único en el dispositivo (mindboxDeviceUUID). Debe pasarse en la mayoría de las operaciones de Mindbox para pegar la información del usuario.



En la documentación, estas cookies no son necesarias para todas las operaciones. Y, sin embargo, luchando por una transferencia de datos ininterrumpida, discutimos este problema con el gerente de Mindbox. Descubrimos que es recomendable enviar siempre una cookie para una máxima fiabilidad. Sin embargo, debe utilizar el SDK de JavaScript para recibir cookies.



Método combinado



Examinamos los métodos de integración anteriores, pero en su forma pura no eran adecuados para nuestro proyecto. Para resolver los problemas comerciales del minorista y construir un sistema de lealtad, fue necesario transferir a Mindbox un conjunto completo de datos sobre las acciones del usuario, incluido el identificador de la cookie. Al mismo tiempo, nuestro objetivo era reducir la dependencia de JavaScript y el riesgo de pérdida de datos si Mindbox no está disponible temporalmente.



Por lo tanto, recurrimos al tercer método combinado: trabajamos tanto con la API como con el SDK de JavaScript utilizando nuestro módulo de cola.



Usando el SDK de Javascript, identificamos al usuario en el sitio (mindboxDeviceUUID). Luego, en el lado del servidor, formamos una solicitud con todos los datos necesarios y la ponemos en la cola. Las solicitudes en cola a través de API se envían al servicio Mindbox. Si la respuesta es no, la solicitud se vuelve a poner en cola. Por lo tanto, al enviar datos, Mindbox recibe un conjunto completo de información necesaria.



En el siguiente ejemplo, la clase Remitente le permite recopilar y enviar una solicitud realizando el procesamiento inicial de la respuesta. La clase utiliza datos del propio comando (tipo de solicitud / respuesta, dispositivoUUID, etc.) y de la configuración del módulo (parámetros para trabajar con la 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;
    }
}


El rasgo Sendable contiene todas las configuraciones de comando posibles para enviar una solicitud a Mindbox, incluidas las predefinidas, como el tipo de solicitud / respuesta, el método de solicitud y el parámetro sync / async. También contiene métodos comunes a todos los comandos.



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


Como ejemplo, considere el evento de autorización del usuario. En el controlador de eventos de autorización, agregamos un objeto de la clase AuthorizationCommand a nuestra cola. En esta clase se lleva a cabo la preparación mínima necesaria de la información, ya que al momento de ejecutar el comando, los datos en la base de datos pueden cambiar y es necesario guardarlos. Además, se configuran los parámetros correspondientes para la solicitud en Mindbox, en este caso este es el nombre de la operación (lo encontraremos en el panel de administración de Mindbox). Además, puede especificar el tipo de solicitud / respuesta, el método de solicitud y el parámetro sync / async de acuerdo con el rasgo Sendable.



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


Esquema de interacción del módulo



En nuestro proyecto, hemos identificado tres módulos:



  • Base



Almacena clases de utilidad e interfaces comunes (como interfaces de comando) que luego se pueden usar en todo el proyecto.



  • Módulo de cola



La interacción con Mindbox se implementa a través de comandos. Para ejecutarlos secuencialmente, usamos nuestro módulo de cola. Este módulo guarda los comandos entrantes y los ejecuta cuando llega el momento.



  • Módulo de integración de Mindbox



Este módulo "captura" eventos en el sitio, tales como autorización, registro, creación de un pedido, adición al carrito y otros, y luego genera comandos y los envía al módulo de cola.







El módulo Mindbox monitorea los eventos y la información relacionada en el sitio, incluidas las cookies, forma un comando a partir de ellos y lo coloca en una cola. Cuando el módulo de cola recupera un comando de la cola y lo ejecuta, se envían datos. Si la respuesta de Mindbox es negativa, el comando ejecutado sin éxito se mueve al final de la cola, si es positivo, el comando ejecutado con éxito se elimina de la cola.



Por lo tanto, utilizando el método combinado descrito anteriormente, pudimos garantizar una transferencia de datos sin problemas a Mindbox.



Resumiendo



En este artículo, examinamos las formas en que una tienda en línea puede conectarse a la plataforma de datos del cliente para desarrollar sistemas de fidelización.



En nuestro ejemplo, la documentación de Mindbox describe dos métodos de conexión principales: a través del SDK de Javascript y a través de la API. Para mejorar la fiabilidad de la transmisión de datos, incluso en el caso de indisponibilidad temporal del servicio CDP, hemos elegido e implementado un tercer método combinado: utilizar API y Javascript SDK, con envío de datos asincrónico.



¡Gracias por su atención! Esperamos que este artículo te haya resultado útil.



All Articles