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_MODE | single | single, express или multi |
COUPON_AMOUNT | 10 | Сумма одного запроса или каждого ординара при multi |
COUPON_CURRENCY | USD | Валюта купона |
SPORT_LINE_TYPE | live | live или line |
DOCUMENT_LANGUAGE | en | Язык данных линии и купона |
Не вставляйте логин, пароль, 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, пока не проверили, был ли принят
первый запрос.
Что делает пример
- Получает актуальное меню и матчи Sport Line API.
- Выбирает незаблокированный исход и формирует полный указатель Coupon API.
- Получает JWT через
/api/partner/login. - Создаёт выбранный тип купона через
/api/partner/coupons/place. - Получает каждый созданный купон по коду.
- Запрашивает активные, недавно рассчитанные и групповые результаты.
- Интерпретирует статусы купона и ставки по разным таблицам.
См. также cURL-пример, «Указатель ставки» и «Статусы и расчёт выплаты».