Структура ответа#

Детальное описание структуры ответа от /avia/ticket_issue.

Основная структура#

Ответ содержит список выписанных билетов.

ПолеТипОписание
ticketsarrayМассив выписанных билетов

Структура Ticket#

ПолеТипОписание
idstringИдентификатор билета
numberstringНомер билета
vendorstringКод BSP/ARC
passengerobjectИнформация о пассажире

Структура Passenger#

ПолеТипОписание
idstringИдентификатор пассажира из бронирования
firstNamestringИмя пассажира
lastNamestringФамилия пассажира
middleNamestringОтчество пассажира
typestringТип пассажира (adt, chd, inf)
titlestringОбращение (MR, MRS, MS и т.д.)

Примеры ответов#

Успешная выписка для одного пассажира#

 1{
 2  "tickets": [
 3    {
 4      "number": "2469982473",
 5      "vendor": "555",
 6      "passenger": {
 7        "id": "PAX_12",
 8        "firstName": "IVAN",
 9        "lastName": "EGOROV",
10        "type": "adt"
11      }
12    }
13  ]
14}

Выписка для нескольких пассажиров#

 1{
 2  "tickets": [
 3    {
 4      "number": "2469982473",
 5      "vendor": "555",
 6      "passenger": {
 7        "id": "PAX_1",
 8        "firstName": "IVAN",
 9        "lastName": "EGOROV",
10        "type": "adt"
11      }
12    },
13    {
14      "number": "2469982474",
15      "vendor": "555",
16      "passenger": {
17        "id": "PAX_2",
18        "firstName": "MARIA",
19        "lastName": "EGOROVA",
20        "type": "adt"
21      }
22    },
23    {
24      "number": "2469982475",
25      "vendor": "555",
26      "passenger": {
27        "id": "PAX_3",
28        "firstName": "PETR",
29        "lastName": "EGOROV",
30        "type": "chd"
31      }
32    }
33  ]
34}

Ответ с ошибкой - бронирование не найдено#

1{
2  "error": {
3    "code": "BOOKING_NOT_FOUND",
4    "message": "Бронирование не найдено",
5    "description": "Бронирование с указанным ID не существует",
6    "errorId": "018c5f2e-9b3a-7f4d-8e2c-1a2b3c4d5e6f"
7  }
8}

Ответ с ошибкой - билеты уже выписаны#

1{
2  "error": {
3    "code": "TICKETS_ALREADY_ISSUED",
4    "message": "Билеты уже выписаны",
5    "description": "Для данного бронирования билеты уже были выписаны ранее",
6    "errorId": "018c5f2e-9b3a-7f4d-8e2c-1a2b3c4d5e6f"
7  }
8}

Ответ с ошибкой - цена изменилась#

1{
2  "error": {
3    "code": "PRICE_CHANGED",
4    "message": "Цена изменилась",
5    "description": "Стоимость бронирования изменилась. Требуется подтверждение новой цены",
6    "errorId": "018c5f2e-9b3a-7f4d-8e2c-1a2b3c4d5e6f"
7  }
8}

Использование данных#

Получение номеров билетов#

 1var response = await IssueTicket(bookingId, "invoice");
 2
 3Console.WriteLine("Билеты успешно выписаны:");
 4
 5foreach (var ticket in response.Tickets)
 6{
 7    Console.WriteLine($"Билет: {ticket.Number}");
 8    Console.WriteLine($"Пассажир: {ticket.Passenger.LastName} {ticket.Passenger.FirstName}");
 9    Console.WriteLine($"Тип: {ticket.Passenger.Type.ToUpper()}");
10    Console.WriteLine($"Vendor: {ticket.Vendor}");
11    Console.WriteLine();
12}

Сохранение билетов в базу данных#

 1public async Task SaveTicketsToDatabase(string bookingId, TicketIssueResponse response)
 2{
 3    foreach (var ticket in response.Tickets)
 4    {
 5        await _database.Tickets.InsertAsync(new TicketEntity
 6        {
 7            BookingId = bookingId,
 8            TicketNumber = ticket.Number,
 9            Vendor = ticket.Vendor,
10            PassengerId = ticket.Passenger.Id,
11            PassengerFirstName = ticket.Passenger.FirstName,
12            PassengerLastName = ticket.Passenger.LastName,
13            PassengerType = ticket.Passenger.Type,
14            IssuedAt = DateTime.UtcNow
15        });
16    }
17
18    await _database.SaveChangesAsync();
19
20    LogInfo($"Saved {response.Tickets.Count} tickets for booking {bookingId}");
21}

