public async void BillingInfo_NewAddressInSession_DisplaysBillingInfo()
        {
            WebOrderCheckoutDetails checkoutDetails = new WebOrderCheckoutDetails
            {
                Address = new Address(),
                CountryCode = "CA",
                ProvinceCode = "ON"
            };

            var viewModel = validAddressViewModel;

            Mock<IVeilDataAccess> dbStub = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbStub, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithCountriesSetupForInclude(dbStub, GetCountries());
            SetupVeilDataAccessWithProvincesSetupForInclude(dbStub, new List<Province> { new Province { ProvinceCode = viewModel.ProvinceCode, CountryCode = viewModel.CountryCode } });
            SetupVeilDataAccessWithMember(dbStub, new Member());
            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(checkoutDetails);

            CheckoutController controller = CreateCheckoutController(dbStub.Object, context: contextStub.Object);

            var result = await controller.BillingInfo() as ViewResult;

            Assert.That(result != null);
            Assert.That(result.ViewName, Is.EqualTo(string.Empty).Or.EqualTo("BillingInfo"));
        }
        public async void ConfirmOrder_AddressNotSetInSession_RedirectsToShippingInfo()
        {
            WebOrderCheckoutDetails checkoutDetails = new WebOrderCheckoutDetails();

            Mock<IVeilDataAccess> dbStub = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbStub, GetCartsListContainingCartWithNewAndUsed());
            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(checkoutDetails);

            CheckoutController controller = CreateCheckoutController(dbStub.Object, context: contextStub.Object);

            var result = await controller.ConfirmOrder() as RedirectToRouteResult;

            Assert.That(result != null);
            Assert.That(result.RouteValues["Action"], Is.EqualTo(nameof(CheckoutController.ShippingInfo)));
            Assert.That(result.RouteValues["Controller"], Is.EqualTo(null));
        }
        public async void ExistingShippingInfo_ExistingSession_UpdatesAndReassignsOrderDetails()
        {
            WebOrderCheckoutDetails checkoutDetails = new WebOrderCheckoutDetails
            {
                StripeCardToken = "cardToken"
            };
            WebOrderCheckoutDetails setCheckoutDetails = null;

            Mock<IVeilDataAccess> dbStub = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbStub, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithAddresses(dbStub, GetMemberAddresses());
            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(checkoutDetails);
            contextStub.
                SetupSet(c => c.HttpContext.Session[CheckoutController.OrderCheckoutDetailsKey] = It.IsAny<WebOrderCheckoutDetails>()).
                Callback((string name, object val) => setCheckoutDetails = (WebOrderCheckoutDetails)val);

            CheckoutController controller = CreateCheckoutController(dbStub.Object, context: contextStub.Object);

            await controller.ExistingShippingInfo(addressId);

            Assert.That(setCheckoutDetails != null);
            Assert.That(setCheckoutDetails, Is.SameAs(checkoutDetails));
            Assert.That(setCheckoutDetails.MemberAddressId, Is.EqualTo(addressId));
        }
        public async void ConfirmOrder_CardIsId_GetsCardFromDb()
        {
            WebOrderCheckoutDetails details = new WebOrderCheckoutDetails
            {
                MemberAddressId = addressId,
                MemberCreditCardId = creditCardId
            };

            Member currentMember = member;
            member.CreditCards = new List<MemberCreditCard> { memberCreditCard };

            Mock<IVeilDataAccess> dbMock = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbMock, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithUser(dbMock, memberUser);
            SetupVeilDataAccessWithAddresses(dbMock, GetMemberAddresses());
            Mock<DbSet<Member>> memberDbSet = TestHelpers.GetFakeAsyncDbSet(new List<Member> { currentMember }.AsQueryable());

            dbMock.
                Setup(db => db.Members).
                Returns(memberDbSet.Object).
                Verifiable();

            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(details);
            Mock<IShippingCostService> shippingServiceStub = new Mock<IShippingCostService>();

            CheckoutController controller = CreateCheckoutController(dbMock.Object, context: contextStub.Object, shippingCostService: shippingServiceStub.Object);

            var result = await controller.ConfirmOrder() as ViewResult;

            Assert.That(
                () =>
                    dbMock.Verify(db => db.Members,
                    Times.Once),
                Throws.Nothing);

            Assert.That(result != null);
            Assert.That(result.Model, Is.InstanceOf<ConfirmOrderViewModel>());

            var model = (ConfirmOrderViewModel)result.Model;

            Assert.That(model.CreditCardLast4Digits, Is.EqualTo(memberCreditCard.Last4Digits.FormatLast4Digits()));
        }
        public async void ConfirmOrder_AddressIsId_GetsAddressFromDb()
        {
            WebOrderCheckoutDetails details = new WebOrderCheckoutDetails
            {
                MemberAddressId = addressId,
                MemberCreditCardId = creditCardId
            };

            MemberAddress address = new MemberAddress
            {
                Address = new Address
                {
                    City = "Waterloo",
                    PostalCode = "N2L 6R2",
                    StreetAddress = "445 Wes Graham Way"
                },
                CountryCode = "CA",
                Country = new Country { CountryCode = "CA", CountryName = "Canada", FederalTaxRate = 0.05m },
                ProvinceCode = "ON",
                Province = new Province { CountryCode = "CA", ProvinceCode = "ON", ProvincialTaxRate = 0.08m },
                MemberId = memberId,
                Id = addressId
            };

            List<MemberAddress> addresses = new List<MemberAddress> { address };

            Mock<IVeilDataAccess> dbMock = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbMock, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithMember(dbMock, member);
            SetupVeilDataAccessWithUser(dbMock, memberUser);
            Mock<DbSet<MemberAddress>> addressDbMock = TestHelpers.GetFakeAsyncDbSet(addresses.AsQueryable());
            addressDbMock.SetupForInclude();

            dbMock.
                Setup(db => db.MemberAddresses).
                Returns(addressDbMock.Object).
                Verifiable();

            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(details);
            Mock<IShippingCostService> shippingServiceStub = new Mock<IShippingCostService>();

            CheckoutController controller = CreateCheckoutController(dbMock.Object, context: contextStub.Object, shippingCostService: shippingServiceStub.Object);

            var result = await controller.ConfirmOrder() as ViewResult;

            Assert.That(
                () => 
                    dbMock.Verify(db => db.MemberAddresses,
                    Times.Once),
                Throws.Nothing);

            Assert.That(result != null);
            Assert.That(result.Model, Is.InstanceOf<ConfirmOrderViewModel>());

            var model = (ConfirmOrderViewModel) result.Model;

            Assert.That(model.Address, Is.SameAs(address.Address));
            Assert.That(model.CountryName, Is.SameAs(address.Country.CountryName));
            Assert.That(model.ProvinceName, Is.SameAs(address.Province.Name));
        }
        public async void ConfirmOrder_CardIsIdButIdNotInDb_RedirectsToBillingInfo()
        {
            WebOrderCheckoutDetails details = new WebOrderCheckoutDetails
            {
                MemberAddressId = addressId,
                MemberCreditCardId = creditCardId
            };

            member.CreditCards = new List<MemberCreditCard>();

            Mock<IVeilDataAccess> dbStub = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbStub, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithAddresses(dbStub, GetMemberAddresses());
            SetupVeilDataAccessWithMember(dbStub, member);
            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(details);

            CheckoutController controller = CreateCheckoutController(dbStub.Object, context: contextStub.Object);

            var result = await controller.ConfirmOrder() as RedirectToRouteResult;

            Assert.That(result != null);
            Assert.That(result.RouteValues["Action"], Is.EqualTo(nameof(CheckoutController.BillingInfo)));
            Assert.That(result.RouteValues["Controller"], Is.EqualTo(null));
        }
        /// <summary>
        ///     Gets a new <see cref="MemberAddress"/> populated with the shipping information
        ///     from the <see cref="orderCheckoutDetails"/>
        /// </summary>
        /// <param name="orderCheckoutDetails">
        ///     The <see cref="WebOrderCheckoutDetails"/> to use for retrieving the address information
        /// </param>
        /// <returns>
        ///     A new <see cref="MemberAddress"/> populated with the shipping address information.
        ///     If unsuccessful, null with be returned with an error alert already added
        /// </returns>
        private async Task<MemberAddress> GetShippingAddress(WebOrderCheckoutDetails orderCheckoutDetails)
        {
            MemberAddress memberAddress;

            if (orderCheckoutDetails.MemberAddressId != null)
            {
                memberAddress = await db.MemberAddresses.
                    Include(ma => ma.Province).
                    Include(ma => ma.Country).
                    SingleOrDefaultAsync(ma => ma.Id == orderCheckoutDetails.MemberAddressId);
            }
            else
            {
                memberAddress = new MemberAddress
                {
                    Address = orderCheckoutDetails.Address,
                    ProvinceCode = orderCheckoutDetails.ProvinceCode,
                    CountryCode = orderCheckoutDetails.CountryCode
                };

                memberAddress.Province = await db.Provinces.
                    Include(p => p.Country).
                    FirstOrDefaultAsync(
                        p => p.ProvinceCode == memberAddress.ProvinceCode &&
                            p.CountryCode == memberAddress.CountryCode);

                memberAddress.Country = memberAddress.Province.Country;
            }

            if (memberAddress == null)
            {
                this.AddAlert(AlertType.Error, "The shipping address you selected could not be found.");
            }

            return memberAddress;
        }
        /// <summary>
        ///     Gets the last for digits for the card information in <see cref="orderCheckoutDetails"/>
        /// </summary>
        /// <param name="orderCheckoutDetails">
        ///     The <see cref="WebOrderCheckoutDetails"/> to use for retrieving the last 4 digits
        /// </param>
        /// <param name="memberId">
        ///     The id of the current member. Used if the information in 
        ///     <see cref="orderCheckoutDetails"/> is for a saved <see cref="MemberCreditCard"/>
        /// </param>
        /// <returns>
        ///     The last 4 card digits for the card associated with <see cref="orderCheckoutDetails"/>
        ///     If unsuccessful, null will be returned with an error alert already added.
        /// </returns>
        private async Task<string> GetLast4DigitsAsync(WebOrderCheckoutDetails orderCheckoutDetails,
            Guid memberId)
        {
            string last4Digits;

            if (orderCheckoutDetails.MemberCreditCardId != null)
            {
                last4Digits = await db.Members.
                    Where(m => m.UserId == memberId).
                    SelectMany(m => m.CreditCards).
                    Where(cc => cc.Id == orderCheckoutDetails.MemberCreditCardId.Value).
                    Select(cc => cc.Last4Digits).
                    SingleOrDefaultAsync();
            }
            else
            {
                try
                {
                    last4Digits = stripeService.GetLast4ForToken(orderCheckoutDetails.StripeCardToken);
                }
                catch (StripeServiceException ex)
                {
                    if (ex.ExceptionType == StripeExceptionType.ApiKeyError)
                    {
                        throw new HttpException((int)HttpStatusCode.InternalServerError, ex.Message, ex);
                    }

                    this.AddAlert(AlertType.Error, ex.Message);

                    return null;
                }
            }
            
            if (last4Digits == null)
            {
                this.AddAlert(AlertType.Error, "The billing information you selected could not be found.");
            }

            return last4Digits;
        }
        private RedirectToRouteResult EnsureValidSessionForConfirmStep(
            WebOrderCheckoutDetails checkoutDetails)
        {
            if (checkoutDetails == null ||
                (checkoutDetails.MemberAddressId == null && checkoutDetails.Address == null))
            {
                this.AddAlert(AlertType.Info,
                    "You must provide your shipping information before confirming your order.");

                return RedirectToAction("ShippingInfo");
            }

            if (checkoutDetails.StripeCardToken == null && checkoutDetails.MemberCreditCardId == null)
            {
                this.AddAlert(AlertType.Info,
                    "You must provide your billing information before confirming your order.");

                return RedirectToAction("BillingInfo");
            }

            return null;
        }
        private RedirectToRouteResult EnsureValidSessionForBillingStep(
            WebOrderCheckoutDetails checkoutDetails)
        {
            if (checkoutDetails == null || 
                (checkoutDetails.MemberAddressId == null && checkoutDetails.Address == null))
            {
                this.AddAlert(AlertType.Info,
                    "You must provide your shipping information before providing billing information.");

                return RedirectToAction("ShippingInfo");
            }

            return null;
        }
        /// <summary>
        ///     Gets the Stripe Card Token from the <see cref="orderCheckoutDetails"/> for
        ///     the member identified by <see cref="memberId"/>
        /// </summary>
        /// <param name="orderCheckoutDetails">
        ///     The <see cref="WebOrderCheckoutDetails"/> to use for retrieving the Stripe Card Token
        /// </param>
        /// <param name="memberId">
        ///     The id for the current member
        /// </param>
        /// <returns>
        ///     The Stripe Card Token
        /// </returns>
        private async Task<string> GetStripeCardToken(WebOrderCheckoutDetails orderCheckoutDetails,
            Guid memberId)
        {
            if (orderCheckoutDetails.MemberCreditCardId != null)
            {
                return await db.Members.
                    Where(m => m.UserId == memberId).
                    SelectMany(m => m.CreditCards).
                    Where(cc => cc.Id == orderCheckoutDetails.MemberCreditCardId.Value).
                    Select(cc => cc.StripeCardId).
                    SingleOrDefaultAsync();
            }

            return orderCheckoutDetails.StripeCardToken;
        }
        public void SetupBase()
        {
            memberId = new Guid("59EF92BE-D71F-49ED-992D-DF15773DAF98");
            addressId = new Guid("53BE47E4-0C74-4D49-97BB-7246A7880B39");
            creditCardId = new Guid("D9A69026-E3DA-4748-816B-293D9BE3E43F");
            cartProduct1Id = new Guid("3882D242-A62A-4E99-BA11-D6EF340C2EE8");
            cartProduct2Id = new Guid("7413D131-7337-42DC-A7E4-1155EB91E8C9");

            memberAddress = new MemberAddress
            {
                Address = new Address
                {
                    City = "Waterloo",
                    PostalCode = "N2L 6R2",
                    StreetAddress = "445 Wes Graham Way"
                },
                CountryCode = "CA",
                Country = new Country { CountryCode = "CA", CountryName = "Canada", FederalTaxRate = 0.05m },
                ProvinceCode = "ON",
                Province = new Province { CountryCode = "CA", ProvinceCode = "ON", ProvincialTaxRate = 0.08m },
                MemberId = memberId,
                Id = addressId
            };

            game = new Game
            {
                Name = "A game"
            };

            platform = new Platform
            {
                PlatformCode = "XONE",
                PlatformName = "Xbox One"
            };

            cartProduct1 = new PhysicalGameProduct
            {
                Id = cartProduct1Id,
                NewWebPrice = 60.00m,
                ProductAvailabilityStatus = AvailabilityStatus.Available,
                ReleaseDate = new DateTime(635835582902643008L, DateTimeKind.Local),
                UsedWebPrice = 10.00m,
                Game = game,
                Platform = platform
            };

            cartProduct2 = new PhysicalGameProduct
            {
                Id = cartProduct2Id,
                NewWebPrice = 59.99m,
                ProductAvailabilityStatus = AvailabilityStatus.Available,
                ReleaseDate = new DateTime(635837213100050176L, DateTimeKind.Local),
                Game = game,
                Platform = platform
            };

            newProduct1CartItem = new CartItem
            {
                IsNew = true,
                MemberId = memberId,
                Product = cartProduct1,
                ProductId = cartProduct1.Id,
                Quantity = 1
            };

            usedProduct1CartItem = new CartItem
            {
                IsNew = false,
                MemberId = memberId,
                Product = cartProduct1,
                ProductId = cartProduct1.Id,
                Quantity = 1
            };

            newProduct2CartItem = new CartItem
            {
                IsNew = true,
                MemberId = memberId,
                Product = cartProduct2,
                ProductId = cartProduct2.Id,
                Quantity = 1
            };

            validNotSavedShippingDetails = new WebOrderCheckoutDetails
            {
                Address = new Address
                {
                    City = "Waterloo",
                    PostalCode = "N2L 6R2",
                    POBoxNumber = "123",
                    StreetAddress = "445 Wes Graham Way"
                },
                ProvinceCode = "ON",
                CountryCode = "CA"
            };

            validAddressViewModel = new AddressViewModel
            {
                City = "Waterloo",
                CountryCode = "CA",
                ProvinceCode = "ON",
                POBoxNumber = "1234",
                PostalCode = "N2L 6R2",
                StreetAddress = "445 Wes Graham Way"
            };

            memberCreditCard = new MemberCreditCard
            {
                Id = creditCardId,
                CardholderName = "John Doe",
                ExpiryMonth = 11,
                ExpiryYear = 2015,
                Last4Digits = "4242",
                Member = member,
                MemberId = memberId,
                StripeCardId = "cardToken"
            };

            member = new Member
            {
                UserId = memberId,
                CreditCards = new List<MemberCreditCard>
                {
                    memberCreditCard
                }
            };

            memberUser = new User
            {
                FirstName = "John",
                LastName = "Doe",
                Id = memberId,
                PhoneNumber = "800-555-0199",
            };
            
            validNotSavedShippingBillingDetails = new WebOrderCheckoutDetails
            {
                Address = new Address
                {
                    City = "Waterloo",
                    PostalCode = "N2L 6R2",
                    POBoxNumber = "123",
                    StreetAddress = "445 Wes Graham Way"
                },
                ProvinceCode = "ON",
                CountryCode = "CA",
                StripeCardToken = "card_token"
            };

            validSavedShippingBillingDetails = new WebOrderCheckoutDetails
            {
                MemberCreditCardId = creditCardId,
                MemberAddressId = addressId
            };

            cartWithNewAndUsed = new Cart
            {
                Items = new List<CartItem>
                {
                    newProduct1CartItem,
                    usedProduct1CartItem
                },
                Member = member,
                MemberId = memberId
            };
        }
        protected List<Province> GetProvinceList(WebOrderCheckoutDetails details)
        {
            List<Province> provinces = new List<Province>
            {
                new Province
                {
                    ProvinceCode = details.ProvinceCode,
                    CountryCode = details.CountryCode,
                    ProvincialTaxRate = 0.08m,
                    Country = new Country { CountryCode = "CA", CountryName = "Canada", FederalTaxRate = 0.05m }
                }
            };

            return provinces;
        }
        protected Mock<ControllerContext> GetControllerContextWithSessionSetupToReturn(WebOrderCheckoutDetails returnValue)
        {
            Mock<ControllerContext> contextStub = TestHelpers.GetSetupControllerContextFakeWithUserIdentitySetup();
            contextStub.
                SetupGet(c => c.HttpContext.Session[CheckoutController.OrderCheckoutDetailsKey]).
                Returns(returnValue);

            return contextStub;
        }
        public async void NewShippingInfo_ExistingSession_UpdatesAndReassignsOrderDetails()
        {
            WebOrderCheckoutDetails checkoutDetails = new WebOrderCheckoutDetails
            {
                StripeCardToken = "cardToken"
            };

            WebOrderCheckoutDetails setCheckoutDetails = null;

            var viewModel = validAddressViewModel;

            Mock<IVeilDataAccess> dbStub = TestHelpers.GetVeilDataAccessFake();
            SetupVeilDataAccessWithCarts(dbStub, GetCartsListContainingCartWithNewAndUsed());
            SetupVeilDataAccessWithCountriesSetupForInclude(dbStub, GetCountries());
            SetupVeilDataAccessWithProvincesSetupForInclude(dbStub, GetProvinceList(viewModel));
            Mock<ControllerContext> contextStub = GetControllerContextWithSessionSetupToReturn(checkoutDetails);

            contextStub.
                SetupSet(c => c.HttpContext.Session[CheckoutController.OrderCheckoutDetailsKey] = It.IsAny<WebOrderCheckoutDetails>()).
                Callback((string name, object val) => setCheckoutDetails = (WebOrderCheckoutDetails)val);

            CheckoutController controller = CreateCheckoutController(dbStub.Object, context: contextStub.Object);

            await controller.NewShippingInfo(viewModel, false);

            Assert.That(setCheckoutDetails != null);
            Assert.That(setCheckoutDetails, Is.SameAs(checkoutDetails));
            Assert.That(setCheckoutDetails.StripeCardToken, Is.SameAs(checkoutDetails.StripeCardToken));
            Assert.That(setCheckoutDetails.ProvinceCode, Is.EqualTo(viewModel.ProvinceCode));
        }