Оптовая платформа цифровых товаров

Отслеживание статусов заказов FoxReload: опрос вместо вебхуков

FoxReload работает без вебхуков. Разбираем надёжный опрос GET /api/orders/{id}, poll-loop паттерны и как построить свой слой уведомлений для партнёров/клиентов.

Отслеживание статусов заказов FoxReload: опрос вместо вебхуков

Важно знать это с самого начала: в FoxReload нет вебхуков. Нет X-FoxReload-Signature, нет HMAC-колбэков, нет событий order.*, нет Settings → Webhooks. Единственный способ узнать результат заказа — это опрос GET /api/orders/{order_id}.

Эта статья объясняет, как строить надёжный poll-loop для получения кодов, и как при необходимости построить собственный слой событий поверх опроса в инфраструктуре читателя.

1. Штатный способ: опрос GET /api/orders/{order_id}

После создания заказа (POST /api/orders) регулярно запрашивайте его статус:

curl "https://public-api.foxreload.com/api/orders/{order_id}" \
  -H "X-API-Key: YOUR_API_KEY"

Возможные значения status: active, paid, processing, completed, cancelled, failed.

Терминальные состояния: completed, cancelled, failed — дальше статус не изменится.

Когда status == "completed", в каждой позиции items[].externalData — массив выданных кодов. Если по позиции была ошибка — смотрите items[].error.

async function waitForCompletion(orderId: string, maxAttempts = 30): Promise<Order> {
  for (let i = 0; i < maxAttempts; i++) {
    const order = await fetchOrder(orderId);
    if (['completed', 'cancelled', 'failed'].includes(order.status)) {
      return order;
    }
    // экспоненциальный backoff: 1s, 2s, 4s, … до 30s
    await sleep(Math.min(1000 * Math.pow(2, i), 30000));
  }
  throw new Error('order poll timeout');
}

async function fetchOrder(orderId: string): Promise<Order> {
  const resp = await fetch(
    `https://public-api.foxreload.com/api/orders/${orderId}`,
    { headers: { 'X-API-Key': process.env.FOXRELOAD_KEY! } }
  );
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  return resp.json();
}

2. Список заказов в определённом статусе

Для мониторинга незавершённых заказов удобен GET /api/orders/ с фильтром:

curl "https://public-api.foxreload.com/api/orders/?statuses=active,paid,processing&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"

Параметр statuses принимает значения через запятую: active, paid, processing, completed, cancelled, failed. Дополнительно: limit, offset.

Полезная стратегия: запускать фоновый воркер каждые 30–60 секунд, который опрашивает все незавершённые заказы (статусы active,paid,processing) и обновляет локальную БД.

3. Предотвращение дублей при отсутствии idempotency-ключей

В FoxReload нет idempotency-ключей. Это значит, что при повторном вызове POST /api/orders после network timeout может создаться второй заказ. Правильный паттерн:

async function createOrderSafe(items: OrderItem[]): Promise<Order> {
  // 1. Сохрани pending-заказ локально ДО вызова API
  const localRef = await db.orders.create({ status: 'local_pending', items });

  try {
    const order = await callCreateOrder(items);
    await db.orders.update(localRef.id, { foxreloadOrderId: order.id, status: 'submitted' });
    return order;
  } catch (err) {
    if (isNetworkError(err)) {
      // 2. При сетевой ошибке — проверь, не создался ли заказ
      const recent = await fetchRecentOrders(); // GET /api/orders/?statuses=active,paid,processing
      // Сравни по itemId/quantity/сумме с localRef
      const dup = recent.find(o => matchesLocalRef(o, localRef));
      if (dup) {
        await db.orders.update(localRef.id, { foxreloadOrderId: dup.id, status: 'submitted' });
        return dup;
      }
    }
    throw err;
  }
}

4. Построение собственного слоя уведомлений поверх опроса

Если ваш бэкенд должен пушить события партнёрам или клиентам (например, вебхуки на вашем уровне), стройте их поверх poll-loop:

// Фоновый воркер
async function pollAndNotify() {
  const pendingOrders = await db.orders.findByStatus(['active', 'paid', 'processing']);

  for (const local of pendingOrders) {
    const remote = await fetchOrder(local.foxreloadOrderId);

    if (remote.status !== local.lastKnownStatus) {
      // Статус изменился — публикуем событие в вашу очередь
      await queue.publish('order.status_changed', {
        orderId: local.id,
        oldStatus: local.lastKnownStatus,
        newStatus: remote.status,
        externalData: remote.status === 'completed'
          ? remote.items.map(i => i.externalData)
          : null,
      });

      await db.orders.update(local.id, { lastKnownStatus: remote.status });
    }
  }
}

// Запускать каждые 10–30 секунд
setInterval(pollAndNotify, 15_000);

Ваш слой затем отправляет вебхуки вашим партнёрам или уведомляет ваших клиентов — это полностью ваша инфраструктура; FoxReload в этой схеме выступает только как источник данных через REST-опрос.

5. Мониторинг и алерты

Метрики, которые стоит отслеживать в poll-loop:

Метрика Рекомендуемый алерт
Заказы в статусе processing старше 5 минут Немедленный алерт
Заказы в статусе active/paid без движения 10+ минут Проверка вручную
Статус failed по позиции (items[].error) Лог + уведомление ops
HTTP 429 от FoxReload API Увеличить интервал опроса, применить backoff

Коды ответов

Код Значение
200 OK
201 Заказ создан
401 Нет или неверный X-API-Key
403 Ключ выключен или IP вне allowlist
404 Заказ не найден
422 Ошибка валидации тела запроса
429 Rate limit — применяйте backoff
500 Серверная ошибка — ретрай с backoff

Нужна детальная документация по созданию заказов и структуре ответа? Получите доступ к FoxReload API.

Получить доступ к FoxReload API

Похожие статьи