SportAPI Документация
RU
C Документация продуктаCoupon API
v1
Услуга и цены ↗ Получить доступ ↗
Coupon API / JavaScript для Node.js

Coupon API — пример на JavaScript для Node.js

Пример выполняет полный сценарий без сторонних библиотек: находит актуальные исходы в Sport Line API, авторизуется в Coupon API, создаёт ординар, экспресс или несколько ординаров и получает их текущее состояние.

Требования и настройки

Используйте Node.js 20 или новее. Сохраните пример в файле coupon-example.mjs и передайте настройки через переменные окружения:

export COUPON_API_BASE_URL='https://YOUR_COUPON_API_DOMAIN'
export COUPON_LOGIN='YOUR_LOGIN'
export COUPON_PASSWORD='YOUR_PASSWORD'
export SPORT_LINE_BASE_URL='https://YOUR_SPORT_LINE_DOMAIN'
export SPORT_LINE_PACKAGE='YOUR_SPORT_LINE_API_KEY'

Дополнительные переменные:

ПеременнаяЗначение по умолчаниюНазначение
COUPON_EXAMPLE_MODEsinglesingle, express или multi
COUPON_AMOUNT10Сумма одного запроса или каждого ординара при multi
COUPON_CURRENCYUSDВалюта купона
SPORT_LINE_TYPElivelive или line
DOCUMENT_LANGUAGEenЯзык данных линии и купона

Не вставляйте логин, пароль, JWT или ключ линии непосредственно в файл.

Полный исполняемый пример

const requiredEnv = (name) => {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`${name} is not configured`);
  return value;
};

const couponBaseUrl = requiredEnv('COUPON_API_BASE_URL').replace(/\/$/, '');
const couponLogin = requiredEnv('COUPON_LOGIN');
const couponPassword = requiredEnv('COUPON_PASSWORD');
const sportLineBaseUrl = requiredEnv('SPORT_LINE_BASE_URL').replace(/\/$/, '');
const sportLinePackage = requiredEnv('SPORT_LINE_PACKAGE');

const exampleMode = process.env.COUPON_EXAMPLE_MODE ?? 'single';
const amount = Number(process.env.COUPON_AMOUNT ?? 10);
const currency = process.env.COUPON_CURRENCY ?? 'USD';
const lineType = process.env.SPORT_LINE_TYPE ?? 'live';
const language = process.env.DOCUMENT_LANGUAGE ?? 'en';

if (!['single', 'express', 'multi'].includes(exampleMode)) {
  throw new Error('COUPON_EXAMPLE_MODE must be single, express, or multi');
}
if (!['live', 'line'].includes(lineType)) {
  throw new Error('SPORT_LINE_TYPE must be live or line');
}
if (!Number.isFinite(amount) || amount <= 0) {
  throw new Error('COUPON_AMOUNT must be a positive number');
}

async function requestJson(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    signal: AbortSignal.timeout(15_000),
    headers: {
      Accept: 'application/json',
      ...options.headers,
    },
  });

  const payload = await response.json().catch(() => null);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${payload?.error_message ?? 'request failed'}`);
  }
  if (!payload) throw new Error('API returned invalid JSON');
  return payload;
}

function requireCouponSuccess(payload, operation) {
  if (payload.code !== 1) {
    throw new Error(
      `${operation}: ${payload.error_code ?? 'unknown'} ${payload.error_message ?? 'error'}`,
    );
  }
  return payload.body;
}

async function login() {
  const payload = await requestJson(`${couponBaseUrl}/api/partner/login`, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({username: couponLogin, password: couponPassword}),
  });
  return requireCouponSuccess(payload, 'Login').token;
}

async function sportLineGet(path) {
  return requestJson(`${sportLineBaseUrl}${path}`, {
    headers: {Package: sportLinePackage},
  });
}

async function findGames(limit) {
  const menu = await sportLineGet(`/v1/menu/${lineType}/${language}`);
  const games = new Map();

  for (const sport of menu.body ?? []) {
    for (const country of sport.sub ?? []) {
      for (const tournament of country.sub ?? []) {
        const result = await sportLineGet(
          `/v1/events/${sport.id}/${tournament.id}/sub/50/${lineType}/${language}`,
        );
        for (const group of result.body ?? []) {
          for (const game of group.events_list ?? []) {
            games.set(String(game.game_id), game);
            if (games.size >= limit) return [...games.values()];
          }
        }
      }
    }
  }
  throw new Error(`Only ${games.size} suitable events found; ${limit} required`);
}

function findOutcome(value) {
  if (Array.isArray(value)) {
    for (const item of value) {
      const outcome = findOutcome(item);
      if (outcome) return outcome;
    }
    return null;
  }
  if (!value || typeof value !== 'object') return null;

  if (
    typeof value.oc_pointer === 'string' &&
    Number.isFinite(value.oc_rate) &&
    value.oc_rate > 1 &&
    (value.oc_block === false || value.oc_block === 0 || value.oc_block == null) &&
    value.op_id == null
  ) {
    return value;
  }

  for (const child of Object.values(value)) {
    const outcome = findOutcome(child);
    if (outcome) return outcome;
  }
  return null;
}

async function getSelection(game) {
  const event = await sportLineGet(
    `/v1/event/${game.game_id}/group/${lineType}/${language}`,
  );
  const outcome = findOutcome(event.body);
  if (!outcome) throw new Error(`No available outcome for game ${game.game_id}`);

  const technicalPointer = outcome.oc_pointer.replaceAll('|', '#');
  const playerPart = outcome.op_id == null ? '' : `#${outcome.op_id}`;
  return {
    pointer: `${lineType}#${technicalPointer}#${outcome.oc_rate}${playerPart}`,
    gameId: game.game_id,
    event: `${game.opp_1_name} — ${game.opp_2_name}`,
    market: outcome.oc_group_name,
    outcome: outcome.oc_name,
    coefficient: outcome.oc_rate,
  };
}

