تكامل متجر على الإنترنت على 1C-Bitrix مع Mindbox

لتطوير أنظمة الولاء ، تتجه المتاجر عبر الإنترنت إلى منصات أتمتة التسويق ، منصة بيانات العملاء (CDP). في نفس الوقت ، أحيانًا لتحقيق التكامل الناجح ، تحتاج إلى حفظ بيانات أكثر مما هو مذكور في وثائق API.



سنخبرك بالبيانات التي نحتاجها لدمج متجر على Bitrix مع النظام الأساسي Mindbox ، وكيف يمكن الحصول عليها باستخدام API و SDK ، وكيفية استخدام نهج مشترك مع إرسال البيانات غير المتزامن.







بمساعدة خدمات منصة بيانات العملاء ، "يتعرف" تجار التجزئة على صورة عملائهم ، بما في ذلك البيانات السلوكية. يتم تخزين هذه المعلومات بشكل آمن في CDP وتساعد تجار التجزئة في حملات التسويق والتحليلات.



عندما يضيف العميل جهاز تلفزيون أو أي منتج آخر إلى عربة التسوق ، يقوم CDP بتخزين هذه البيانات. بناءً عليها ، يمكن لتجار التجزئة توسيع تفاعلهم مع المستخدمين ، على سبيل المثال ، تقديم توصيات وخصومات على منتجات مماثلة.



قرر أحد عملائنا - سلسلة من متاجر الإلكترونيات - الاتصال بمنصة Mindbox CDP واتجه إلينا للمساعدة في التكامل. لقد أجرينا تكاملًا لسيناريوهات المستخدم الرئيسية: التفويض ، والإضافة إلى سلة التسوق ، والدفع ، وما إلى ذلك.



خلفية



يمكن للمتاجر عبر الإنترنت الاتصال بـ Mindbox بطريقتين رئيسيتين: استخدام API أو JavaScript SDK (سنتحدث عن الاختلافات لاحقًا).



لاختيار أفضل طريقة ، لجأنا إلى وثائق Mindbox ، وإذا لم تكن هناك معلومات كافية ، فقد طرحنا أسئلة على المدير. لقد وجدنا أن تعاوننا تزامن مع فترة من النمو السريع لمنصة Mindbox: فقد تضاعف متوسط ​​الحمل اليومي على مكالمات واجهة برمجة تطبيقات Mindbox (حتى 120 ألف طلب في الدقيقة ، في الذروة - حتى 250 ألفًا). هذا يعني أنه خلال يوم الجمعة الأسود والمبيعات الأخرى ، بسبب الزيادة الإضافية في الحمل ، كان هناك خطر من أن خدمة CDP لن تكون متاحة ولن تتلقى البيانات من المتجر عبر الإنترنت الذي تم دمجها معها.



استجابت Mindbox بسرعة لهذه المشكلة وبدأت في تحسين الهندسة المعمارية والبنية التحتية لأنظمة تكنولوجيا المعلومات لديها لتحقيق عامل أمان رباعي. نحن ، بدورنا ، بحاجة إلى ضمان إرسال بيانات الشراء إلى Mindbox بسلاسة. هذا يتطلب اختيار طريقة التكامل الأكثر موثوقية.



طرق تكامل Mindbox



كما هو مذكور أعلاه ، يقترح Mindbox استخدام API أو JavaScript SDK للاتصال. بعد ذلك ، سننظر في ميزاتها.



  • جافا سكريبت SDK



المكتبة عبارة عن "غلاف" فوق واجهة برمجة التطبيقات التي توفرها الخدمة. وتتمثل مزاياه في سهولة التكامل وإمكانية نقل البيانات غير المتزامن. الأنسب للحالات التي تحتاج إلى دعم منصة الويب فقط.



القيود: احتمال فقدان البيانات إذا لم يكن Mindbox متاحًا في وقت الإرسال. لن يتم تحميل البرامج النصية للنظام الأساسي في حالة وجود أخطاء js على جانب المتجر عبر الإنترنت.



  • تكامل API



يمكن دمج المتجر مع Mindbox من خلال واجهة برمجة التطبيقات. تقلل هذه الطريقة من الاعتماد على JavaScript وهي مناسبة أيضًا لإعداد إرسال البيانات غير المتزامن.



القيود: لقد واجهنا حقيقة أننا لم نتلق بعض بيانات ملفات تعريف الارتباط ، أي المعرف الفريد للمستخدم على الجهاز (mindboxDeviceUUID). يجب تمريره في معظم عمليات Mindbox لإلصاق معلومات المستخدم.



