Центр разработчика

Интеграция с NineLogix

Начните в Test. Одобренный Live API создаёт размещённый Checkout для разовых Price, но не списывает средства при создании ссылки. Для подписок используйте только одобренный Checkout вашего Store. Для размещённой ссылки с ручной выдачей API не обязателен.

Ключи и среды

  • Храните API-ключи и секреты webhook только на сервере. Ключ показывается один раз. Не отправляйте его в журналы, клиентский код или поддержку.
  • Ключи nlx_test_ и nlx_live_ определяют среду и Store. Поля запроса не могут включить Live. Для Test и Live используйте отдельные секреты и записи; один URL обработчика допустим при строгом разделении сред.
  • Возможности подтверждаются для конкретного Store, рынка, валюты и модели оплаты. Публикация Checkout, готовность вашей интеграции и официальное разрешение запуска — разные факты.

Создание и чтение Checkout

  • POST https://www.ninelogix.com/api/v1/checkout-sessions принимает order_id (ваш стабильный номер, 1–64 безопасных ASCII-символа) и одобренный price_id. Store определяется ключом. Не отправляйте amount_minor, currency, product_name, store_id, environment или способ оплаты.
  • Обязателен заголовок Idempotency-Key (1–128 символов). Допустимы буквы, цифры, точка, подчёркивание, двоеточие и дефис; первый символ — буква или цифра. После тайм-аута повторяйте тот же order_id, price_id и ключ. Другие данные с тем же ключом дают 409.
  • Test создаёт симуляцию. Live поддерживает одобренные разовые Price и требует активного Live webhook order.paid. Для подписки используйте опубликованный Checkout. Создание ссылки не является оплатой.
  • Ответ 201: {ok, request_id, data}. Сохраните data.id, data.order_id, data.price_id и request_id. GET /checkout-sessions/{session_id} читает сессию в том же Store и среде; отдельный публичный API чтения заказов/подписок здесь не заявлен.
  • 400 — неверные поля; 401 — ключ; 403 — доступ; 404 — объект недоступен; 409 — конфликт состояния/идемпотентности; 429 — ограничение частоты. При неизвестном результате сначала запросите состояние, не создавайте повторную оплату.
curl https://www.ninelogix.com/api/v1/checkout-sessions \
  -H "Authorization: Bearer nlx_test_REPLACE" \
  -H "Idempotency-Key: order-demo-001" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"demo-001","price_id":"prc_APPROVED"}'

Подпись, приём и повторы

  • Читайте точные исходные байты ДО JSON-парсинга. X-NineLogix-Signature имеет формат v1=<hex>. HMAC-SHA256 подписывает X-NineLogix-Timestamp + точку + исходное тело. Сравнивайте равные по длине байты за постоянное время; отклоняйте время более чем на 300 секунд в прошлом или будущем.
  • Идентификатор JSON — id, не event_id. После проверки подписи сравните его с X-NineLogix-Event-Id и проверьте environment/livemode. Заголовок содержит время доставки; created — время события, которое при повторе может быть старым.
  • Атомарно сохраните (Store, среда, id) и надёжную задачу до ответа 2xx. Уже принятый дубликат подтвердите без повторной выдачи. Worker также обеспечивает одну выдачу на бизнес-заказ или оплаченный invoice_id.
  • 2xx означает только транспортный приём, не проверку подписи, обработку заказа или фактическую выдачу. Текст ответа не является доказательством бизнес-успеха. Если безопасный приём не состоялся, верните не-2xx.
  • Для order.paid сверьте Store, merchant_reference, Product, Price, amount_minor и currency с локальным заказом. В Live API исходный order_id возвращается как merchant_reference; order_id события — идентификатор заказа NineLogix. Сохраните оба.
  • Для размещённой ссылки сначала создайте локальный заказ и добавьте merchant_reference (1–120 безопасных ASCII-символов, без персональных данных). Сохраняйте ссылку на пользователя и заказ; для подписки ссылка стабильна весь срок.
  • Тайм-аут доставки — пять секунд, максимум восемь плановых попыток. Возможны повторы, задержки и иной порядок. Разрешённая повторная доставка сохраняет id/тело, меняет время и подпись и никогда не повторяет списание.

Проверка подписи исходного тела в Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

