de.hybris.platform.storefront.controllers.pages.checkout.MultiStepCheckoutController.java Source code

Java tutorial

Introduction

Here is the source code for de.hybris.platform.storefront.controllers.pages.checkout.MultiStepCheckoutController.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2013 hybris AG
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 * 
 *  
 */
package de.hybris.platform.storefront.controllers.pages.checkout;

import de.hybris.platform.acceleratorfacades.order.AcceleratorCheckoutFacade;
import de.hybris.platform.acceleratorfacades.payment.PaymentFacade;
import de.hybris.platform.acceleratorfacades.payment.data.PaymentSubscriptionResultData;
import de.hybris.platform.acceleratorservices.customer.CustomerLocationService;
import de.hybris.platform.acceleratorservices.payment.constants.PaymentConstants;
import de.hybris.platform.acceleratorservices.payment.data.PaymentData;
import de.hybris.platform.acceleratorservices.payment.data.PaymentErrorField;
import de.hybris.platform.acceleratorstorefrontcommons.annotations.RequireHardLogIn;
import de.hybris.platform.acceleratorstorefrontcommons.breadcrumb.ResourceBreadcrumbBuilder;
import de.hybris.platform.acceleratorstorefrontcommons.breadcrumb.impl.ContentPageBreadcrumbBuilder;
import de.hybris.platform.acceleratorstorefrontcommons.constants.WebConstants;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.AbstractCheckoutController;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.util.GlobalMessages;
import de.hybris.platform.acceleratorstorefrontcommons.forms.AddressForm;
import de.hybris.platform.acceleratorstorefrontcommons.forms.PaymentDetailsForm;
import de.hybris.platform.acceleratorstorefrontcommons.forms.PlaceOrderForm;
import de.hybris.platform.acceleratorstorefrontcommons.forms.SopPaymentDetailsForm;
import de.hybris.platform.acceleratorstorefrontcommons.forms.validation.AddressValidator;
import de.hybris.platform.acceleratorstorefrontcommons.forms.validation.PaymentDetailsValidator;
import de.hybris.platform.acceleratorstorefrontcommons.forms.verification.AddressVerificationResultHandler;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.cms2.model.pages.ContentPageModel;
import de.hybris.platform.commercefacades.address.data.AddressVerificationResult;
import de.hybris.platform.commercefacades.order.CartFacade;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.CardTypeData;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.order.data.OrderEntryData;
import de.hybris.platform.commercefacades.product.ProductFacade;
import de.hybris.platform.commercefacades.product.ProductOption;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercefacades.user.data.CountryData;
import de.hybris.platform.commercefacades.user.data.RegionData;
import de.hybris.platform.commercefacades.user.data.TitleData;
import de.hybris.platform.commerceservices.address.AddressVerificationDecision;
import de.hybris.platform.commerceservices.order.CommerceCartModificationException;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.storefront.controllers.ControllerConstants;
import de.hybris.platform.util.Config;
import de.hybris.platform.acceleratorservices.enums.CheckoutPciOptionEnum;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * MultiStepCheckoutController
 */
@Controller
@RequestMapping(value = "/checkout/multi")
public class MultiStepCheckoutController extends AbstractCheckoutController {
    private static final Logger LOG = Logger.getLogger(MultiStepCheckoutController.class);

    private static final String MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL = "multiStepCheckoutSummary";
    private static final String REDIRECT_URL_ADD_DELIVERY_ADDRESS = REDIRECT_PREFIX
            + "/checkout/multi/add-delivery-address";
    private static final String REDIRECT_URL_CHOOSE_DELIVERY_METHOD = REDIRECT_PREFIX
            + "/checkout/multi/choose-delivery-method";
    private static final String REDIRECT_URL_ADD_PAYMENT_METHOD = REDIRECT_PREFIX
            + "/checkout/multi/add-payment-method";
    private static final String REDIRECT_URL_CHOOSE_DELIVERY_LOCATION = REDIRECT_PREFIX
            + "/checkout/multi/choose-delivery-location";
    private static final String REDIRECT_URL_SUMMARY = REDIRECT_PREFIX + "/checkout/multi/summary";
    private static final String REDIRECT_URL_CART = REDIRECT_PREFIX + "/cart";
    private static final String REDIRECT_URL_ERROR = REDIRECT_PREFIX + "/checkout/multi/hop-error";
    private static final Map<String, String> cybersourceSopCardTypes = new HashMap<String, String>();

    static {
        // Map hybris card type to Cybersource SOP credit card
        cybersourceSopCardTypes.put("visa", "001");
        cybersourceSopCardTypes.put("master", "002");
        cybersourceSopCardTypes.put("amex", "003");
        cybersourceSopCardTypes.put("diners", "005");
        cybersourceSopCardTypes.put("maestro", "024");
    }

    @Resource(name = "paymentDetailsValidator")
    private PaymentDetailsValidator paymentDetailsValidator;

    @Resource(name = "accProductFacade")
    private ProductFacade productFacade;

    @Resource(name = "multiStepCheckoutBreadcrumbBuilder")
    private ResourceBreadcrumbBuilder resourceBreadcrumbBuilder;

    @Resource(name = "paymentFacade")
    private PaymentFacade paymentFacade;

    @Resource(name = "addressValidator")
    private AddressValidator addressValidator;

    @Resource(name = "customerLocationService")
    private CustomerLocationService customerLocationService;

    @Resource(name = "cartFacade")
    private CartFacade cartFacade;

    @Resource(name = "addressVerificationResultHandler")
    private AddressVerificationResultHandler addressVerificationResultHandler;

    @Resource(name = "contentPageBreadcrumbBuilder")
    private ContentPageBreadcrumbBuilder contentPageBreadcrumbBuilder;

    @Resource(name = "acceleratorCheckoutFacade")
    private AcceleratorCheckoutFacade checkoutFacade;

    @Override
    protected CartFacade getCartFacade() {
        return cartFacade;
    }

    protected ProductFacade getProductFacade() {
        return productFacade;
    }

    protected PaymentDetailsValidator getPaymentDetailsValidator() {
        return paymentDetailsValidator;
    }

    protected ResourceBreadcrumbBuilder getResourceBreadcrumbBuilder() {
        return resourceBreadcrumbBuilder;
    }

    protected PaymentFacade getPaymentFacade() {
        return paymentFacade;
    }

    protected AddressValidator getAddressValidator() {
        return addressValidator;
    }

    protected CustomerLocationService getCustomerLocationService() {
        return customerLocationService;
    }

    protected AddressVerificationResultHandler getAddressVerificationResultHandler() {
        return addressVerificationResultHandler;
    }

    @ModelAttribute("titles")
    public Collection<TitleData> getTitles() {
        return getUserFacade().getTitles();
    }

    @ModelAttribute("countries")
    public Collection<CountryData> getCountries() {
        return getCheckoutFacade().getDeliveryCountries();
    }

    @ModelAttribute("countryDataMap")
    public Map<String, CountryData> getCountryDataMap() {
        final Map<String, CountryData> countryDataMap = new HashMap<String, CountryData>();
        for (final CountryData countryData : getCountries()) {
            countryDataMap.put(countryData.getIsocode(), countryData);
        }
        return countryDataMap;
    }

