예제 #1
0
        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
        }
예제 #2
0
 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);
예제 #7
0
        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);
        }
예제 #8
0
        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);
        }
예제 #9
0
        //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
            });
        }
예제 #10
0
        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));
            }
        }