Intégration d'une boutique en ligne sur 1C-Bitrix avec Mindbox

Pour développer des systèmes de fidélisation, les boutiques en ligne se tournent vers les plateformes d'automatisation du marketing, Customer Data Platform (CDP). Dans le même temps, pour une intégration réussie, vous devez parfois enregistrer plus de données que ce qui est indiqué dans la documentation de l'API.



Nous vous dirons de quelles données nous avons besoin pour intégrer un magasin sur 1C-Bitrix avec la plate-forme Mindbox, comment vous pouvez l'obtenir à l'aide de l'API et du SDK, et comment utiliser une approche combinée avec l'envoi de données asynchrone.







À l'aide des services de la plateforme de données client, les détaillants «reconnaissent» le profil de leur client, y compris les données comportementales. Ces informations sont stockées en toute sécurité dans CDP et aident les détaillants à réaliser des campagnes marketing et des analyses.



Lorsqu'un client ajoute un téléviseur ou tout autre produit au panier, CDP stocke ces données. Sur cette base, les détaillants peuvent élargir leur interaction avec les utilisateurs, par exemple, proposer des recommandations et des remises sur des produits similaires.



Un de nos clients - une chaîne de magasins d'électronique - a décidé de se connecter à la plateforme Mindbox CDP et s'est tourné vers nous pour obtenir de l'aide lors de l'intégration. Nous avons réalisé l'intégration pour des scénarios utilisateurs clés: autorisation, ajout au panier, paiement, etc.



Contexte



Les boutiques en ligne peuvent se connecter à Mindbox de deux manières principales: à l'aide de l'API ou du SDK JavaScript (nous expliquerons les différences plus tard).



Pour choisir le meilleur moyen, nous nous sommes tournés vers la documentation Mindbox, et s'il n'y avait pas suffisamment d'informations, nous avons posé des questions au manager. Nous avons constaté que notre coopération coïncidait avec une période de croissance rapide de la plate-forme Mindbox: la charge quotidienne moyenne sur les appels de l'API Mindbox a doublé (jusqu'à 120000 requêtes par minute, au sommet - jusqu'à 250000). Cela signifiait que pendant le Black Friday et d'autres ventes, en raison d'une augmentation supplémentaire de la charge, il y avait un risque que le service CDP ne soit pas disponible et ne reçoive pas de données de la boutique en ligne qui y était intégrée.



Mindbox a réagi rapidement à ce problème et a commencé à améliorer l'architecture et l'infrastructure de ses systèmes informatiques pour atteindre un facteur de sécurité quadruple. À notre tour, nous devions nous assurer que les données d'achat étaient envoyées à Mindbox en douceur. Cela a nécessité le choix de la méthode d'intégration la plus fiable.



Méthodes d'intégration Mindbox



Comme indiqué ci-dessus, Mindbox suggère d'utiliser une API ou un SDK JavaScript pour se connecter. Ensuite, nous examinerons leurs caractéristiques.



  • SDK JavaScript



La bibliothèque est un "wrapper" sur l'API fournie par le service. Ses avantages sont la facilité d'intégration et la possibilité de transfert de données asynchrone. Idéal pour les cas où seule la plate-forme Web doit être prise en charge.



Limitations: perte de données potentielle si Mindbox n'est pas disponible au moment de l'envoi. Les scripts de plate-forme ne seront pas chargés s'il y a des erreurs js du côté de la boutique en ligne.



  • Intégration API



L'intégration de la boutique avec Mindbox peut se faire via l'API. Cette méthode réduit la dépendance vis-à-vis de JavaScript et convient également à la configuration de la soumission de données asynchrone.



Limitations: Nous avons été confrontés au fait que nous n'avons pas reçu certaines données de cookies, à savoir l'identifiant unique de l'utilisateur sur l'appareil (mindboxDeviceUUID). Il doit être transmis dans la plupart des opérations Mindbox pour coller les informations utilisateur.



Dans la documentation, ces cookies ne sont pas nécessaires pour toutes les opérations. Et pourtant, en recherchant une transmission de données ininterrompue, nous avons discuté de ce problème avec le responsable de Mindbox. Nous avons découvert qu'il est conseillé de toujours envoyer un cookie pour une fiabilité maximale. Cependant, vous devez utiliser le SDK JavaScript pour recevoir des cookies.



Méthode combinée