    @ModelAttribute("billingCountries")
    public Collection<CountryData> getBillingCountries() {
        return getCheckoutFacade().getBillingCountries();
    }

    @ModelAttribute("cardTypes")
    public Collection<CardTypeData> getCardTypes() {
        return getCheckoutFacade().getSupportedCardTypes();
    }

    @ModelAttribute("months")
    public List<SelectOption> getMonths() {
        final List<SelectOption> months = new ArrayList<SelectOption>();

        months.add(new SelectOption("1", "01"));
        months.add(new SelectOption("2", "02"));
        months.add(new SelectOption("3", "03"));
        months.add(new SelectOption("4", "04"));
        months.add(new SelectOption("5", "05"));
        months.add(new SelectOption("6", "06"));
        months.add(new SelectOption("7", "07"));
        months.add(new SelectOption("8", "08"));
        months.add(new SelectOption("9", "09"));
        months.add(new SelectOption("10", "10"));
        months.add(new SelectOption("11", "11"));
        months.add(new SelectOption("12", "12"));

        return months;
    }

    @ModelAttribute("startYears")
    public List<SelectOption> getStartYears() {
        final List<SelectOption> startYears = new ArrayList<SelectOption>();
        final Calendar calender = new GregorianCalendar();

        for (int i = calender.get(Calendar.YEAR); i > (calender.get(Calendar.YEAR) - 6); i--) {
            startYears.add(new SelectOption(String.valueOf(i), String.valueOf(i)));
        }

        return startYears;
    }

    @ModelAttribute("expiryYears")
    public List<SelectOption> getExpiryYears() {
        final List<SelectOption> expiryYears = new ArrayList<SelectOption>();
        final Calendar calender = new GregorianCalendar();

        for (int i = calender.get(Calendar.YEAR); i < (calender.get(Calendar.YEAR) + 11); i++) {
            expiryYears.add(new SelectOption(String.valueOf(i), String.valueOf(i)));
        }

        return expiryYears;
    }

    @ModelAttribute("checkoutSteps")
    public List<CheckoutSteps> addCheckoutStepsToModel(final HttpServletRequest request) {
        final List<CheckoutSteps> checkoutSteps = new ArrayList<CheckoutSteps>();
        checkoutSteps.add(new CheckoutSteps("deliveryAddress", "/checkout/multi/add-delivery-address"));
        checkoutSteps.add(new CheckoutSteps("deliveryMethod", "/checkout/multi/choose-delivery-method"));
        checkoutSteps.add(new CheckoutSteps("paymentMethod", "/checkout/multi/add-payment-method"));
        checkoutSteps.add(new CheckoutSteps("confirmOrder", "/checkout/multi/summary"));

        return checkoutSteps;
    }

    @RequestMapping(method = RequestMethod.GET)
    public String gotoFirstStep() {
        if (hasValidCart()) {
            return (getCheckoutFacade().hasShippingItems()) ? REDIRECT_URL_ADD_DELIVERY_ADDRESS
                    : REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        }
        LOG.info("Missing, empty or unsupported cart");
        return REDIRECT_URL_CART;
    }