Отправка билетов пассажирам#

 1public async Task SendTicketsToPassengers(
 2    string bookingId,
 3    TicketIssueResponse ticketResponse)
 4{
 5    // Получаем полную информацию о бронировании
 6    var booking = await RetrieveBooking(bookingId);
 7
 8    // Группируем билеты по пассажирам
 9    var ticketsByPassenger = ticketResponse.Tickets
10        .GroupBy(t => t.Passenger.Id)
11        .ToList();
12
13    foreach (var group in ticketsByPassenger)
14    {
15        var passengerId = group.Key;
16        var passengerTickets = group.ToList();
17
18        // Находим контакты пассажира
19        var email = booking.Pnr.Contacts
20            .FirstOrDefault(c => c.PassengerId == passengerId && c.Type == "email")?.Value;
21
22        if (!string.IsNullOrEmpty(email))
23        {
24            await SendTicketEmail(email, booking, passengerTickets);
25            LogInfo($"Tickets sent to {email}");
26        }
27        else
28        {
29            LogWarning($"No email for passenger {passengerId}");
30        }
31    }
32}

Формирование списка для отображения#

 1public class TicketDisplayModel
 2{
 3    public string TicketNumber { get; set; }
 4    public string PassengerName { get; set; }
 5    public string PassengerType { get; set; }
 6    public string Route { get; set; }
 7    public string DepartureDate { get; set; }
 8}
 9
10public async Task<List<TicketDisplayModel>> GetTicketDisplayModels(
11    string bookingId,
12    TicketIssueResponse ticketResponse)
13{
14    var booking = await RetrieveBooking(bookingId);
15    var result = new List<TicketDisplayModel>();
16
17    foreach (var ticket in ticketResponse.Tickets)
18    {
19        var route = GetRouteDescription(booking.Pnr.Itinerary);
20        var departureDate = booking.Pnr.Itinerary[0].Segments[0].Origin.DateTime;
21
22        result.Add(new TicketDisplayModel
23        {
24            TicketNumber = ticket.Number,
25            PassengerName = $"{ticket.Passenger.LastName} {ticket.Passenger.FirstName}",
26            PassengerType = GetPassengerTypeDescription(ticket.Passenger.Type),
27            Route = route,
28            DepartureDate = departureDate
29        });
30    }
31
32    return result;
33}
34
35private string GetPassengerTypeDescription(string type)
36{
37    return type switch
38    {
39        "adt" => "Взрослый",
40        "chd" => "Ребенок",
41        "inf" => "Младенец",
42        _ => type.ToUpper()
43    };
44}

Проверка выписки#

 1public async Task<bool> VerifyTicketIssue(
 2    string bookingId,
 3    TicketIssueResponse ticketResponse)
 4{
 5    // Получаем обновленное бронирование
 6    var booking = await RetrieveBooking(bookingId);
 7
 8    // Проверяем количество билетов
 9    var expectedTicketCount = booking.Pnr.Passengers.Count;
10    var actualTicketCount = ticketResponse.Tickets.Count;
11
12    if (expectedTicketCount != actualTicketCount)
13    {
14        LogError($"Ticket count mismatch: expected {expectedTicketCount}, got {actualTicketCount}");
15        return false;
16    }
17
18    // Проверяем, что билеты появились в бронировании
19    if (booking.Pnr.Tickets.Count == 0)
20    {
21        LogError("Tickets not found in booking after issue");
22        return false;
23    }
24
25    // Проверяем соответствие номеров
26    var responseNumbers = ticketResponse.Tickets
27        .Select(t => t.Number)
28        .OrderBy(n => n)
29        .ToList();
30
31    var bookingNumbers = booking.Pnr.Tickets
32        .Select(t => t.Number)
33        .OrderBy(n => n)
34        .ToList();
35
36    if (!responseNumbers.SequenceEqual(bookingNumbers))
37    {
38        LogError("Ticket numbers mismatch between response and booking");
39        return false;
40    }
41
42    LogInfo($"Ticket issue verified for booking {bookingId}");
43    return true;
44}