في الوثائق ، ملفات تعريف الارتباط هذه ليست مطلوبة لجميع العمليات. ومع ذلك ، سعينا جاهدين لنقل البيانات دون انقطاع ، ناقشنا هذه المشكلة مع مدير Mindbox. لقد اكتشفنا أنه من المستحسن دائمًا إرسال ملف تعريف ارتباط لتحقيق أقصى قدر من الموثوقية. ومع ذلك ، تحتاج إلى استخدام JavaScript SDK لتلقي ملفات تعريف الارتباط.



الطريقة المركبة



لقد درسنا طرق التكامل المذكورة أعلاه ، ولكن في شكلها النقي لم تكن مناسبة لمشروعنا. لحل مشاكل أعمال بائع التجزئة وبناء نظام ولاء ، كان من الضروري نقل مجموعة كاملة من البيانات حول إجراءات المستخدم إلى Mindbox ، بما في ذلك المعرف من ملف تعريف الارتباط. في الوقت نفسه ، كنا نهدف إلى تقليل اعتمادنا على JavaScript وخطر فقدان البيانات إذا كان Mindbox غير متاح مؤقتًا.



لذلك ، لجأنا إلى الطريقة الثالثة المدمجة: نحن نعمل مع كل من API و JavaScript SDK باستخدام وحدة قائمة الانتظار الخاصة بنا.



باستخدام Javascript SDK ، نحدد المستخدم على الموقع (mindboxDeviceUUID). ثم ، على جانب الخادم ، نقوم بتشكيل طلب مع جميع البيانات اللازمة ووضعها في قائمة الانتظار. يتم إرسال الطلبات في قائمة الانتظار عبر API إلى خدمة Mindbox. إذا كانت الإجابة بالنفي ، يُعاد الطلب إلى قائمة الانتظار. وهكذا ، عند إرسال البيانات ، يتلقى Mindbox مجموعة كاملة من المعلومات الضرورية.



في المثال أدناه ، تسمح لك فئة المرسل بجمع طلب وإرساله عن طريق إجراء المعالجة الأولية للاستجابة. يستخدم الفصل بيانات من الأمر نفسه (نوع الطلب / الاستجابة ، معرف الجهاز ، إلخ) ومن إعدادات الوحدة (معلمات للعمل مع 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 ، بما في ذلك الإعدادات المحددة مسبقًا ، مثل نوع الطلب / الاستجابة وطريقة الطلب ومعلمة المزامنة / غير المتزامن. يحتوي أيضًا على طرق مشتركة لجميع الأوامر.



<?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). بالإضافة إلى ذلك ، يمكنك تحديد نوع الطلب / الاستجابة وطريقة الطلب ومعلمة المزامنة / غير المتزامن وفقًا لسمة الإرسال.



<?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 الأحداث والمعلومات ذات الصلة على الموقع ، بما في ذلك من ملفات تعريف الارتباط ، وتشكل أمرًا منها وتضعها في قائمة انتظار. عندما تسترد وحدة قائمة الانتظار أمرًا من قائمة الانتظار وتقوم بتنفيذه ، يتم إرسال البيانات. إذا كانت الإجابة من Mindbox سلبية ، يتم نقل الأمر الذي تم تنفيذه دون نجاح إلى نهاية قائمة الانتظار ، وإذا كانت الإجابة إيجابية ، فسيتم إزالة الأمر المنفذ بنجاح من قائمة الانتظار.



وبالتالي ، باستخدام الطريقة المدمجة الموضحة أعلاه ، تمكنا من ضمان نقل البيانات بسلاسة إلى Mindbox.



تلخيص لما سبق



في هذه المقالة ، درسنا الطرق التي يمكن لمتجر عبر الإنترنت من خلالها الاتصال بمنصة بيانات العملاء لتطوير أنظمة ولاء.



في مثالنا ، وصفت وثائق Mindbox طريقتين رئيسيتين للاتصال: عبر Javascript SDK وعبر API. لتحسين موثوقية نقل البيانات ، حتى في حالة عدم التوفر المؤقت لخدمة CDP ، اخترنا وطبقنا طريقة ثالثة مجمعة: استخدام API و Javascript SDK ، مع إرسال البيانات غير المتزامن.



شكرآ لك على أهتمامك! نأمل أن تكون هذه المقالة مفيدة لك.



All Articles