async function placeCoupons(token, pointers) {
  const payload = await requestJson(`${couponBaseUrl}/api/partner/coupons/place`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      list_bets: pointers,
      amount,
      currency,
      callback_url: null,
      lang: language,
      mode: 'reject',
      mode_type: null,
      multi: exampleMode === 'multi',
    }),
  });
  return requireCouponSuccess(payload, 'Coupon placement').coupons;
}

async function couponRequest(token, path, options = {}) {
  const payload = await requestJson(`${couponBaseUrl}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      ...options.headers,
    },
  });
  return requireCouponSuccess(payload, path);
}

const couponStatuses = new Map([
  [0, 'NEW'], [2, 'WIN'], [4, 'LOSE'], [8, 'RETURN'], [15, 'UPDATE'],
]);
const betStatuses = new Map([
  [0, 'NET'], [1, 'WIN'], [2, 'LOSE'], [3, 'RETURN'], [4, 'RECALCULATE'],
  [21, 'HALF_WIN'], [22, 'HALF_LOSE'], [23, 'PUSH'],
]);

function summarizeCoupon(coupon) {
  return {
    couponCode: coupon.coupon_code,
    status: coupon.status,
    statusName: couponStatuses.get(coupon.status) ?? 'UNKNOWN',
    amount: coupon.amount,
    coefficient: coupon.coef,
    realWin: coupon.real_win,
    events: (coupon.events_data ?? []).map((event) => ({
      gameId: event.game_id,
      bet: event.bet_name,
      status: event.status,
      statusName: betStatuses.get(event.status) ?? 'UNKNOWN',
    })),
  };
}

async function main() {
  const requiredGames = exampleMode === 'single' ? 1 : 2;
  const games = await findGames(requiredGames);
  const selections = await Promise.all(games.map(getSelection));

  console.log('Selected outcomes:', selections.map(({pointer, ...safe}) => safe));

  const token = await login();
  const created = await placeCoupons(token, selections.map(({pointer}) => pointer));
  console.log('Created coupons:', created.map(summarizeCoupon));

  const couponCodes = created.map((coupon) => coupon.coupon_code);
  const current = await Promise.all(
    couponCodes.map((code) => couponRequest(
      token,
      `/api/partner/coupons/get?coupon_code=${encodeURIComponent(code)}`,
    )),
  );
  console.log('Current state:', current.map(summarizeCoupon));

  const active = await couponRequest(token, '/api/partner/coupons/active');
  console.log('Active coupons:', active.map(summarizeCoupon));

  const calculated = await couponRequest(
    token,
    '/api/partner/coupons/calculated?time=120',
  );
  console.log('Recently settled:', calculated.map(summarizeCoupon));

  const batch = await couponRequest(token, '/api/partner/coupons/results', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({coupon_ids: couponCodes}),
  });
  console.log('Batch result:', batch.coupons.map(summarizeCoupon));
}

main().catch((error) => {
  console.error(error.message);
  process.exitCode = 1;
});

Запуск сценариев

Ординар:

COUPON_EXAMPLE_MODE=single node coupon-example.mjs

Экспресс из двух разных матчей:

COUPON_EXAMPLE_MODE=express node coupon-example.mjs

Два отдельных ординара:

COUPON_EXAMPLE_MODE=multi node coupon-example.mjs

При multi значение COUPON_AMOUNT применяется полностью к каждому созданному ординару. Не запускайте пример повторно после timeout, пока не проверили, был ли принят первый запрос.

Что делает пример

  1. Получает актуальное меню и матчи Sport Line API.
  2. Выбирает незаблокированный исход и формирует полный указатель Coupon API.
  3. Получает JWT через /api/partner/login.
  4. Создаёт выбранный тип купона через /api/partner/coupons/place.
  5. Получает каждый созданный купон по коду.
  6. Запрашивает активные, недавно рассчитанные и групповые результаты.
  7. Интерпретирует статусы купона и ставки по разным таблицам.

См. также cURL-пример, «Указатель ставки» и «Статусы и расчёт выплаты».