База знаний EMS-платформы PlatParts Руководство по настройке EMS-платформы PlatParts

Примеры

На этой странице 16

Примеры: от нуля до рабочей интеграции

Три полных мини‑сценария с кодом. Домен: https://example.com.

Пример 1. Только webhook (без PHP на zapex)

Цель: CRM на https://crm.example/hooks/zapex получает новые заказы.

1. Панель

  1. Модуль уже в modules/Integrations/ или не нужен код — достаточно любого включённого пакета с подключением (можно минимальный свой модуль без hooks.php).
  2. Подключения модулей → создать → точка «Центральная» → Исходящие webhooks:
    • URL: https://crm.example/hooks/zapex
    • Секрет: whsec_...
    • Хуки: order.created (или пусто)

2. Приёмник (Node.js)

javascript
const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/hooks/zapex', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.ZAPEX_WHSEC;
  const body = JSON.parse(req.body.toString());
  const canonical = JSON.stringify(sortKeys(body));
  const sig = crypto.createHmac('sha256', secret).update(canonical).digest('hex');

  if (req.get('X-Integration-Signature') !== sig) {
    return res.status(401).end();
  }

  if (body.hook === 'order.created') {
    // idempotent: queue job by body.payload.id
  }
  res.status(200).json({ ok: true });
});

function sortKeys(obj) {
  if (Array.isArray(obj)) return obj.map(sortKeys);
  if (obj && typeof obj === 'object') {
    return Object.keys(obj).sort().reduce((acc, k) => {
      acc[k] = sortKeys(obj[k]);
      return acc;
    }, {});
  }
  return obj;
}

3. Проверка

  • Создайте тестовый заказ на точке «Центральная».
  • Worker: queue:work --queue=integrations.outbound
  • Лог CRM / integrations.log

Пример 2. PHP‑модуль + handler

Цель: при order.created писать в лог и слать HTTP в CRM.

hooks.php

php
return [
    'order.created' => \Modules\Integrations\MyCrm\Handlers\OrderCreatedHandler::class,
];

Handler

php
namespace Modules\Integrations\MyCrm\Handlers;

use App\Integrations\Hooks\IntegrationHookContext;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

final class OrderCreatedHandler
{
    public function handle(IntegrationHookContext $context): void
    {
        $orderId = $context->payload['id'] ?? null;
        Log::channel('integrations')->info('order.created', ['id' => $orderId]);

        Http::timeout(5)->post(config('services.my_crm.url'), [
            'external_id' => $orderId,
            'total' => $context->payload['total'] ?? null,
        ]);
    }
}

Симуляция без заказа

bash
php artisan integrations:hooks:simulate order.created \
  --package=MyCrm \
  --payload='{"id":999,"pickup_point_id":1,"total":1500}'

Пример 3. REST: смена статуса позиции из CRM

Цель: CRM выставляет «Выдано» через API.

1. Настройка API

Настройка API → ключ с ролью, где есть orders.write_status, точка = нужный ПВЗ.

2. Запрос

bash
curl -sS -X POST "https://example.com/api/integrations/v1/orders/42/items/7/status" \
  -H "Authorization: Bearer my-id:my-secret" \
  -H "Content-Type: application/json" \
  -d '{"status_code":"item.issued"}'

3. Ожидание в CRM

Подпишите webhook на order.item_status_changed — при смене с сайта придёт подтверждение с status_semantic.


Пример 4. Inbound: CRM пушит событие в магазин

bash
BODY='{"action":"ping"}'
SECRET='inbound-secret-from-instance-credentials'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -sS -D- -X POST \
  "https://example.com/api/integrations/v1/integrations/inbound/MyCrm/3" \
  -H "Content-Type: application/json" \
  -H "X-Integration-Signature: $SIG" \
  -d "$BODY"

Ожидается: HTTP/1.1 202 и {"accepted":true}.


Пример 5. Проверка подписи outbound (PHP)

php
$raw = file_get_contents('php://input');
$body = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$secret = getenv('OUTBOUND_SECRET');

// Как в ядре:
ksort_recursive($body);
$canonical = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$expected = hash_hmac('sha256', $canonical, $secret);

function ksort_recursive(array &$arr): void {
    ksort($arr);
    foreach ($arr as &$v) {
        if (is_array($v)) ksort_recursive($v);
    }
}

Используйте весь объект {hook, payload, sent_at}, не только payload.

Пример 6. Модуль с instance_form и bulk

  1. Manifest: instance_form, external_system, inbound_webhook, hooks.
  2. Подключения → СоздатьВсе активные точки → заполнить форму модуля (креды, маппинг).
  3. В списке — по одной строке на склад; inbound URL с разным {instance}.
  4. Раздел Интеграции отдельно не заполняется вручную, если служебные записи создаёт bulk.

См. Подключения модулей.

Связанные разделы