public override ValueTask <IDatabaseTransaction> BeginOrderTransaction(FlowStage stage) { // Returning new ValueTask<IDatabaseTransaction>() for sync completion return(new ValueTask <IDatabaseTransaction>(new OrderTransaction())); // Note that this method can also be made async for async completion }
protected override OrderTransaction BeginOrderTransaction(FlowStage stage) { if (stage != FlowStage.C1) { return(new OrderTransaction()); } else { return(null); } }
//TODO: Should we move Seller into the Abstract level? Perhaps too much complexity private O ValidateFlowRequest <O>(string clientId, Uri authenticationSellerId, FlowStage stage, string uuid, OrderType orderType, O orderQuote) where O : Order, new() { var orderId = new OrderIdComponents { ClientId = clientId, uuid = uuid, OrderType = orderType }; // TODO: Add more request validation rules here SellerIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(authenticationSellerId); ILegalEntity seller = settings.SellerStore.GetSellerById(sellerIdComponents); if (seller == null) { throw new OpenBookingException(new SellerNotFoundError()); } if (orderQuote?.Seller?.Id != null && seller?.Id != orderQuote?.Seller?.Id) { throw new OpenBookingException(new SellerMismatchError()); } // Check that taxMode is set in Seller if (!(seller?.TaxMode == TaxMode.TaxGross || seller?.TaxMode == TaxMode.TaxNet)) { throw new EngineConfigurationException("taxMode must always be set in the Seller"); } // Default to BusinessToConsumer if no customer provided TaxPayeeRelationship taxPayeeRelationship = orderQuote.Customer == null ? TaxPayeeRelationship.BusinessToConsumer : orderQuote.BrokerRole == BrokerType.ResellerBroker || orderQuote.Customer.IsOrganization ? TaxPayeeRelationship.BusinessToBusiness : TaxPayeeRelationship.BusinessToConsumer; var payer = orderQuote.BrokerRole == BrokerType.ResellerBroker ? orderQuote.Broker : orderQuote.Customer; return(ProcessFlowRequest <O>(new BookingFlowContext { Stage = stage, OrderId = orderId, OrderIdTemplate = settings.OrderIdTemplate, Seller = seller, SellerId = sellerIdComponents, TaxPayeeRelationship = taxPayeeRelationship, Payer = payer }, orderQuote)); }
private ResponseContent ProcessCheckpoint(string clientId, Uri sellerId, string uuid, string orderQuoteJson, FlowStage flowStage, OrderType orderType) { OrderQuote orderQuote = OpenActiveSerializer.Deserialize <OrderQuote>(orderQuoteJson); if (orderQuote == null || orderQuote.GetType() != typeof(OrderQuote)) { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderQuote is required for C1 and C2"); } var orderResponse = ValidateFlowRequest <OrderQuote>(clientId, sellerId, flowStage, uuid, orderType, orderQuote); // Return a 409 status code if any OrderItem level errors exist return(ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), orderResponse.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.OK)); }
IDatabaseTransaction IOrderStore.BeginOrderTransaction(FlowStage stage) { return(BeginOrderTransaction(stage)); }
/// <summary> /// Stage is provided as it depending on the implementation (e.g. what level of leasing is applied) /// it might not be appropriate to create transactions for all stages. /// Null can be returned in the case that a transaction has not been created. /// </summary> /// <param name="stage"></param> /// <returns></returns> protected abstract TDatabaseTransaction BeginOrderTransaction(FlowStage stage);
public static List <OpenBookingError> ValidateAdditionalDetails(OrderItem responseOrderItem, FlowStage flowStage) { var validationErrorArray = new List <OpenBookingError>(); // Validation is needed for C2, B, and P if (flowStage == FlowStage.C1) { return(validationErrorArray); } var properties = responseOrderItem?.OrderItemIntakeForm; if (properties == null) { return(validationErrorArray); } var values = responseOrderItem.OrderItemIntakeFormResponse; foreach (var property in properties) { var required = false; if (property is BooleanFormFieldSpecification) { required = true; } else { required = property.ValueRequired ?? false; } if (required && values == null) { var error = new IncompleteIntakeFormError(); error.Instance = property.Id; error.Description = "Incomplete additional details supplied"; validationErrorArray.Add(error); continue; } var correspondingValues = values?.Where(value => value.PropertyID == property.Id).ToArray(); if (correspondingValues?.Length > 1) { var error = new InvalidIntakeFormError(); error.Instance = property.Id; error.Description = $"More than one Response provided for {property.Id}"; validationErrorArray.Add(error); continue; } var correspondingValue = correspondingValues?.SingleOrDefault(); if (required && correspondingValue == null) { var error = new IncompleteIntakeFormError(); error.Instance = property.Id; error.Description = "Incomplete additional details supplied"; validationErrorArray.Add(error); continue; } if (!required && correspondingValue == null) { continue; } switch (property) { case DropdownFormFieldSpecification p when !p.ValueOption.Contains(correspondingValue.Value.Value): { var error = new InvalidIntakeFormError(); error.Instance = property.Id; error.Description = "Value provided is not one of ValueOptions provided"; validationErrorArray.Add(error); break; } case BooleanFormFieldSpecification _ when !correspondingValue.Value.HasValueOfType <bool?>(): { var error = new InvalidIntakeFormError(); error.Instance = property.Id; error.Description = "Value provided is not a boolean"; validationErrorArray.Add(error); break; } case ShortAnswerFormFieldSpecification _ when !correspondingValue.Value.HasValueOfType <string>(): { var error = new InvalidIntakeFormError(); error.Instance = property.Id; error.Description = "Value provided is not a string"; validationErrorArray.Add(error); } break; case FileUploadFormFieldSpecification _ when !(correspondingValue.Value.HasValueOfType <Uri>() || (correspondingValue.Value.HasValueOfType <string>() && correspondingValue.Value.GetClass <string>()?.ParseUrlOrNull() != null)): { var error = new InvalidIntakeFormError(); error.Instance = property.Id; error.Description = "Value provided is not a Url"; validationErrorArray.Add(error); } break; } } return(validationErrorArray); }
public static IncompleteAttendeeDetailsError ValidateAttendeeDetails(OrderItem responseOrderItem, FlowStage flowStage) { // Validation is needed for C2, B, and P if (flowStage == FlowStage.C1) { return(null); } if (responseOrderItem?.AttendeeDetailsRequired == null) { return(null); } if (responseOrderItem.Attendee == null) { return(new IncompleteAttendeeDetailsError()); } var values = (from namespacedName in responseOrderItem.AttendeeDetailsRequired let titleCasedName = namespacedName.ToString().Split('.').Last() let name = char.ToLowerInvariant(titleCasedName[0]) + titleCasedName.Substring(1) let property = PersonAttributes[name] let value = property.GetValue(responseOrderItem.Attendee) select value).ToArray(); if (values.Length != responseOrderItem.AttendeeDetailsRequired.Count || values.Any(v => v == null)) { return(new IncompleteAttendeeDetailsError()); } return(null); }
//TODO: Should we move Seller into the Abstract level? Perhaps too much complexity protected BookingFlowContext ValidateFlowRequest <TOrder>(OrderIdComponents orderId, SellerIdComponents sellerIdComponents, ILegalEntity seller, FlowStage stage, TOrder order) where TOrder : Order, new() { // If being called from Order Status then expect Seller to already be a full object var sellerIdFromOrder = stage == FlowStage.OrderStatus ? order?.Seller.Object?.Id : order?.Seller.IdReference; if (sellerIdFromOrder == null) { throw new OpenBookingException(new SellerMismatchError()); } if (seller?.Id != sellerIdFromOrder) { throw new OpenBookingException(new InvalidAuthorizationDetailsError()); } // Check that taxMode is set in Seller if (!(seller?.TaxMode == TaxMode.TaxGross || seller?.TaxMode == TaxMode.TaxNet)) { throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "taxMode must always be set in the Seller"); } // Default to BusinessToConsumer if no customer provided TaxPayeeRelationship taxPayeeRelationship = order.Customer == null ? TaxPayeeRelationship.BusinessToConsumer : order.BrokerRole == BrokerType.ResellerBroker || order.Customer.IsOrganization ? TaxPayeeRelationship.BusinessToBusiness : TaxPayeeRelationship.BusinessToConsumer; if (order.BrokerRole == null) { throw new OpenBookingException(new IncompleteBrokerDetailsError()); } if (order.BrokerRole == BrokerType.NoBroker && order.Broker != null) { throw new OpenBookingException(new IncompleteBrokerDetailsError()); // TODO: Placeholder for https://github.com/openactive/open-booking-api/issues/167 } // Throw error on incomplete customer details if C2, P or B if Broker type is not ResellerBroker if (order.BrokerRole != BrokerType.ResellerBroker) { if (stage != FlowStage.C1 && (order.Customer == null || string.IsNullOrWhiteSpace(order.Customer.Email))) { throw new OpenBookingException(new IncompleteCustomerDetailsError()); } } // Throw error on incomplete broker details if (order.BrokerRole != BrokerType.NoBroker && (order.Broker == null || string.IsNullOrWhiteSpace(order.Broker.Name))) { throw new OpenBookingException(new IncompleteBrokerDetailsError()); } // Throw error if TotalPaymentDue is not specified at B or P if (order.TotalPaymentDue?.Price.HasValue != true && (stage == FlowStage.B || stage == FlowStage.P)) { // TODO replace this with a more specific error throw new OpenBookingException(new OpenBookingError(), "TotalPaymentDue must have a price set"); } var payer = order.BrokerRole == BrokerType.ResellerBroker ? order.Broker : order.Customer; return(new BookingFlowContext { Stage = stage, OrderId = orderId, OrderIdTemplate = settings.OrderIdTemplate, Seller = seller, SellerId = sellerIdComponents, TaxPayeeRelationship = taxPayeeRelationship, Payer = payer }); }
private async Task <ResponseContent> ProcessCheckpoint(string clientId, Uri sellerId, string uuidString, string orderQuoteJson, FlowStage flowStage, OrderType orderType) { OrderQuote orderQuote = OpenActiveSerializer.Deserialize <OrderQuote>(orderQuoteJson); if (orderQuote == null || orderQuote.GetType() != typeof(OrderQuote)) { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderQuote is required for C1 and C2"); } var(orderId, sellerIdComponents, seller) = await ConstructIdsFromRequest(clientId, sellerId, uuidString, orderType); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { var orderResponse = await ProcessFlowRequest(ValidateFlowRequest <OrderQuote>(orderId, sellerIdComponents, seller, flowStage, orderQuote), orderQuote); // Return a 409 status code if any OrderItem level errors exist return(ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), orderResponse.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.OK)); } }