Nous avons examiné les méthodes d'intégration ci-dessus, mais dans leur forme pure, elles n'étaient pas adaptées à notre projet. Pour résoudre les problèmes commerciaux du détaillant et construire un système de fidélisation, il était nécessaire de transférer à Mindbox un ensemble complet de données sur les actions des utilisateurs, y compris l'identifiant du cookie. Dans le même temps, nous visons à réduire la dépendance à JavaScript et le risque de perte de données si Mindbox est temporairement indisponible.



Par conséquent, nous nous sommes tournés vers la troisième méthode combinée: nous travaillons à la fois avec l'API et le SDK JavaScript en utilisant notre module de file d'attente.



A l'aide du SDK Javascript, nous identifions l'utilisateur sur le site (mindboxDeviceUUID). Ensuite, côté serveur, nous formons une requête avec toutes les données nécessaires et la mettons dans la file d'attente. Les demandes en file d'attente via l'API sont envoyées au service Mindbox. Si la réponse est non, la demande est remise en file d'attente. Ainsi, lors de l'envoi de données, Mindbox reçoit un ensemble complet d'informations nécessaires.



Dans l'exemple ci-dessous, la classe Sender vous permet de collecter et d'envoyer une demande en effectuant le traitement initial de la réponse. La classe utilise les données de la commande elle-même (type de requête / réponse, deviceUUID, etc.) et des paramètres du module (paramètres pour travailler avec l'API, jetons, 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;
    }
}


Le trait Sendable contient tous les paramètres de commande possibles pour envoyer une requête à Mindbox, y compris ceux prédéfinis, tels que le type de requête / réponse, la méthode de requête et le paramètre sync / async. Il contient également des méthodes communes à toutes les commandes.



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


À titre d'exemple, considérons l'événement d'autorisation utilisateur. Dans le gestionnaire d'événements d'autorisation, nous ajoutons un objet de la classe AuthorizationCommand à notre file d'attente. Dans cette classe, la préparation minimale nécessaire des informations a lieu, car au moment de l'exécution de la commande, les données de la base de données peuvent changer et vous devez les enregistrer. De plus, les paramètres correspondants pour la demande dans Mindbox sont définis, dans ce cas, il s'agit du nom de l'opération (nous le trouverons dans le panneau d'administration de Mindbox). En outre, vous pouvez spécifier le type de demande / réponse, la méthode de demande et le paramètre sync / async selon le trait 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'],
            ],
        ];
    }
}


Schéma d'interaction du module



Dans notre projet, nous avons identifié trois modules:



  • Base



Stocke les classes d'utilitaires et les interfaces courantes (telles que les interfaces de commande) qui peuvent ensuite être utilisées tout au long du projet.



  • Module de file d'attente



L'interaction avec Mindbox est implémentée via des commandes. Pour les exécuter séquentiellement, nous utilisons notre module de file d'attente. Ce module enregistre les commandes entrantes et les exécute le moment venu.



  • Module d'intégration Mindbox



Ce module «capture» les événements sur le site, tels que l'autorisation, l'inscription, la création d'une commande, l'ajout au panier et autres, puis génère des commandes et les envoie au module files d'attente.







Le module Mindbox surveille les événements et les informations associées sur le site, y compris à partir des cookies, forme une commande à partir d'eux et les met dans une file d'attente. Lorsque le module de file d'attente récupère une commande de la file d'attente et l'exécute, les données sont envoyées. Si la réponse de Mindbox est négative - la commande exécutée sans succès est déplacée à la fin de la file d'attente, si elle est positive - la commande exécutée avec succès est supprimée de la file d'attente.



Ainsi, en utilisant la méthode combinée décrite ci-dessus, nous avons pu assurer un transfert de données fluide vers Mindbox.



Résumer



Dans cet article, nous avons examiné les moyens par lesquels une boutique en ligne peut se connecter à la plate-forme de données client pour développer des systèmes de fidélité.



Dans notre exemple, la documentation Mindbox décrit deux méthodes de connexion principales: via le SDK Javascript et via l'API. Pour améliorer la fiabilité de la transmission des données, même en cas d'indisponibilité temporaire du service CDP, nous avons choisi et mis en œuvre une troisième méthode combinée: l'utilisation de l'API et du SDK Javascript, avec envoi de données asynchrone.



Merci de votre attention! Nous espérons que cet article vous a été utile.



All Articles