public BookEventResponse BookEvent(long hostId, BookEventRequest value, out string status)
        {
            BookEventResponse result = null;

            //Validate if customer exist
            CustomerServices customerServices = new CustomerServices();
            var customer = customerServices.GetCustomerByRef(hostId, value.CustomerReferenceId);

            if (customer == null)
            {
                status = BookEventStatuses.CustomerNotFound;
                return(null);
            }

            //Validate if event and tier exist
            EventsServices eventsServices = new EventsServices();
            EventInfo      eventInfo      = eventsServices.GetEventByRef(hostId, value.EventReferenceId);

            if (eventInfo == null)
            {
                status = BookEventStatuses.EventNotFound;
                return(null);
            }
            List <EventTierInfo> eventTiers = eventsServices.GetEventTiersByEventId(eventInfo.EventId.Value);

            if (eventTiers == null && eventTiers.Count == 0)
            {
                status = BookEventStatuses.EventNotFound;
                return(null);
            }

            //Validate event status
            if (eventInfo.Status != "Active")
            {
                status = BookEventStatuses.EventNotActive;
                return(null);
            }

            //Validate if event has already started
            if (DateTime.UtcNow >= eventInfo.StartDate)
            {
                status = BookEventStatuses.EventHasAlreadyStarted;
                return(null);
            }

            //Validate if all requested tiers exist
            List <string> allAvailableEventTiers = eventTiers.Select(x => x.ReferenceId).ToList();
            List <string> requestedEventTiers    = value.Tickets.Select(x => x.EventTierReferenceId).ToList();
            bool          contained = !requestedEventTiers.Except(allAvailableEventTiers).Any();

            if (!contained)
            {
                status = BookEventStatuses.TierNotFound;
                return(null);
            }

            //Validate requested ticket quantities (must be more than zero)
            if (value.Tickets.Where(x => x.Quantity < 1).Count() > 0)
            {
                status = BookEventStatuses.InvalidTicketQuantity;
                return(null);
            }

            //Validate if tickets are still available for the requested quantities
            foreach (var ticket in value.Tickets)
            {
                var tier = eventTiers.Where(x => x.ReferenceId == ticket.EventTierReferenceId).ToList()[0];
                if (tier.AvailableTickets < ticket.Quantity)
                {
                    status = BookEventStatuses.InsufficientTickets;
                    return(null);
                }
            }

            //Calculate total cost
            decimal totalCost = 0;

            foreach (var ticket in value.Tickets)
            {
                var tier = eventTiers.Where(x => x.ReferenceId == ticket.EventTierReferenceId).ToList()[0];
                totalCost += (ticket.Quantity * tier.Price.Value);
            }

            //Check customer wallet balance
            if (customer.WalletBalance < totalCost)
            {
                status = BookEventStatuses.CustomerInsufficientFunds;
                return(null);
            }

            //TODO: Transaction lock for thread safety

            string bookingConfirmation = null;
            string ticketNumber        = null;
            string ticketUniqueId      = null;
            List <KeyValuePair <long, string> > tierTicketNumbers = new List <KeyValuePair <long, string> >();

            //Insert customer booking
            bookingConfirmation = GenerateBookingConfirmation(hostId);
            long bookingId = InsertUserBooking(customer.CustomerId.Value, eventInfo.EventId.Value, bookingConfirmation);

            //Perform transaction
            WalletServices walletService = new WalletServices();

            walletService.Transfer(customer.WalletAddress, eventInfo.WalletAddress, totalCost, bookingId, "Booking");

            //Insert customer tickets
            foreach (var ticketPurchase in value.Tickets)
            {
                var tier = eventTiers.Where(x => x.ReferenceId == ticketPurchase.EventTierReferenceId).ToList()[0];

                for (int i = 0; i < ticketPurchase.Quantity; i++)
                {
                    ticketNumber   = GenerateTicketNumber(eventInfo.EventId.Value);
                    ticketUniqueId = HashUtility.GenerateHash();

                    InsertUserTicket(customer.CustomerId.Value, bookingId, eventInfo.EventId.Value, tier.TierId.Value, ticketNumber, tier.Price.Value, "Active", ticketUniqueId);

                    tierTicketNumbers.Add(new KeyValuePair <long, string>(tier.TierId.Value, ticketNumber));
                }
            }

            //Update tickets availability
            foreach (var ticket in value.Tickets)
            {
                var tier = eventTiers.Where(x => x.ReferenceId == ticket.EventTierReferenceId).ToList()[0];
                UpdateAvailableTickets(tier.TierId.Value, tier.AvailableTickets.Value - ticket.Quantity);
            }

            //Commit to blockchain
            ContractApi blockchainContract = new ContractApi();

            for (int i = 0; i < value.Tickets.Count; i++)
            {
                var ticket = value.Tickets[i];
                var tier   = eventTiers
                             .Where(x => x.ReferenceId == ticket.EventTierReferenceId).ToList()[0];

                List <string> ticketNumbers = tierTicketNumbers
                                              .Where(x => x.Key == tier.TierId.Value)
                                              .Select(x => x.Value).ToList();

                // Update: Use BookEventV2 method on smart contract
                //blockchainContract.BookEvent(customer.CustomerAddress, eventInfo.EventUniqueId, tier.TierUniqueId, ticketNumbers);
                foreach (string number in ticketNumbers)
                {
                    blockchainContract.BookEventV2(customer.CustomerAddress, eventInfo.EventUniqueId, tier.TierUniqueId, number);
                }
            }

            result = new BookEventResponse();
            result.ConfirmationNumber = bookingConfirmation;
            result.TicketNumbers      = tierTicketNumbers.Select(x => x.Value).ToList();

            status = BookEventStatuses.Success;
            return(result);
        }
        //TODO: performance optimize this method to cancel tickets in bulk
        public CancelTicketResponse CancelAndRefundTicket(long hostId, long ticketId, out string status)
        {
            CancelTicketResponse result = null;

            AdmissionServices admissionServices = new AdmissionServices();

            //Get ticket info
            var ticket = admissionServices.GetTicketById(ticketId);

            if (ticket == null)
            {
                status = CancelTicketStatuses.TicketNotFound;
                return(null);
            }
            if (ticket.Status == TicketStatuses.Used)
            {
                status = CancelTicketStatuses.TicketAlreadyUsed;
                return(null);
            }
            if (ticket.Status == TicketStatuses.Cancelled)
            {
                status = CancelTicketStatuses.TicketAlreadyCancelled;
                return(null);
            }

            //Get event info
            var eventInfo = GetEventByRef(hostId, ticket.EventReferenceId);

            if (eventInfo == null)
            {
                status = CancelTicketStatuses.EventNotFound;
                return(null);
            }
            //Validate event status
            if (eventInfo.Status != EventStatuses.Active)
            {
                status = CancelTicketStatuses.EventNotActive;
                return(null);
            }
            //Validate if event start date is over
            if (DateTime.UtcNow >= eventInfo.StartDate)
            {
                status = CancelTicketStatuses.EventHasAlreadyStarted;
                return(null);
            }

            //Get event tier
            EventsServices eventsServices = new EventsServices();
            var            eventTier      = eventsServices.GetEventTiersByRef(hostId, ticket.TierReferenceId);

            if (eventTier == null)
            {
                status = CancelTicketStatuses.TierNotFound;
                return(null);
            }

            //Get customer info
            CustomerServices customerServices = new CustomerServices();
            var customer = customerServices.GetCustomerByRef(hostId, ticket.CustomerReferenceId);

            if (customer == null)
            {
                status = CancelTicketStatuses.CustomerNotFound;
                return(null);
            }

            //Refund the customer using funds from the event address
            WalletServices walletServices = new WalletServices();

            walletServices.Transfer(eventInfo.WalletAddress, customer.WalletAddress, ticket.PaidPrice.Value, ticket.BookingId, "Refund");

            //Update ticket status to cancelled
            admissionServices.UpdateTicketStatus(ticketId, TicketStatuses.Cancelled);

            //Update ticket availability
            BookingServices bookingServices = new BookingServices();

            bookingServices.UpdateAvailableTickets(eventTier.TierId.Value, eventTier.AvailableTickets.Value + 1);

            //Commit to blockchain
            ContractApi blockchainContract = new ContractApi();

            blockchainContract.CancelTicket(ticket.TicketNo);

            status = CancelEventStatuses.Success;

            result                 = new CancelTicketResponse();
            result.EventTitle      = ticket.EventTitle;
            result.TierTitle       = ticket.TierTitle;
            result.PaidPrice       = ticket.PaidPrice;
            result.NewTicketStatus = TicketStatuses.Cancelled;

            return(result);
        }