// headers: Web Headers; rawBody: Buffer read BEFORE JSON parsing.
// secret and expectedEnvironment come from your server configuration.
export function verifyNineLogixWebhook(
  rawBody, headers, secret, expectedEnvironment,
  nowSeconds = Math.floor(Date.now() / 1000)
) {
  const timestamp = headers.get("x-ninelogix-timestamp") ?? "";
  const signature = headers.get("x-ninelogix-signature") ?? "";
  const headerId = headers.get("x-ninelogix-event-id");
  if (!["test", "live"].includes(expectedEnvironment) ||
      !Buffer.isBuffer(rawBody) || !secret ||
      !/^\d{1,12}$/.test(timestamp) ||
      Math.abs(nowSeconds - Number(timestamp)) > 300 ||
      !/^v1=[0-9a-f]{64}$/.test(signature)) {
    throw new Error("Invalid webhook headers");
  }
  const expected = createHmac("sha256", secret)
    .update(timestamp + ".").update(rawBody).digest();
  const received = Buffer.from(signature.slice(3), "hex");
  if (received.length !== expected.length ||
      !timingSafeEqual(received, expected)) {
    throw new Error("Invalid webhook signature");
  }
  const event = JSON.parse(rawBody.toString("utf8"));
  // Some legacy Test transport probes omit environment, not livemode.
  if (!event || typeof event.id !== "string" || !event.id ||
      event.id !== headerId || typeof event.type !== "string" ||
      event.livemode !== (expectedEnvironment === "live") ||
      (event.environment !== expectedEnvironment &&
       !(expectedEnvironment === "test" &&
         event.environment === undefined))) {
    throw new Error("Webhook identity or environment mismatch");
  }
  return event;
}

// Signature verification is not fulfillment.
// Validate the event's business fields against your local pending order.
// Atomically persist (Store, environment, event.id) and enqueue work.
// Only then return 2xx; duplicates already durably accepted return 2xx.
// A durable worker grants once per order_id (or paid invoice_id),
// with a unique business key and transactional/outbox protection.

Идентификаторы иллюстративные: согласуйте Test-данные с локальным ожидающим заказом. Обычное завершение Test Checkout и транспортная проверка не доказывают выдачу по order.paid. Функция проверяет подлинность сообщения, но не бизнес-поля и надёжную обработку.

Синтетический пример order.paid

{
  "id": "evt_test_example",
  "type": "order.paid",
  "created": 1793070000,
  "environment": "test",
  "livemode": false,
  "data": {
    "object": {
      "id": "ord_test_example",
      "object": "order",
      "environment": "test",
      "livemode": false,
      "status": "paid",
      "order_id": "ord_test_example",
      "merchant_id": "nlx_merchant_example",
      "store_id": "sto_example",
      "merchant_reference": "demo-001",
      "product_id": "prd_example",
      "price_id": "prc_APPROVED",
      "amount_minor": 1000,
      "currency": "USD",
      "payment_method": "test",
      "paid_at": "2026-10-27T03:00:00.000Z",
      "test": true
    }
  }
}

События и бизнес-результат

  • checkout.session.completed и checkout.session.failed описывают сессию. Обычный Test Checkout отправляет completed, но не доказывает обработку order.paid. Не выдавайте доступ второй раз по обоим событиям.
  • order.paid — основание выдачи после проверки подписи и заказа. Для подписки выдавайте один раз на invoice_id; храните subscription_id, invoice_id, order_id, merchant_reference и UTC period_start/period_end (конец исключён).
  • subscription.active, subscription.updated, subscription.ended меняют состояние, но не дают новые права. Отмена в конце периода сохраняет оплаченный доступ и не означает возврат. Не отменяйте оплаченный период из-за более позднего неуспешного продления.
  • refund.succeeded и refund.failed — результат возврата. Успешный возврат отменяет только связанные права по согласованной политике, однократно; неуспешный не подтверждает возврат. Запись API Checkout не даёт права на возвраты, отмены, расчёты и выплаты.
  • Оплата, выдача, сверка, расчёт и выплата — отдельные факты. expired не доказывает отсутствие оплаты. В поддержку передавайте среду, время, request_id, id события, заказ/сессию и безопасный код ошибки, без секретов и персональных данных.

Проверка интеграции без реального платежа

  • Для автоматизации сопоставьте Test-заказ и пользователя, проверьте неверную подпись/секрет, изменённое тело, старое/будущее время и неверную среду: ни один отказ не должен выдавать доступ.
  • Повторите одно событие и подтвердите ровно одну выдачу. Проверьте отдельный бизнес-ключ заказа/invoice, восстановление прерванного worker и одинаковую сессию при повторном Idempotency-Key.
  • Проверьте фактическую Test-выдачу по согласованному order.paid, а не только HTTP 200. Сохраните безопасные идентификаторы, UTC-время и результат; не создавайте реальный платёж для этой проверки.

Подтверждение готовности и запуск

  • Повторно не заполняйте одобренные политики. Подтвердите настоящий URL/канал продаж и ручную либо автоматическую выдачу. Ручная выдача не требует API.
  • Готовность платформы и публикация не доказывают установку ссылки продавцом. Готовность интеграции не заменяет официального разрешения запуска NineLogix.
  • После запуска наблюдайте первый естественный Live-заказ. Дополнительная реальная покупка до запуска не обязательна.