    /**
     * This method gets called when the "Use this Address" button is clicked. It sets the selected delivery address on
     * the checkout facade - if it has changed, and reloads the page highlighting the selected delivery address.
     * 
     * @param selectedAddressCode
     *           - the id of the delivery address.
     * @return - a URL to the page to load.
     */
    @RequestMapping(value = "/select-delivery-address", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doSelectDeliveryAddress(@RequestParam("selectedAddressCode") final String selectedAddressCode) {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (!getCheckoutFacade().hasShippingItems()) {
            return REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        }

        if (StringUtils.isNotBlank(selectedAddressCode)) {
            final AddressData selectedAddressData = getCheckoutFacade()
                    .getDeliveryAddressForCode(selectedAddressCode);
            final boolean hasSelectedAddressData = selectedAddressData != null;
            if (hasSelectedAddressData) {
                final AddressData cartCheckoutDeliveryAddress = getCheckoutFacade().getCheckoutCart()
                        .getDeliveryAddress();
                if (isAddressIdChanged(cartCheckoutDeliveryAddress, selectedAddressData)) {
                    getCheckoutFacade().setDeliveryAddress(selectedAddressData);
                    if (cartCheckoutDeliveryAddress != null
                            && !cartCheckoutDeliveryAddress.isVisibleInAddressBook()) { // temporary address should be removed
                        getUserFacade().removeAddress(cartCheckoutDeliveryAddress);
                    }
                }
            }
        }
        return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
    }

    @RequestMapping(value = "/add-delivery-address", method = RequestMethod.GET)
    @RequireHardLogIn
    public String addDeliveryAddress(final Model model) throws CMSItemNotFoundException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (!getCheckoutFacade().hasShippingItems()) {
            return REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        }

        getCheckoutFacade().setDeliveryAddressIfAvailable();

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);
        model.addAttribute("deliveryAddresses", getDeliveryAddresses(cartData.getDeliveryAddress()));
        model.addAttribute("noAddress", Boolean.valueOf(hasNoDeliveryAddress()));
        model.addAttribute("addressForm", new AddressForm());
        model.addAttribute("showSaveToAddressBook", Boolean.TRUE);
        this.prepareDataForPage(model);
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.deliveryAddress.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
    }

    @RequestMapping(value = "/add-delivery-address", method = RequestMethod.POST)
    @RequireHardLogIn
    public String addDeliveryAddress(final AddressForm addressForm, final BindingResult bindingResult,
            final Model model, final RedirectAttributes redirectModel) throws CMSItemNotFoundException {
        getAddressValidator().validate(addressForm, bindingResult);

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);
        model.addAttribute("deliveryAddresses", getDeliveryAddresses(cartData.getDeliveryAddress()));
        this.prepareDataForPage(model);
        if (StringUtils.isNotBlank(addressForm.getCountryIso())) {
            model.addAttribute("regions", getI18NFacade().getRegionsForCountryIso(addressForm.getCountryIso()));
            model.addAttribute("country", addressForm.getCountryIso());
        }

        model.addAttribute("noAddress", Boolean.valueOf(hasNoDeliveryAddress()));
        model.addAttribute("showSaveToAddressBook", Boolean.TRUE);

        if (bindingResult.hasErrors()) {
            GlobalMessages.addErrorMessage(model, "address.error.formentry.invalid");
            storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
        }
        final AddressData newAddress = new AddressData();
        newAddress.setTitleCode(addressForm.getTitleCode());
        newAddress.setFirstName(addressForm.getFirstName());
        newAddress.setLastName(addressForm.getLastName());
        newAddress.setLine1(addressForm.getLine1());
        newAddress.setLine2(addressForm.getLine2());
        newAddress.setTown(addressForm.getTownCity());
        newAddress.setPostalCode(addressForm.getPostcode());
        newAddress.setBillingAddress(false);
        newAddress.setShippingAddress(true);
        if (addressForm.getCountryIso() != null) {
            final CountryData countryData = getI18NFacade().getCountryForIsocode(addressForm.getCountryIso());
            newAddress.setCountry(countryData);
        }
        if (addressForm.getRegionIso() != null && !StringUtils.isEmpty(addressForm.getRegionIso())) {
            final RegionData regionData = getI18NFacade().getRegion(addressForm.getCountryIso(),
                    addressForm.getRegionIso());
            newAddress.setRegion(regionData);
        }

        if (addressForm.getSaveInAddressBook() != null) {
            newAddress.setVisibleInAddressBook(addressForm.getSaveInAddressBook().booleanValue());
            if (addressForm.getSaveInAddressBook().booleanValue() && getUserFacade().isAddressBookEmpty()) {
                newAddress.setDefaultAddress(true);
            }
        } else if (getCheckoutCustomerStrategy().isAnonymousCheckout()) {
            newAddress.setDefaultAddress(true);
            newAddress.setVisibleInAddressBook(true);
        }

        // Verify the address data.
        final AddressVerificationResult<AddressVerificationDecision> verificationResult = getAddressVerificationFacade()
                .verifyAddressData(newAddress);
        final boolean addressRequiresReview = getAddressVerificationResultHandler().handleResult(verificationResult,
                newAddress, model, redirectModel, bindingResult,
                getAddressVerificationFacade().isCustomerAllowedToIgnoreAddressSuggestions(),
                "checkout.multi.address.updated");

        if (addressRequiresReview) {
            storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
        }

        getUserFacade().addAddress(newAddress);

        final AddressData previousSelectedAddress = getCheckoutFacade().getCheckoutCart().getDeliveryAddress();
        // Set the new address as the selected checkout delivery address
        getCheckoutFacade().setDeliveryAddress(newAddress);
        if (previousSelectedAddress != null && !previousSelectedAddress.isVisibleInAddressBook()) { // temporary address should be removed
            getUserFacade().removeAddress(previousSelectedAddress);
        }

        // Set the new address as the selected checkout delivery address
        getCheckoutFacade().setDeliveryAddress(newAddress);

        return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
    }

    @RequestMapping(value = "/remove-address", method = { RequestMethod.GET, RequestMethod.POST })
    @RequireHardLogIn
    public String removeAddress(@RequestParam("addressCode") final String addressCode,
            final RedirectAttributes redirectModel, final Model model) throws CMSItemNotFoundException {
        final AddressData addressData = new AddressData();
        addressData.setId(addressCode);
        getUserFacade().removeAddress(addressData);
        GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.CONF_MESSAGES_HOLDER,
                "account.confirmation.address.removed");
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute("addressForm", new AddressForm());
        return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
    }

    @RequestMapping(value = "/edit-delivery-address", method = RequestMethod.POST)
    @RequireHardLogIn
    public String editDeliveryAddress(final AddressForm addressForm, final BindingResult bindingResult,
            final Model model, final RedirectAttributes redirectModel) throws CMSItemNotFoundException {
        getAddressValidator().validate(addressForm, bindingResult);
        if (StringUtils.isNotBlank(addressForm.getCountryIso())) {
            model.addAttribute("regions", getI18NFacade().getRegionsForCountryIso(addressForm.getCountryIso()));
            model.addAttribute("country", addressForm.getCountryIso());
        }
        model.addAttribute("noAddress", Boolean.valueOf(hasNoDeliveryAddress()));

        if (bindingResult.hasErrors()) {
            GlobalMessages.addErrorMessage(model, "address.error.formentry.invalid");
            storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
        }

        final AddressData newAddress = new AddressData();
        newAddress.setId(addressForm.getAddressId());
        newAddress.setTitleCode(addressForm.getTitleCode());
        newAddress.setFirstName(addressForm.getFirstName());
        newAddress.setLastName(addressForm.getLastName());
        newAddress.setLine1(addressForm.getLine1());
        newAddress.setLine2(addressForm.getLine2());
        newAddress.setTown(addressForm.getTownCity());
        newAddress.setPostalCode(addressForm.getPostcode());
        newAddress.setBillingAddress(false);
        newAddress.setShippingAddress(true);
        if (addressForm.getCountryIso() != null) {
            final CountryData countryData = getI18NFacade().getCountryForIsocode(addressForm.getCountryIso());
            newAddress.setCountry(countryData);
        }
        if (addressForm.getRegionIso() != null && !StringUtils.isEmpty(addressForm.getRegionIso())) {
            final RegionData regionData = getI18NFacade().getRegion(addressForm.getCountryIso(),
                    addressForm.getRegionIso());
            newAddress.setRegion(regionData);
        }

        if (addressForm.getSaveInAddressBook() == null) {
            newAddress.setVisibleInAddressBook(true);
        } else {
            newAddress.setVisibleInAddressBook(Boolean.TRUE.equals(addressForm.getSaveInAddressBook()));
        }

        newAddress.setDefaultAddress(
                getUserFacade().isAddressBookEmpty() || getUserFacade().getAddressBook().size() == 1
                        || Boolean.TRUE.equals(addressForm.getDefaultAddress()));

        // Verify the address data.
        final AddressVerificationResult<AddressVerificationDecision> verificationResult = getAddressVerificationFacade()
                .verifyAddressData(newAddress);
        final boolean addressRequiresReview = getAddressVerificationResultHandler().handleResult(verificationResult,
                newAddress, model, redirectModel, bindingResult,
                getAddressVerificationFacade().isCustomerAllowedToIgnoreAddressSuggestions(),
                "checkout.multi.address.updated");

        if (addressRequiresReview) {
            if (StringUtils.isNotBlank(addressForm.getCountryIso())) {
                model.addAttribute("regions", getI18NFacade().getRegionsForCountryIso(addressForm.getCountryIso()));
                model.addAttribute("country", addressForm.getCountryIso());
            }
            storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
            setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));

            if (StringUtils.isNotEmpty(addressForm.getAddressId())) {
                final AddressData addressData = getCheckoutFacade()
                        .getDeliveryAddressForCode(addressForm.getAddressId());
                if (addressData != null) {
                    model.addAttribute("showSaveToAddressBook",
                            Boolean.valueOf(!addressData.isVisibleInAddressBook()));
                    model.addAttribute("edit", Boolean.TRUE);
                }
            }

            return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
        }

        getUserFacade().editAddress(newAddress);
        getCheckoutFacade().setDeliveryModeIfAvailable();
        getCheckoutFacade().setDeliveryAddress(newAddress);

        return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
    }

    @RequestMapping(value = "/edit-delivery-address", method = RequestMethod.GET)
    @RequireHardLogIn
    public String editDeliveryAddress(@RequestParam("editAddressCode") final String editAddressCode,
            final Model model) throws CMSItemNotFoundException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (!getCheckoutFacade().hasShippingItems()) {
            return REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        }

        AddressData addressData = null;
        if (StringUtils.isNotEmpty(editAddressCode)) {
            addressData = getCheckoutFacade().getDeliveryAddressForCode(editAddressCode);
        }

        final AddressForm addressForm = new AddressForm();
        final boolean hasAddressData = addressData != null;
        if (hasAddressData) {
            addressForm.setAddressId(addressData.getId());
            addressForm.setTitleCode(addressData.getTitleCode());
            addressForm.setFirstName(addressData.getFirstName());
            addressForm.setLastName(addressData.getLastName());
            addressForm.setLine1(addressData.getLine1());
            addressForm.setLine2(addressData.getLine2());
            addressForm.setTownCity(addressData.getTown());
            addressForm.setPostcode(addressData.getPostalCode());
            addressForm.setCountryIso(addressData.getCountry().getIsocode());
            addressForm.setSaveInAddressBook(Boolean.valueOf(addressData.isVisibleInAddressBook()));
            addressForm.setShippingAddress(Boolean.valueOf(addressData.isShippingAddress()));
            addressForm.setBillingAddress(Boolean.valueOf(addressData.isBillingAddress()));
            if (addressData.getRegion() != null && !StringUtils.isEmpty(addressData.getRegion().getIsocode())) {
                addressForm.setRegionIso(addressData.getRegion().getIsocode());
            }
        }

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);
        model.addAttribute("deliveryAddresses", getDeliveryAddresses(cartData.getDeliveryAddress()));
        if (StringUtils.isNotBlank(addressForm.getCountryIso())) {
            model.addAttribute("regions", getI18NFacade().getRegionsForCountryIso(addressForm.getCountryIso()));
            model.addAttribute("country", addressForm.getCountryIso());
        }
        model.addAttribute("noAddress", Boolean.valueOf(hasNoDeliveryAddress()));
        model.addAttribute("edit", Boolean.valueOf(hasAddressData));
        model.addAttribute("addressForm", addressForm);
        if (addressData != null) {
            model.addAttribute("showSaveToAddressBook", Boolean.valueOf(!addressData.isVisibleInAddressBook()));
        }
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.deliveryAddress.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.AddEditDeliveryAddressPage;
    }

    @RequestMapping(value = "/select-suggested-address", method = RequestMethod.POST)
    @RequireHardLogIn
    public String doSelectSuggestedAddress(final AddressForm addressForm, final RedirectAttributes redirectModel) {
        final Set<String> resolveCountryRegions = org.springframework.util.StringUtils
                .commaDelimitedListToSet(Config.getParameter("resolve.country.regions"));

        final AddressData selectedAddress = new AddressData();
        selectedAddress.setId(addressForm.getAddressId());
        selectedAddress.setTitleCode(addressForm.getTitleCode());
        selectedAddress.setFirstName(addressForm.getFirstName());
        selectedAddress.setLastName(addressForm.getLastName());
        selectedAddress.setLine1(addressForm.getLine1());
        selectedAddress.setLine2(addressForm.getLine2());
        selectedAddress.setTown(addressForm.getTownCity());
        selectedAddress.setPostalCode(addressForm.getPostcode());
        selectedAddress.setBillingAddress(false);
        selectedAddress.setShippingAddress(true);
        final CountryData countryData = getI18NFacade().getCountryForIsocode(addressForm.getCountryIso());
        selectedAddress.setCountry(countryData);

        if (resolveCountryRegions.contains(countryData.getIsocode())) {
            if (addressForm.getRegionIso() != null && !StringUtils.isEmpty(addressForm.getRegionIso())) {
                final RegionData regionData = getI18NFacade().getRegion(addressForm.getCountryIso(),
                        addressForm.getRegionIso());
                selectedAddress.setRegion(regionData);
            }
        }

        if (addressForm.getSaveInAddressBook() != null) {
            selectedAddress.setVisibleInAddressBook(addressForm.getSaveInAddressBook().booleanValue());
        }

        if (Boolean.TRUE.equals(addressForm.getEditAddress())) {
            getUserFacade().editAddress(selectedAddress);
        } else {
            getUserFacade().addAddress(selectedAddress);
        }

        final AddressData previousSelectedAddress = getCheckoutFacade().getCheckoutCart().getDeliveryAddress();
        // Set the new address as the selected checkout delivery address
        getCheckoutFacade().setDeliveryAddress(selectedAddress);
        if (previousSelectedAddress != null && !previousSelectedAddress.isVisibleInAddressBook()) { // temporary address should be removed
            getUserFacade().removeAddress(previousSelectedAddress);
        }

        GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.CONF_MESSAGES_HOLDER,
                "checkout.multi.address.added");

        return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
    }

    @RequestMapping(value = "/choose-delivery-method", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doChooseDeliveryModes(final Model model, final RedirectAttributes redirectAttributes)
            throws CMSItemNotFoundException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (!getCheckoutFacade().hasShippingItems()) {
            return REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        }

        if (hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }

        // Try to set default delivery mode
        getCheckoutFacade().setDeliveryModeIfAvailable();

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);
        model.addAttribute("deliveryMethods", getCheckoutFacade().getSupportedDeliveryModes());
        this.prepareDataForPage(model);
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.deliveryMethod.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.ChooseDeliveryMethodPage;
    }

    @RequestMapping(value = "/choose-delivery-location", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doChooseDeliveryLocation(final Model model, final RedirectAttributes redirectAttributes)
            throws CMSItemNotFoundException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }
        if (hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }
        if (hasNoDeliveryMode()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryMethod.notprovided");
            return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }

        // Try to set default delivery mode
        getCheckoutFacade().setDeliveryModeIfAvailable();

        model.addAttribute("cartData", getCheckoutFacade().getCheckoutCart());
        model.addAttribute("pickupConsolidationOptions", getCheckoutFacade().getConsolidatedPickupOptions());
        model.addAttribute("userLocation", getCustomerLocationService().getUserLocation());
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.deliveryMethod.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.ChoosePickupLocationPage;
    }

    @RequestMapping(value = "/select-delivery-location", method = RequestMethod.POST)
    @RequireHardLogIn
    public String doSelectDeliveryLocation(@RequestParam(value = "posName") final String posName, final Model model,
            final RedirectAttributes redirectAttributes)
            throws CMSItemNotFoundException, CommerceCartModificationException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }
        if (hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }
        if (hasNoDeliveryMode()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryMethod.notprovided");
            return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }

        //Consolidate the cart and add unsuccessful modifications to page
        model.addAttribute("validationData", getCheckoutFacade().consolidateCheckoutCart(posName));
        model.addAttribute("cartData", getCheckoutFacade().getCheckoutCart());
        model.addAttribute("userLocation", getCustomerLocationService().getUserLocation());
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.deliveryMethod.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.ChoosePickupLocationPage;
    }

    /**
     * This method gets called when the "Use Selected Delivery Method" button is clicked. It sets the selected delivery
     * mode on the checkout facade and reloads the page highlighting the selected delivery Mode.
     * 
     * @param selectedDeliveryMethod
     *           - the id of the delivery mode.
     * @return - a URL to the page to load.
     */
    @RequestMapping(value = "/select-delivery-method", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doSelectDeliveryMode(@RequestParam("delivery_method") final String selectedDeliveryMethod) {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (StringUtils.isNotEmpty(selectedDeliveryMethod)) {
            getCheckoutFacade().setDeliveryMode(selectedDeliveryMethod);
        }

        if (getCheckoutFacade().hasPickUpItems()) {
            return REDIRECT_URL_CHOOSE_DELIVERY_LOCATION;
        } else {
            return REDIRECT_URL_ADD_PAYMENT_METHOD;
        }
    }

    /**
     * This method gets called when the "Use These Payment Details" button is clicked. It sets the selected payment
     * method on the checkout facade and reloads the page highlighting the selected payment method.
     * 
     * @param selectedPaymentMethodId
     *           - the id of the payment method to use.
     * @return - a URL to the page to load.
     */
    @RequestMapping(value = "/select-payment-method", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doSelectPaymentMethod(
            @RequestParam("selectedPaymentMethodId") final String selectedPaymentMethodId) {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (StringUtils.isNotBlank(selectedPaymentMethodId)) {
            getCheckoutFacade().setPaymentDetails(selectedPaymentMethodId);
        }
        return REDIRECT_URL_SUMMARY;
    }

    @RequestMapping(value = "/add-payment-method", method = RequestMethod.GET)
    @RequireHardLogIn
    public String doAddPaymentMethod(final Model model, final RedirectAttributes redirectAttributes)
            throws CMSItemNotFoundException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }
        if (hasNoDeliveryMode()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryMethod.notprovided");
            return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }

        setupAddPaymentPage(model);

        // Use the checkout PCI strategy for getting the URL for creating new subscriptions.
        final CheckoutPciOptionEnum subscriptionPciOption = getCheckoutFlowFacade().getSubscriptionPciOption();
        if (CheckoutPciOptionEnum.HOP.equals(subscriptionPciOption)) {
            // Redirect the customer to the HOP page or show error message if it fails (e.g. no HOP configurations).
            try {
                final PaymentData hostedOrderPageData = getPaymentFacade().beginHopCreateSubscription(
                        "/checkout/multi/hop-response", "/integration/merchant_callback");
                model.addAttribute("hostedOrderPageData", hostedOrderPageData);

                final boolean hopDebugMode = getSiteConfigService()
                        .getBoolean(PaymentConstants.PaymentProperties.HOP_DEBUG_MODE, false);
                model.addAttribute("hopDebugMode", Boolean.valueOf(hopDebugMode));

                return ControllerConstants.Views.Pages.MultiStepCheckout.HostedOrderPostPage;
            } catch (final Exception e) {
                LOG.error("Failed to build beginCreateSubscription request", e);
                GlobalMessages.addErrorMessage(model,
                        "checkout.multi.paymentMethod.addPaymentDetails.generalError");
            }
        } else if (CheckoutPciOptionEnum.SOP.equals(subscriptionPciOption)) {
            // Build up the SOP form data and render page containing form
            final SopPaymentDetailsForm sopPaymentDetailsForm = new SopPaymentDetailsForm();
            try {
                setupSilentOrderPostPage(sopPaymentDetailsForm, model);
                return ControllerConstants.Views.Pages.MultiStepCheckout.SilentOrderPostPage;
            } catch (final Exception e) {
                LOG.error("Failed to build beginCreateSubscription request", e);
                GlobalMessages.addErrorMessage(model,
                        "checkout.multi.paymentMethod.addPaymentDetails.generalError");
                model.addAttribute("sopPaymentDetailsForm", sopPaymentDetailsForm);
            }
        }

        // If not using HOP or SOP we need to build up the payment details form
        final PaymentDetailsForm paymentDetailsForm = new PaymentDetailsForm();
        final AddressForm addressForm = new AddressForm();
        paymentDetailsForm.setBillingAddress(addressForm);
        model.addAttribute(paymentDetailsForm);

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);
        return ControllerConstants.Views.Pages.MultiStepCheckout.AddPaymentMethodPage;
    }

    protected void setupSilentOrderPostPage(final SopPaymentDetailsForm sopPaymentDetailsForm, final Model model) {
        try {
            final PaymentData silentOrderPageData = getPaymentFacade()
                    .beginSopCreateSubscription("/checkout/multi/sop-response", "/integration/merchant_callback");
            model.addAttribute("silentOrderPageData", silentOrderPageData);
            sopPaymentDetailsForm.setParameters(silentOrderPageData.getParameters());
            model.addAttribute("paymentFormUrl", silentOrderPageData.getPostUrl());
        } catch (final IllegalArgumentException e) {
            model.addAttribute("paymentFormUrl", "");
            model.addAttribute("silentOrderPageData", null);
            LOG.warn("Failed to set up silent order post page " + e.getMessage());
            GlobalMessages.addErrorMessage(model, "checkout.multi.sop.globalError");
        }

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("silentOrderPostForm", new PaymentDetailsForm());
        model.addAttribute("cartData", cartData);
        model.addAttribute("deliveryAddress", cartData.getDeliveryAddress());
        model.addAttribute("sopPaymentDetailsForm", sopPaymentDetailsForm);
        model.addAttribute("paymentInfos", getUserFacade().getCCPaymentInfos(true));
        model.addAttribute("sopCardTypes", getSopCardTypes());
        if (StringUtils.isNotBlank(sopPaymentDetailsForm.getBillTo_country())) {
            model.addAttribute("regions",
                    getI18NFacade().getRegionsForCountryIso(sopPaymentDetailsForm.getBillTo_country()));
            model.addAttribute("country", sopPaymentDetailsForm.getBillTo_country());
        }
    }

    protected void setupAddPaymentPage(final Model model) throws CMSItemNotFoundException {
        model.addAttribute("metaRobots", "no-index,no-follow");
        model.addAttribute("hasNoPaymentInfo", Boolean.valueOf(hasNoPaymentInfo()));
        this.prepareDataForPage(model);
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.paymentMethod.breadcrumb"));
        final ContentPageModel contentPage = getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL);
        storeCmsPageInModel(model, contentPage);
        setUpMetaDataForContentPage(model, contentPage);
    }

    protected Collection<CardTypeData> getSopCardTypes() {
        final Collection<CardTypeData> sopCardTypes = new ArrayList<CardTypeData>();

        final List<CardTypeData> supportedCardTypes = getCheckoutFacade().getSupportedCardTypes();
        for (final CardTypeData supportedCardType : supportedCardTypes) {
            // Add credit cards for all supported cards that have mappings for cybersource SOP
            if (cybersourceSopCardTypes.containsKey(supportedCardType.getCode())) {
                sopCardTypes.add(createCardTypeData(cybersourceSopCardTypes.get(supportedCardType.getCode()),
                        supportedCardType.getName()));
            }
        }

        return sopCardTypes;
    }

    protected CardTypeData createCardTypeData(final String code, final String name) {
        final CardTypeData cardTypeData = new CardTypeData();
        cardTypeData.setCode(code);
        cardTypeData.setName(name);
        return cardTypeData;
    }

    @RequestMapping(value = { "/add-payment-method" }, method = RequestMethod.POST)
    @RequireHardLogIn
    public String doSavePaymentMethod(final Model model, @Valid final PaymentDetailsForm paymentDetailsForm,
            final BindingResult bindingResult) throws CMSItemNotFoundException {
        getPaymentDetailsValidator().validate(paymentDetailsForm, bindingResult);
        setupAddPaymentPage(model);

        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        model.addAttribute("cartData", cartData);

        if (bindingResult.hasErrors()) {
            GlobalMessages.addErrorMessage(model, "checkout.error.paymentethod.formentry.invalid");
            return ControllerConstants.Views.Pages.MultiStepCheckout.AddPaymentMethodPage;
        }

        final CCPaymentInfoData paymentInfoData = new CCPaymentInfoData();
        paymentInfoData.setId(paymentDetailsForm.getPaymentId());
        paymentInfoData.setCardType(paymentDetailsForm.getCardTypeCode());
        paymentInfoData.setAccountHolderName(paymentDetailsForm.getNameOnCard());
        paymentInfoData.setCardNumber(paymentDetailsForm.getCardNumber());
        paymentInfoData.setStartMonth(paymentDetailsForm.getStartMonth());
        paymentInfoData.setStartYear(paymentDetailsForm.getStartYear());
        paymentInfoData.setExpiryMonth(paymentDetailsForm.getExpiryMonth());
        paymentInfoData.setExpiryYear(paymentDetailsForm.getExpiryYear());
        if (Boolean.TRUE.equals(paymentDetailsForm.getSaveInAccount())
                || getCheckoutCustomerStrategy().isAnonymousCheckout()) {
            paymentInfoData.setSaved(true);
        }
        paymentInfoData.setIssueNumber(paymentDetailsForm.getIssueNumber());

        final AddressData addressData;
        if (Boolean.FALSE.equals(paymentDetailsForm.getNewBillingAddress())) {
            addressData = getCheckoutFacade().getCheckoutCart().getDeliveryAddress();
            if (addressData == null) {
                GlobalMessages.addErrorMessage(model,
                        "checkout.multi.paymentMethod.createSubscription.billingAddress.noneSelectedMsg");
                return ControllerConstants.Views.Pages.MultiStepCheckout.AddPaymentMethodPage;
            }

            addressData.setBillingAddress(true); // mark this as billing address
        } else {
            final AddressForm addressForm = paymentDetailsForm.getBillingAddress();
            addressData = new AddressData();
            if (addressForm != null) {
                addressData.setId(addressForm.getAddressId());
                addressData.setTitleCode(addressForm.getTitleCode());
                addressData.setFirstName(addressForm.getFirstName());
                addressData.setLastName(addressForm.getLastName());
                addressData.setLine1(addressForm.getLine1());
                addressData.setLine2(addressForm.getLine2());
                addressData.setTown(addressForm.getTownCity());
                addressData.setPostalCode(addressForm.getPostcode());
                addressData.setCountry(getI18NFacade().getCountryForIsocode(addressForm.getCountryIso()));
                if (addressForm.getRegionIso() != null) {
                    addressData.setRegion(
                            getI18NFacade().getRegion(addressForm.getCountryIso(), addressForm.getRegionIso()));
                }

                addressData.setShippingAddress(Boolean.TRUE.equals(addressForm.getShippingAddress()));
                addressData.setBillingAddress(Boolean.TRUE.equals(addressForm.getBillingAddress()));
            }
        }

        getAddressVerificationFacade().verifyAddressData(addressData);
        paymentInfoData.setBillingAddress(addressData);

        final CCPaymentInfoData newPaymentSubscription = getCheckoutFacade()
                .createPaymentSubscription(paymentInfoData);
        if (newPaymentSubscription != null && StringUtils.isNotBlank(newPaymentSubscription.getSubscriptionId())) {
            if (Boolean.TRUE.equals(paymentDetailsForm.getSaveInAccount())
                    && getUserFacade().getCCPaymentInfos(true).size() <= 1) {
                getUserFacade().setDefaultPaymentInfo(newPaymentSubscription);
            }
            getCheckoutFacade().setPaymentDetails(newPaymentSubscription.getId());
        } else {
            GlobalMessages.addErrorMessage(model, "checkout.multi.paymentMethod.createSubscription.failedMsg");
            return ControllerConstants.Views.Pages.MultiStepCheckout.AddPaymentMethodPage;
        }

        model.addAttribute("paymentId", newPaymentSubscription.getId());

        return REDIRECT_URL_SUMMARY;
    }

    @RequestMapping(value = "/remove-payment-method", method = RequestMethod.POST)
    @RequireHardLogIn
    public String removePaymentMethod(@RequestParam(value = "paymentInfoId") final String paymentMethodId,
            final RedirectAttributes redirectAttributes) throws CMSItemNotFoundException {
        getUserFacade().unlinkCCPaymentInfo(paymentMethodId);
        GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.CONF_MESSAGES_HOLDER,
                "text.account.profile.paymentCart.removed");
        return REDIRECT_URL_ADD_PAYMENT_METHOD;
    }

    @RequestMapping(value = "/hop-response", method = RequestMethod.POST)
    @RequireHardLogIn
    public String doHandleHopResponse(final HttpServletRequest request) {
        final Map<String, String> resultMap = getRequestParameterMap(request);

        final PaymentSubscriptionResultData paymentSubscriptionResultData = getPaymentFacade()
                .completeHopCreateSubscription(resultMap, true);
        if (paymentSubscriptionResultData.isSuccess() && paymentSubscriptionResultData.getStoredCard() != null
                && StringUtils.isNotBlank(paymentSubscriptionResultData.getStoredCard().getSubscriptionId())) {
            final CCPaymentInfoData newPaymentSubscription = paymentSubscriptionResultData.getStoredCard();

            if (getUserFacade().getCCPaymentInfos(true).size() <= 1) {
                getUserFacade().setDefaultPaymentInfo(newPaymentSubscription);
            }
            getCheckoutFacade().setPaymentDetails(newPaymentSubscription.getId());
        } else {
            // HOP ERROR!
            LOG.error("Failed to create subscription.  Please check the log files for more information");
            return REDIRECT_URL_ERROR + "/?decision=" + paymentSubscriptionResultData.getDecision() + "&reasonCode="
                    + paymentSubscriptionResultData.getResultCode();
        }

        return REDIRECT_URL_SUMMARY;
    }

    @RequestMapping(value = "/hop-error", method = RequestMethod.GET)
    public String doHostedOrderPageError(@RequestParam(required = true) final String decision,
            @RequestParam(required = true) final String reasonCode, final Model model,
            final RedirectAttributes redirectAttributes) throws CMSItemNotFoundException {

        String redirectUrl = REDIRECT_URL_ADD_PAYMENT_METHOD;
        if (!hasValidCart()) {
            redirectUrl = REDIRECT_URL_CART;
        }
        if (StringUtils.isBlank(redirectUrl) && hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            redirectUrl = REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }
        if (StringUtils.isBlank(redirectUrl) && hasNoDeliveryMode()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryMethod.notprovided");
            redirectUrl = REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }
        model.addAttribute("decision", decision);
        model.addAttribute("reasonCode", reasonCode);
        model.addAttribute("redirectUrl", redirectUrl.replace(REDIRECT_PREFIX, ""));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.hostedOrderPageError.breadcrumb"));
        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));

        GlobalMessages.addErrorMessage(model, "checkout.multi.hostedOrderPageError.globalError");

        return ControllerConstants.Views.Pages.MultiStepCheckout.HostedOrderPageErrorPage;
    }

    @RequestMapping(value = "/sop-response", method = RequestMethod.POST)
    @RequireHardLogIn
    public String doHandleSopResponse(final HttpServletRequest request,
            @Valid final SopPaymentDetailsForm sopPaymentDetailsForm, final BindingResult bindingResult,
            final Model model, final RedirectAttributes redirectAttributes) throws CMSItemNotFoundException {
        final Map<String, String> resultMap = getRequestParameterMap(request);

        final boolean savePaymentInfo = sopPaymentDetailsForm.isSavePaymentInfo()
                || getCheckoutCustomerStrategy().isAnonymousCheckout();
        final PaymentSubscriptionResultData paymentSubscriptionResultData = this.getPaymentFacade()
                .completeSopCreateSubscription(resultMap, savePaymentInfo);

        if (paymentSubscriptionResultData.isSuccess() && paymentSubscriptionResultData.getStoredCard() != null
                && StringUtils.isNotBlank(paymentSubscriptionResultData.getStoredCard().getSubscriptionId())) {
            final CCPaymentInfoData newPaymentSubscription = paymentSubscriptionResultData.getStoredCard();

            if (getUserFacade().getCCPaymentInfos(true).size() <= 1) {
                getUserFacade().setDefaultPaymentInfo(newPaymentSubscription);
            }
            getCheckoutFacade().setPaymentDetails(newPaymentSubscription.getId());
        } else if ((paymentSubscriptionResultData.getDecision() != null
                && paymentSubscriptionResultData.getDecision().equalsIgnoreCase("error"))
                || (paymentSubscriptionResultData.getErrors() != null
                        && !paymentSubscriptionResultData.getErrors().isEmpty())) {
            // Have SOP errors that we can display

            if (!hasValidCart()) {
                LOG.info("Missing, empty or unsupported cart");
                return REDIRECT_URL_CART;
            }

            setupAddPaymentPage(model);

            // Build up the SOP form data and render page containing form
            try {
                setupSilentOrderPostPage(sopPaymentDetailsForm, model);
            } catch (final Exception e) {
                LOG.error("Failed to build beginCreateSubscription request", e);
                GlobalMessages.addErrorMessage(model,
                        "checkout.multi.paymentMethod.addPaymentDetails.generalError");
                return doAddPaymentMethod(model, redirectAttributes);
            }

            if (paymentSubscriptionResultData.getErrors() != null
                    && !paymentSubscriptionResultData.getErrors().isEmpty()) {
                GlobalMessages.addErrorMessage(model, "checkout.error.paymentethod.formentry.invalid");
                // Add in specific errors for invalid fields
                for (final PaymentErrorField paymentErrorField : paymentSubscriptionResultData.getErrors()
                        .values()) {
                    if (paymentErrorField.isMissing()) {
                        bindingResult.rejectValue(paymentErrorField.getName(),
                                "checkout.error.paymentethod.formentry.sop.missing." + paymentErrorField.getName(),
                                "Please enter a value for this field");
                    }
                    if (paymentErrorField.isInvalid()) {
                        bindingResult.rejectValue(paymentErrorField.getName(),
                                "checkout.error.paymentethod.formentry.sop.invalid." + paymentErrorField.getName(),
                                "This value is invalid for this field");
                    }
                }
            } else if (paymentSubscriptionResultData.getDecision() != null
                    && paymentSubscriptionResultData.getDecision().equalsIgnoreCase("error")) {
                LOG.error(
                        "Failed to create subscription. Error occurred while contacting external payment services.");
                GlobalMessages.addErrorMessage(model,
                        "checkout.multi.paymentMethod.addPaymentDetails.generalError");
            }

            return ControllerConstants.Views.Pages.MultiStepCheckout.SilentOrderPostPage;
        } else {
            // SOP ERROR!
            LOG.error("Failed to create subscription.  Please check the log files for more information");
            return REDIRECT_URL_ERROR + "/?decision=" + paymentSubscriptionResultData.getDecision() + "&reasonCode="
                    + paymentSubscriptionResultData.getResultCode();
        }

        return REDIRECT_URL_SUMMARY;
    }

    protected Map<String, String> getRequestParameterMap(final HttpServletRequest request) {
        final Map<String, String> map = new HashMap<String, String>();

        final Enumeration myEnum = request.getParameterNames();
        while (myEnum.hasMoreElements()) {
            final String paramName = (String) myEnum.nextElement();
            final String paramValue = request.getParameter(paramName);
            map.put(paramName, paramValue);
        }

        return map;
    }

    @RequestMapping(value = "/summary", method = RequestMethod.GET)
    @RequireHardLogIn
    public String checkoutSummary(final Model model, final RedirectAttributes redirectAttributes)
            throws CMSItemNotFoundException, CommerceCartModificationException {
        if (!hasValidCart()) {
            LOG.info("Missing, empty or unsupported cart");
            return REDIRECT_URL_CART;
        }

        if (hasNoDeliveryAddress()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryAddress.notprovided");
            return REDIRECT_URL_ADD_DELIVERY_ADDRESS;
        }
        if (hasNoDeliveryMode()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.deliveryMethod.notprovided");
            return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }
        if (hasNoPaymentInfo()) {
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.INFO_MESSAGES_HOLDER,
                    "checkout.multi.paymentDetails.notprovided");
            return REDIRECT_URL_ADD_PAYMENT_METHOD;
        }

        final CartData cartData = getCheckoutFacade().getCheckoutCart();

        if (!getCheckoutFacade().hasShippingItems()) {
            cartData.setDeliveryAddress(null);
        }
        if (!getCheckoutFacade().hasPickUpItems() && cartData.getDeliveryMode().getCode().equals("pickup")) {
            return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;
        }

        if (cartData.getEntries() != null && !cartData.getEntries().isEmpty()) {
            for (final OrderEntryData entry : cartData.getEntries()) {
                final String productCode = entry.getProduct().getCode();
                final ProductData product = getProductFacade().getProductForCodeAndOptions(productCode,
                        Arrays.asList(ProductOption.BASIC, ProductOption.PRICE));
                entry.setProduct(product);
            }
        }

        model.addAttribute("cartData", cartData);
        model.addAttribute("allItems", cartData.getEntries());
        model.addAttribute("deliveryAddress", cartData.getDeliveryAddress());
        model.addAttribute("deliveryMode", cartData.getDeliveryMode());
        model.addAttribute("paymentInfo", cartData.getPaymentInfo());

        // Only request the security code if the SubscriptionPciOption is set to Default.
        final boolean requestSecurityCode = (CheckoutPciOptionEnum.DEFAULT
                .equals(getCheckoutFlowFacade().getSubscriptionPciOption()));
        model.addAttribute("requestSecurityCode", Boolean.valueOf(requestSecurityCode));

        model.addAttribute(new PlaceOrderForm());

        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.summary.breadcrumb"));
        model.addAttribute("metaRobots", "no-index,no-follow");
        return ControllerConstants.Views.Pages.MultiStepCheckout.CheckoutSummaryPage;
    }

    @RequestMapping(value = "/termsAndConditions")
    @RequireHardLogIn
    public String getTermsAndConditions(final Model model) throws CMSItemNotFoundException {
        final ContentPageModel pageForRequest = getCmsPageService().getPageForLabel("/termsAndConditions");
        storeCmsPageInModel(model, pageForRequest);
        setUpMetaDataForContentPage(model, pageForRequest);
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
                contentPageBreadcrumbBuilder.getBreadcrumbs(pageForRequest));
        return ControllerConstants.Views.Fragments.Checkout.TermsAndConditionsPopup;
    }

    @RequestMapping(value = "/placeOrder")
    @RequireHardLogIn
    public String placeOrder(@ModelAttribute("placeOrderForm") final PlaceOrderForm placeOrderForm,
            final Model model, final HttpServletRequest request, final RedirectAttributes redirectModel)
            throws CMSItemNotFoundException, InvalidCartException, CommerceCartModificationException {
        if (validateOrderForm(placeOrderForm, model)) {
            return checkoutSummary(model, redirectModel);
        }

        //Validate the cart
        if (validateCart(redirectModel)) {
            // Invalid cart. Bounce back to the cart page.
            return REDIRECT_PREFIX + "/cart";
        }

        // authorize, if failure occurs don't allow to place the order
        boolean isPaymentUthorized = false;
        try {
            isPaymentUthorized = getCheckoutFacade().authorizePayment(placeOrderForm.getSecurityCode());
        } catch (final AdapterException ae) {
            // handle a case where a wrong paymentProvider configurations on the store see getCommerceCheckoutService().getPaymentProvider()
            LOG.error(ae.getMessage(), ae);
        }
        if (!isPaymentUthorized) {
            GlobalMessages.addErrorMessage(model, "checkout.error.authorization.failed");
            return checkoutSummary(model, redirectModel);
        }

        final OrderData orderData;
        try {
            orderData = getCheckoutFacade().placeOrder();
        } catch (final Exception e) {
            LOG.error("Failed to place Order", e);
            GlobalMessages.addErrorMessage(model, "checkout.placeOrder.failed");
            return checkoutSummary(model, redirectModel);
        }

        return redirectToOrderConfirmationPage(orderData);
    }

    @RequestMapping(value = "/billingaddressform", method = RequestMethod.GET)
    public String getCountryAddressForm(@RequestParam("countryIsoCode") final String countryIsoCode,
            @RequestParam("useDeliveryAddress") final boolean useDeliveryAddress, final Model model) {
        model.addAttribute("supportedCountries", getCountries());
        model.addAttribute("regions", getI18NFacade().getRegionsForCountryIso(countryIsoCode));
        model.addAttribute("country", countryIsoCode);

        final SopPaymentDetailsForm sopPaymentDetailsForm = new SopPaymentDetailsForm();
        model.addAttribute("sopPaymentDetailsForm", sopPaymentDetailsForm);
        if (useDeliveryAddress) {
            final AddressData deliveryAddress = getCheckoutFacade().getCheckoutCart().getDeliveryAddress();

            if (deliveryAddress.getRegion() != null
                    && !StringUtils.isEmpty(deliveryAddress.getRegion().getIsocode())) {
                sopPaymentDetailsForm.setBillTo_state(deliveryAddress.getRegion().getIsocodeShort());
            }

            sopPaymentDetailsForm.setBillTo_firstName(deliveryAddress.getFirstName());
            sopPaymentDetailsForm.setBillTo_lastName(deliveryAddress.getLastName());
            sopPaymentDetailsForm.setBillTo_street1(deliveryAddress.getLine1());
            sopPaymentDetailsForm.setBillTo_street2(deliveryAddress.getLine2());
            sopPaymentDetailsForm.setBillTo_city(deliveryAddress.getTown());
            sopPaymentDetailsForm.setBillTo_postalCode(deliveryAddress.getPostalCode());
            sopPaymentDetailsForm.setBillTo_country(deliveryAddress.getCountry().getIsocode());
        }
        return ControllerConstants.Views.Fragments.Checkout.BillingAddressForm;
    }

    @RequestMapping(value = "/express", method = RequestMethod.GET)
    @RequireHardLogIn
    public String performExpressCheckout(final RedirectAttributes redirectModel) {
        if (hasValidCart()) {
            switch (getCheckoutFacade().performExpressCheckout()) {
            case SUCCESS:
                return REDIRECT_URL_SUMMARY;

            case ERROR_DELIVERY_ADDRESS:
                GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.ERROR_MESSAGES_HOLDER,
                        "checkout.express.error.deliveryAddress");
                return REDIRECT_URL_ADD_DELIVERY_ADDRESS;

            case ERROR_DELIVERY_MODE:
            case ERROR_CHEAPEST_DELIVERY_MODE:
                GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.ERROR_MESSAGES_HOLDER,
                        "checkout.express.error.deliveryMode");
                return REDIRECT_URL_CHOOSE_DELIVERY_METHOD;

            case ERROR_PAYMENT_INFO:
                GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.ERROR_MESSAGES_HOLDER,
                        "checkout.express.error.paymentInfo");
                return REDIRECT_URL_ADD_PAYMENT_METHOD;

            default:
                GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.ERROR_MESSAGES_HOLDER,
                        "checkout.express.error.notAvailable");
            }
        }

        return gotoFirstStep();
    }

    /**
     * Validates the order form before to filter out invalid order states
     * 
     * @param placeOrderForm
     *           The spring form of the order being submitted
     * @param model
     *           A spring Model
     * @return True if the order form is invalid and false if everything is valid.
     */
    protected boolean validateOrderForm(final PlaceOrderForm placeOrderForm, final Model model) {
        final String securityCode = placeOrderForm.getSecurityCode();
        boolean invalid = false;

        if (hasNoDeliveryAddress()) {
            GlobalMessages.addErrorMessage(model, "checkout.deliveryAddress.notSelected");
            invalid = true;
        }

        if (hasNoDeliveryMode()) {
            GlobalMessages.addErrorMessage(model, "checkout.deliveryMethod.notSelected");
            invalid = true;
        }

        if (hasNoPaymentInfo()) {
            GlobalMessages.addErrorMessage(model, "checkout.paymentMethod.notSelected");
            invalid = true;
        } else {
            // Only require the Security Code to be entered on the summary page if the SubscriptionPciOption is set to Default.
            if (CheckoutPciOptionEnum.DEFAULT.equals(getCheckoutFlowFacade().getSubscriptionPciOption())
                    && StringUtils.isBlank(securityCode)) {
                GlobalMessages.addErrorMessage(model, "checkout.paymentMethod.noSecurityCode");
                invalid = true;
            }
        }

        if (!placeOrderForm.isTermsCheck()) {
            GlobalMessages.addErrorMessage(model, "checkout.error.terms.not.accepted");
            invalid = true;
            return invalid;
        }
        final CartData cartData = getCheckoutFacade().getCheckoutCart();

        if (!getCheckoutFacade().containsTaxValues()) {
            LOG.error(String.format(
                    "Cart %s does not have any tax values, which means the tax cacluation was not properly done, placement of order can't continue",
                    cartData.getCode()));
            GlobalMessages.addErrorMessage(model, "checkout.error.tax.missing");
            invalid = true;
        }

        if (Boolean.FALSE.equals(cartData.isCalculated())) {
            LOG.error(String.format("Cart %s has a calculated flag of FALSE, placement of order can't continue",
                    cartData.getCode()));
            GlobalMessages.addErrorMessage(model, "checkout.error.cart.notcalculated");
            invalid = true;
        }

        return invalid;
    }

    protected void prepareDataForPage(final Model model) throws CMSItemNotFoundException {
        model.addAttribute("isOmsEnabled",
                Boolean.valueOf(getSiteConfigService().getBoolean("oms.enabled", false)));
        model.addAttribute("supportedCountries", cartFacade.getDeliveryCountries());
        model.addAttribute("expressCheckoutAllowed",
                Boolean.valueOf(checkoutFacade.isExpressCheckoutAllowedForCart()));
        model.addAttribute("taxEstimationEnabled", Boolean.valueOf(checkoutFacade.isTaxEstimationEnabledForCart()));
    }

}