Генерация отчета о выписке#

 1public class TicketIssueReport
 2{
 3    public string BookingId { get; set; }
 4    public string PnrLocator { get; set; }
 5    public DateTime IssuedAt { get; set; }
 6    public List<TicketInfo> Tickets { get; set; }
 7    public decimal TotalAmount { get; set; }
 8    public string PaymentType { get; set; }
 9}
10
11public async Task<TicketIssueReport> GenerateTicketIssueReport(
12    string bookingId,
13    TicketIssueResponse ticketResponse,
14    string paymentType)
15{
16    var booking = await RetrieveBooking(bookingId);
17
18    var report = new TicketIssueReport
19    {
20        BookingId = bookingId,
21        PnrLocator = booking.Pnr.PnrLocator,
22        IssuedAt = DateTime.UtcNow,
23        PaymentType = paymentType,
24        Tickets = new List<TicketInfo>()
25    };
26
27    foreach (var ticket in ticketResponse.Tickets)
28    {
29        var fare = booking.Pnr.FareInfo.Fares
30            .FirstOrDefault(f => f.PaxType == ticket.Passenger.Type);
31
32        report.Tickets.Add(new TicketInfo
33        {
34            Number = ticket.Number,
35            Vendor = ticket.Vendor,
36            PassengerName = $"{ticket.Passenger.LastName} {ticket.Passenger.FirstName}",
37            PassengerType = ticket.Passenger.Type,
38            Amount = fare?.Price.Total.Value ?? 0
39        });
40
41        report.TotalAmount += fare?.Price.Total.Value ?? 0;
42    }
43
44    return report;
45}

Типичные ошибки#

Код ошибкиHTTP статусПричинаРешение
BOOKING_NOT_FOUND404Бронирование не найденоПроверьте bookingId
BOOKING_CANCELLED400Бронирование отмененоСоздайте новое бронирование
TICKETS_ALREADY_ISSUED400Билеты уже выписаныИспользуйте существующие
PAYMENT_REQUIRED400Требуется оплатаПодтвердите оплату
PRICE_CHANGED400Цена измениласьПроверьте новую цену
TIME_LIMIT_EXPIRED400Истек срок выпискиСоздайте новое бронирование
INVALID_PASSENGER_DATA400Некорректные данныеИсправьте данные
INVALID_PAYMENT_TYPE400Некорректный тип оплатыПроверьте тип оплаты

Обработка ответа#

Успешная выписка#

 1var response = await httpClient.PostAsJsonAsync(
 2    "https://test.travel-api.ru/avia/ticket_issue",
 3    new { bookingId, payment = new { type = "invoice" } }
 4);
 5
 6if (response.IsSuccessStatusCode)
 7{
 8    var result = await response.Content
 9        .ReadFromJsonAsync<TicketIssueResponse>();
10
11    Console.WriteLine($"Выписано билетов: {result.Tickets.Count}");
12
13    // Сохраняем билеты
14    await SaveTickets(bookingId, result.Tickets);
15
16    // Отправляем пассажирам
17    await SendTicketsToPassengers(bookingId, result);
18
19    // Обновляем статус
20    await UpdateBookingStatus(bookingId, "ticketed");
21}

Обработка ошибок с повторной попыткой#

 1public async Task<TicketIssueResponse> IssueTicketWithRetry(
 2    string bookingId,
 3    string paymentType,
 4    int maxRetries = 3)
 5{
 6    for (int attempt = 1; attempt <= maxRetries; attempt++)
 7    {
 8        try
 9        {
10            var response = await IssueTicket(bookingId, paymentType);
11            return response;
12        }
13        catch (ApiException ex) when (ex.Code == "PRICE_CHANGED")
14        {
15            if (attempt == maxRetries)
16            {
17                throw;
18            }
19
20            LogInfo($"Price changed on attempt {attempt}, retrying...");
21
22            // Получаем новую цену и подтверждаем
23            var repricing = await RepriceBooking(bookingId);
24            var confirmed = await ConfirmNewPrice(repricing);
25
26            if (!confirmed)
27            {
28                throw new OperationCanceledException("Price change not confirmed");
29            }
30        }
31        catch (ApiException ex) when (ex.Code == "TIME_LIMIT_EXPIRED")
32        {
33            LogError("Time limit expired, cannot retry");
34            throw;
35        }
36    }
37
38    throw new Exception("Failed to issue ticket after retries");
39}

Связанные операции#