public async Task Start(Guid searchId, AvailabilityRequest request, Suppliers supplier, AgentContext agent, string languageCode, AccommodationBookingSettings searchSettings) { var supplierConnector = _supplierConnectorManager.Get(supplier); try { _logger.LogProviderAvailabilitySearchStarted($"Availability search with id '{searchId}' on supplier '{supplier}' started"); await GetAvailability(request, languageCode) .Bind(ConvertCurrencies) .Map(ApplyMarkups) .Map(Convert) .Tap(SaveResult) .Finally(SaveState); } catch (Exception ex) { // TODO: Add sentry error notification _logger.LogProviderAvailabilitySearchException(ex); var result = ProblemDetailsBuilder.Fail <List <AccommodationAvailabilityResult> >("Server error", HttpStatusCode.InternalServerError); await SaveState(result); } async Task <Result <EdoContracts.Accommodations.Availability, ProblemDetails> > GetAvailability(AvailabilityRequest request, string languageCode) { var saveToStorageTask = _storage.SaveState(searchId, SupplierAvailabilitySearchState.Pending(searchId), supplier); var getAvailabilityTask = supplierConnector.GetAvailability(request, languageCode); await Task.WhenAll(saveToStorageTask, getAvailabilityTask); return(getAvailabilityTask.Result); } async Task <Result <EdoContracts.Accommodations.Availability, ProblemDetails> > ConvertCurrencies(EdoContracts.Accommodations.Availability availabilityDetails) { var convertedResults = new List <SlimAccommodationAvailability>(availabilityDetails.Results.Count); foreach (var slimAccommodationAvailability in availabilityDetails.Results) { // Currency can differ in different results var(_, isFailure, convertedAccommodationAvailability, error) = await _priceProcessor.ConvertCurrencies(agent, slimAccommodationAvailability, AvailabilityResultsExtensions.ProcessPrices, AvailabilityResultsExtensions.GetCurrency); if (isFailure) { return(Result.Failure <EdoContracts.Accommodations.Availability, ProblemDetails>(error)); } convertedResults.Add(convertedAccommodationAvailability); } return(new EdoContracts.Accommodations.Availability(availabilityDetails.AvailabilityId, availabilityDetails.NumberOfNights, availabilityDetails.CheckInDate, availabilityDetails.CheckOutDate, convertedResults, availabilityDetails.NumberOfProcessedAccommodations)); } async Task <EdoContracts.Accommodations.Availability> ApplyMarkups(EdoContracts.Accommodations.Availability response) { var markup = await _priceProcessor.ApplyMarkups(agent, response, AvailabilityResultsExtensions.ProcessPrices); return(markup.Data); } async Task <List <AccommodationAvailabilityResult> > Convert(EdoContracts.Accommodations.Availability details) { var supplierAccommodationIds = details.Results .Select(r => new SupplierAccommodationId(supplier, r.Accommodation.Id)) .Distinct() .ToList(); var duplicates = await _duplicatesService.GetDuplicateReports(supplierAccommodationIds); var timestamp = _dateTimeProvider.UtcNow().Ticks; return(details .Results .Select(accommodationAvailability => { var minPrice = accommodationAvailability.RoomContractSets.Min(r => r.Rate.FinalPrice.Amount); var maxPrice = accommodationAvailability.RoomContractSets.Max(r => r.Rate.FinalPrice.Amount); var accommodationId = new SupplierAccommodationId(supplier, accommodationAvailability.Accommodation.Id); var resultId = Guid.NewGuid(); var duplicateReportId = duplicates.TryGetValue(accommodationId, out var reportId) ? reportId : string.Empty; var roomContractSets = accommodationAvailability.RoomContractSets .ToRoomContractSetList() .ApplySearchFilters(searchSettings, _dateTimeProvider, request.CheckInDate); return new AccommodationAvailabilityResult(resultId, timestamp, details.AvailabilityId, accommodationAvailability.Accommodation, roomContractSets, duplicateReportId, minPrice, maxPrice, request.CheckInDate, request.CheckOutDate); }) .Where(a => a.RoomContractSets.Any()) .ToList()); } Task SaveResult(List <AccommodationAvailabilityResult> results) => _storage.SaveResults(searchId, supplier, results); Task SaveState(Result <List <AccommodationAvailabilityResult>, ProblemDetails> result) { var state = result.IsSuccess ? SupplierAvailabilitySearchState.Completed(searchId, result.Value.Select(r => r.DuplicateReportId).ToList(), result.Value.Count) : SupplierAvailabilitySearchState.Failed(searchId, result.Error.Detail); if (state.TaskState == AvailabilitySearchTaskState.Completed) { _logger.LogProviderAvailabilitySearchSuccess( $"Availability search with id '{searchId}' on supplier '{supplier}' finished successfully with '{state.ResultCount}' results"); } else { _logger.LogProviderAvailabilitySearchFailure( $"Availability search with id '{searchId}' on supplier '{supplier}' finished with state '{state.TaskState}', error '{state.Error}'"); } return(_storage.SaveState(searchId, state, supplier)); } }
public async Task <Result <AccommodationBookingInfo, ProblemDetails> > Finalize(string referenceCode, AgentContext agentContext, string languageCode) { var(_, isGetBookingFailure, booking, getBookingError) = await GetAgentsBooking() .Ensure(b => agentContext.AgencyId == b.AgencyId, ProblemDetailsBuilder.Build("The booking does not belong to your current agency")) .Bind(CheckBookingIsPaid) .OnFailure(WriteLogFailure); if (isGetBookingFailure) { return(Result.Failure <AccommodationBookingInfo, ProblemDetails>(getBookingError)); } return(await BookOnProvider(booking, referenceCode, languageCode) .Tap(ProcessResponse) .Bind(CaptureMoneyIfDeadlinePassed) .OnFailure(VoidMoneyAndCancelBooking) .Bind(GenerateInvoice) .Tap(NotifyOnCreditCardPayment) .Bind(GetAccommodationBookingInfo) .Finally(WriteLog)); Task <Result <Data.Booking.Booking, ProblemDetails> > GetAgentsBooking() => _bookingRecordsManager.GetAgentsBooking(referenceCode, agentContext).ToResultWithProblemDetails(); Result <Data.Booking.Booking, ProblemDetails> CheckBookingIsPaid(Data.Booking.Booking bookingFromPipe) { if (bookingFromPipe.PaymentStatus == BookingPaymentStatuses.NotPaid) { _logger.LogBookingFinalizationPaymentFailure($"The booking with reference code: '{referenceCode}' hasn't been paid"); return(ProblemDetailsBuilder.Fail <Data.Booking.Booking>("The booking hasn't been paid")); } return(bookingFromPipe); } Task ProcessResponse(Booking bookingResponse) => _bookingResponseProcessor.ProcessResponse(bookingResponse, booking); async Task <Result <EdoContracts.Accommodations.Booking, ProblemDetails> > CaptureMoneyIfDeadlinePassed(EdoContracts.Accommodations.Booking bookingInPipeline) { var daysBeforeDeadline = Infrastructure.Constants.Common.DaysBeforeDeadlineWhenPayForBooking; var now = _dateTimeProvider.UtcNow(); var deadlinePassed = booking.CheckInDate <= now.AddDays(daysBeforeDeadline) || (booking.DeadlineDate.HasValue && booking.DeadlineDate.Value.Date <= now.AddDays(daysBeforeDeadline)); if (!deadlinePassed) { return(bookingInPipeline); } var(_, isPaymentFailure, _, paymentError) = await _bookingPaymentService.Capture(booking, agentContext.ToUserInfo()); if (isPaymentFailure) { return(ProblemDetailsBuilder.Fail <EdoContracts.Accommodations.Booking>(paymentError)); } return(bookingInPipeline); } Task VoidMoneyAndCancelBooking(ProblemDetails problemDetails) => this.VoidMoneyAndCancelBooking(booking, agentContext); async Task <Result <Booking, ProblemDetails> > NotifyOnCreditCardPayment(Booking details) { await _bookingMailingService.SendCreditCardPaymentNotifications(details.ReferenceCode); return(details); } Task <Result <Booking, ProblemDetails> > GenerateInvoice(Booking details) => this.GenerateInvoice(details, referenceCode, agentContext); Task <Result <AccommodationBookingInfo, ProblemDetails> > GetAccommodationBookingInfo(Booking details) => _bookingRecordsManager.GetAccommodationBookingInfo(details.ReferenceCode, languageCode) .ToResultWithProblemDetails(); void WriteLogFailure(ProblemDetails problemDetails) => _logger.LogBookingByAccountFailure($"Failed to finalize a booking with reference code: '{referenceCode}'. Error: {problemDetails.Detail}"); Result <T, ProblemDetails> WriteLog <T>(Result <T, ProblemDetails> result) => LoggerUtils.WriteLogByResult(result, () => _logger.LogBookingFinalizationSuccess($"Successfully finalized a booking with reference code: '{referenceCode}'"), () => _logger.LogBookingFinalizationFailure( $"Failed to finalize a booking with reference code: '{referenceCode}'. Error: {result.Error.Detail}")); }
public async Task <Result <RoomContractSetAvailability?, ProblemDetails> > GetExactAvailability( Guid searchId, string htId, Guid roomContractSetId, AgentContext agent, string languageCode) { Baggage.AddSearchId(searchId); var settings = await _accommodationBookingSettingsService.Get(agent); var(_, isFailure, result, error) = await GetSelectedRoomSet(searchId, htId, roomContractSetId); if (isFailure) { return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(error)); } var availabilityRequest = await _availabilityRequestStorage.Get(searchId); if (availabilityRequest.IsFailure) { return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(availabilityRequest.Error)); } var connectorEvaluationResult = await EvaluateOnConnector(result); if (connectorEvaluationResult.IsFailure) { _logger.LogBookingEvaluationFailure(connectorEvaluationResult.Error.Status, connectorEvaluationResult.Error.Detail); return((RoomContractSetAvailability?)null); } if (connectorEvaluationResult.Value is null) { return((RoomContractSetAvailability?)null); } var originalSupplierPrice = connectorEvaluationResult.Value.Value.RoomContractSet.Rate.FinalPrice; var(_, isContractFailure, contractKind, contractError) = await _adminAgencyManagementService.GetContractKind(agent.AgencyId); if (isContractFailure) { return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(contractError)); } var accommodationResult = await GetAccommodation(result.htId, languageCode); if (accommodationResult.IsFailure) { _logger.LogGetAccommodationByHtIdFailed(result.htId, accommodationResult.Error.Detail); return((RoomContractSetAvailability?)null); } var slimAccommodation = GetSlimAccommodation(accommodationResult.Value); return(await Convert(connectorEvaluationResult.Value.Value) .Bind(ConvertCurrencies) .Map(ProcessPolicies) .Map(ApplyMarkups) .Map(AlignPrices) .Tap(SaveToCache) .Map(e => e.Data) .Check(CheckAgainstSettings) .Check(CheckCancellationPolicies) .Map(ApplySearchSettings)); async Task <Result <(Suppliers Supplier, RoomContractSet RoomContractSet, string AvailabilityId, string htId, string CountryHtId, string LocalityHtId)> > GetSelectedRoomSet(Guid searchId, string htId, Guid roomContractSetId) { var result = (await _roomSelectionStorage.GetResult(searchId, htId, settings.EnabledConnectors)) .SelectMany(r => { return(r.Result.RoomContractSets .Select(rs => (Source: r.Supplier, RoomContractSet: rs, r.Result.AvailabilityId, r.Result.HtId, CountryHtId: r.Result.CountryHtId, LocalityHtId: r.Result.LocalityHtId))); }) .SingleOrDefault(r => r.RoomContractSet.Id == roomContractSetId); if (result.Equals(default))