Структура ответа#
Детальное описание структуры ответа от /avia/ticket_issue.
Основная структура#
Ответ содержит список выписанных билетов.
| Поле | Тип | Описание |
|---|
tickets | array | Массив выписанных билетов |
Структура Ticket#
| Поле | Тип | Описание |
|---|
id | string | Идентификатор билета |
number | string | Номер билета |
vendor | string | Код BSP/ARC |
passenger | object | Информация о пассажире |
Структура Passenger#
| Поле | Тип | Описание |
|---|
id | string | Идентификатор пассажира из бронирования |
firstName | string | Имя пассажира |
lastName | string | Фамилия пассажира |
middleName | string | Отчество пассажира |
type | string | Тип пассажира (adt, chd, inf) |
title | string | Обращение (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_FOUND | 404 | Бронирование не найдено | Проверьте bookingId |
BOOKING_CANCELLED | 400 | Бронирование отменено | Создайте новое бронирование |
TICKETS_ALREADY_ISSUED | 400 | Билеты уже выписаны | Используйте существующие |
PAYMENT_REQUIRED | 400 | Требуется оплата | Подтвердите оплату |
PRICE_CHANGED | 400 | Цена изменилась | Проверьте новую цену |
TIME_LIMIT_EXPIRED | 400 | Истек срок выписки | Создайте новое бронирование |
INVALID_PASSENGER_DATA | 400 | Некорректные данные | Исправьте данные |
INVALID_PAYMENT_TYPE | 400 | Некорректный тип оплаты | Проверьте тип оплаты |
Обработка ответа#
Успешная выписка#
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}
Связанные операции#