NurCore API
API Reference

Exchange (Обмен билета)

4-шаговый flow для обмена билета на другой рейс (даты → рейсы → preview → execute).

Обмен билета на другой рейс (того же направления). Состоит из 4 шагов с Price Lock в Redis для защиты от race conditions.

Когда обмен невозможен

УсловиеКод ошибки
Бронь в статусе CANCELLED / COMPLETED / EXPIRED400
Хотя бы один пассажир уже прошёл регистрацию (купон в COMPLETED / CLOSED)400
Не та авиакомпания (нет прав доступа к брони)403
Бронь не найдена404
Price Lock истёк (>15 мин с момента preview)410

Flow

1. GET  /bookings/{id}/exchange/dates       → массив доступных дат
2. GET  /bookings/{id}/exchange/flights     → варианты рейсов на дату
3. POST /bookings/{id}/exchange/preview     → расчёт цены + Price Lock (Redis 15м)
4. POST /bookings/{id}/exchange/execute     → выполнить обмен

1. GET /bookings/{booking_id}/exchange/dates

Календарь доступных дат для обмена (то же направление).

curl -H "X-API-Key: $SECRET_KEY" \
     "https://api.nurcore.kg/api/v1/bookings/$BOOKING_ID/exchange/dates?\
date_from=2026-05-15&date_to=2026-08-15"

Query (optional):

ParamDefault
date_fromсегодня
date_toсегодня + 90 дней

Response 200:

{
  "booking_id": "uuid",
  "dates": ["2026-05-15", "2026-05-17", "2026-05-20"]
}

2. GET /bookings/{booking_id}/exchange/flights

Варианты рейсов на выбранную дату.

curl -H "X-API-Key: $SECRET_KEY" \
     "https://api.nurcore.kg/api/v1/bookings/$BOOKING_ID/exchange/flights?date=2026-05-17"

Query:

ParamRequired
date✓ — ISO date

Response 200:

{
  "booking_id": "uuid",
  "date": "2026-05-17",
  "legs": [
    {
      "flight_id": "uuid",
      "flight_number": "ZM-202",
      "origin_iata": "FRU",
      "destination_iata": "OSS",
      "departure_time": "2026-05-17T07:30:00Z",
      "arrival_time": "2026-05-17T08:55:00Z",
      "available_seats": 67,
      "aircraft_type": "DHC-8-402"
    }
  ]
}

legs приходит из Schedule Service. Если пусто — на этой дате рейсов нет, попросите выбрать другую дату.


3. POST /bookings/{booking_id}/exchange/preview

Рассчитывает разницу в цене + штраф + Price Lock.

curl -X POST \
  -H "X-API-Key: $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "new_flight_id": "'$NEW_FLIGHT_ID'"
  }' \
  "https://api.nurcore.kg/api/v1/bookings/$BOOKING_ID/exchange/preview"

Request body:

ПолеТипRequiredОписание
new_flight_idUUIDID нового рейса (из /exchange/flights)
exchange_quote_idstring(M6.B opt-in) Quote ID из POST /fares/quote/exchange для Decimal-precise расчёта

Response 200:

{
  "original_total": 5000.00,
  "new_total": 6500.00,
  "fare_difference": 1000.00,
  "penalty": 500.00,
  "tax_difference": 0.00,
  "extra_amount": 1500.00,
  "refund_amount": 0.00,
  "currency": "KGS",
  "changeable": true,
  "lock_expires_in": 900,
  "message": null
}

Семантика полей:

ПолеЗнакОписание
original_total≥0Сумма, уплаченная за исходный билет
new_total≥0Полная стоимость нового билета
fare_differencenew − oldРазница в тарифе (может быть отрицательная)
penalty≥0Штраф за обмен (зависит от Fare Rules)
extra_amount≥0К доплате (если new + penalty > old)
refund_amount≥0К возврату (если old > new + penalty)
lock_expires_inсекДо истечения Price Lock (default 900 = 15 минут)

Правило взаимоисключения: extra_amount и refund_amount не могут быть оба > 0 — либо доплата, либо возврат.

Pricing modes

NurCore поддерживает 2 режима расчёта exchange:

Legacy (default): старая формула, штраф = change_fee × 2.0 если < 24 часов до вылета. Может страдать от floating-point округления.

M6.B Unified (recommended): передайте exchange_quote_id из POST /api/v1/fares/quote/exchange → расчёт через единый pricing pipeline с Decimal-точностью.

# Шаг 3a — создать exchange quote (валюта стабильна, Decimal)
curl -X POST \
  -H "X-API-Key: $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"booking_id":"'$BOOKING_ID'","new_flight_id":"'$NEW_FLIGHT_ID'"}' \
  "https://api.nurcore.kg/api/v1/fares/quote/exchange"

Ответ содержит quote_id — передайте его в preview и execute.


4. POST /bookings/{booking_id}/exchange/execute

Выполнить обмен — использует Price Lock из шага 3.

curl -X POST \
  -H "X-API-Key: $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "new_flight_id": "'$NEW_FLIGHT_ID'",
    "exchange_quote_id": "'$EXCHANGE_QUOTE_ID'"
  }' \
  "https://api.nurcore.kg/api/v1/bookings/$BOOKING_ID/exchange/execute"

Что происходит:

  1. Acquire row-level lock на бронь (SELECT ... FOR UPDATE)
  2. Re-check купонов через Check-in Service
  3. Использование Price Lock из Redis (если ещё жив)
  4. Создание новой брони на новый рейс (те же пассажиры, контакт, тарифы)
  5. Отмена старой брони (status=CANCELLED)
  6. Возврат данных новой брони + старый booking_id

Response 200:

{
  "original_booking_id": "uuid-old",
  "original_booking_reference": "ABC123",
  "new_booking_id": "uuid-new",
  "new_booking_reference": "XYZ789",
  "new_flight_id": "uuid",
  "extra_amount": 1500.00,
  "refund_amount": 0,
  "currency": "KGS",
  "payment_required": true,
  "payment_url": "https://api.nurcore.kg/api/v1/bookings/uuid-new/initiate-payment"
}

Сценарии после execute:

СценарийДействие mobile/UI
extra_amount > 0 + payment_required: trueРедирект на initiate-payment для новой брони
extra_amount > 0 (B2B partner с кошельком)POST /bookings/$NEW_ID/pay-with-balance
refund_amount > 0Refund инициируется на исходный платёж автоматически
extra_amount = refund_amount = 0Новая бронь сразу в CONFIRMED

Price Lock semantics

Между preview и execute цена гарантирована на 15 минут. Если пользователь нажал «Подтвердить» через 16 минут:

  • execute вернёт 410 Gone
  • В UI отобразите «Цена изменилась, обновите расчёт» → редирект на preview

Если за окно Lock'а появились другие изменения (новые promo, изменения RBD), они не применяются — пользователь платит зафиксированную цену.


Concurrent exchanges

Обмен использует SELECT ... FOR UPDATE — если два параллельных запроса на ту же бронь → второй ждёт первого. После первого commit второй увидит status=CANCELLED и вернёт 400.

В UI ограничьте обмен одной активной сессией пользователя.


Ошибки

CodeСценарий
400Бронь в финальном статусе, или купон в COMPLETED/CLOSED
403Нет прав на эту бронь
404Бронь / рейс не найдены
410Price Lock истёк → нужен новый preview
502Schedule / Fare / Checkin сервис недоступен — retry с backoff

Связанные endpoints

On this page