@Secured("PAYMENT")
  @RequestMapping(value = "/admin/payments/paymentMethod.html", method = RequestMethod.GET)
  public String displayPaymentMethod(
      @RequestParam("code") String code,
      Model model,
      HttpServletRequest request,
      HttpServletResponse response)
      throws Exception {

    this.setMenu(model, request);
    MerchantStore store = (MerchantStore) request.getAttribute(Constants.ADMIN_STORE);

    // get configured shipping modules
    IntegrationConfiguration configuration = paymentService.getPaymentConfiguration(code, store);
    if (configuration == null) {
      configuration = new IntegrationConfiguration();
      configuration.setEnvironment(
          com.salesmanager.core.constants.Constants.PRODUCTION_ENVIRONMENT);

      Map<String, String> keys = new HashMap<String, String>();
      keys.put("transaction", TransactionType.AUTHORIZECAPTURE.name());

      configuration.setIntegrationKeys(keys);
    }

    configuration.setModuleCode(code);

    List<String> environments = new ArrayList<String>();
    environments.add(com.salesmanager.core.constants.Constants.TEST_ENVIRONMENT);
    environments.add(com.salesmanager.core.constants.Constants.PRODUCTION_ENVIRONMENT);

    model.addAttribute("configuration", configuration);
    model.addAttribute("environments", environments);
    return ControllerConstants.Tiles.Payment.paymentMethod;
  }
  @Override
  public void saveShippingQuoteModuleConfiguration(
      IntegrationConfiguration configuration, MerchantStore store) throws ServiceException {

    // validate entries
    try {

      String moduleCode = configuration.getModuleCode();
      ShippingQuoteModule quoteModule = (ShippingQuoteModule) shippingModules.get(moduleCode);
      if (quoteModule == null) {
        throw new ServiceException("Shipping quote module " + moduleCode + " does not exist");
      }
      quoteModule.validateModuleConfiguration(configuration, store);

    } catch (IntegrationException ie) {
      throw ie;
    }

    try {
      Map<String, IntegrationConfiguration> modules =
          new HashMap<String, IntegrationConfiguration>();
      MerchantConfiguration merchantConfiguration =
          merchantConfigurationService.getMerchantConfiguration(SHIPPING_MODULES, store);
      if (merchantConfiguration != null) {
        if (!StringUtils.isBlank(merchantConfiguration.getValue())) {

          String decrypted = encryption.decrypt(merchantConfiguration.getValue());
          modules = ConfigurationModulesLoader.loadIntegrationConfigurations(decrypted);
        }
      } else {
        merchantConfiguration = new MerchantConfiguration();
        merchantConfiguration.setMerchantStore(store);
        merchantConfiguration.setKey(SHIPPING_MODULES);
      }
      modules.put(configuration.getModuleCode(), configuration);

      String configs = ConfigurationModulesLoader.toJSONString(modules);

      String encrypted = encryption.encrypt(configs);
      merchantConfiguration.setValue(encrypted);
      merchantConfigurationService.saveOrUpdate(merchantConfiguration);

    } catch (Exception e) {
      throw new ServiceException(e);
    }
  }
  @Override
  public void validateModuleConfiguration(
      IntegrationConfiguration integrationConfiguration, MerchantStore store)
      throws IntegrationException {

    List<String> errorFields = null;

    Map<String, String> keys = integrationConfiguration.getIntegrationKeys();

    // validate integrationKeys['address']
    if (keys == null || StringUtils.isBlank(keys.get("address"))) {
      errorFields = new ArrayList<String>();
      errorFields.add("address");
    }

    if (errorFields != null) {
      IntegrationException ex =
          new IntegrationException(IntegrationException.ERROR_VALIDATION_SAVE);
      ex.setErrorFields(errorFields);
      throw ex;
    }

    return;
  }
  @Override
  public ShippingQuote getShippingQuote(
      MerchantStore store, Delivery delivery, List<ShippingProduct> products, Language language)
      throws ServiceException {

    // ShippingConfiguration -> Global configuration of a given store
    // IntegrationConfiguration -> Configuration of a given module
    // IntegrationModule -> The concrete module as defined in integrationmodules.properties

    // delivery without postal code is accepted
    Validate.notNull(store, "MerchantStore must not be null");
    Validate.notNull(delivery, "Delivery must not be null");
    Validate.notEmpty(products, "products must not be empty");
    Validate.notNull(language, "Language must not be null");

    ShippingQuote shippingQuote = new ShippingQuote();
    ShippingQuoteModule shippingQuoteModule = null;

    try {

      if (StringUtils.isBlank(delivery.getPostalCode())) {
        shippingQuote.getWarnings().add("No postal code in delivery address");
        shippingQuote.setShippingReturnCode(ShippingQuote.NO_POSTAL_CODE);
      }

      // get configuration
      ShippingConfiguration shippingConfiguration = getShippingConfiguration(store);
      ShippingType shippingType = ShippingType.INTERNATIONAL;

      /** get shipping origin * */
      ShippingOrigin shippingOrigin = shippingOriginService.getByStore(store);
      if (shippingOrigin == null || !shippingOrigin.isActive()) {
        shippingOrigin = new ShippingOrigin();
        shippingOrigin.setAddress(store.getStoreaddress());
        shippingOrigin.setCity(store.getStorecity());
        shippingOrigin.setCountry(store.getCountry());
        shippingOrigin.setPostalCode(store.getStorepostalcode());
        shippingOrigin.setState(store.getStorestateprovince());
        shippingOrigin.setZone(store.getZone());
      }

      if (shippingConfiguration == null) {
        shippingConfiguration = new ShippingConfiguration();
      }

      if (shippingConfiguration.getShippingType() != null) {
        shippingType = shippingConfiguration.getShippingType();
      }

      // look if customer country code excluded
      Country shipCountry = delivery.getCountry();
      if (shipCountry == null) {
        throw new ServiceException("Delivery country is null");
      }

      // a ship to country is required
      Validate.notNull(shipCountry);
      Validate.notNull(store.getCountry());

      if (shippingType.name().equals(ShippingType.NATIONAL.name())) {
        // customer country must match store country
        if (!shipCountry.getIsoCode().equals(store.getCountry().getIsoCode())) {
          shippingQuote.setShippingReturnCode(
              ShippingQuote.NO_SHIPPING_TO_SELECTED_COUNTRY + " " + shipCountry.getIsoCode());
          return shippingQuote;
        }
      } else if (shippingType.name().equals(ShippingType.INTERNATIONAL.name())) {

        // customer shipping country code must be in accepted list
        List<String> supportedCountries = this.getSupportedCountries(store);
        if (!supportedCountries.contains(shipCountry.getIsoCode())) {
          shippingQuote.setShippingReturnCode(
              ShippingQuote.NO_SHIPPING_TO_SELECTED_COUNTRY + " " + shipCountry.getIsoCode());
          return shippingQuote;
        }
      }

      // must have a shipping module configured
      Map<String, IntegrationConfiguration> modules = this.getShippingModulesConfigured(store);
      if (modules == null) {
        shippingQuote.setShippingReturnCode(ShippingQuote.NO_SHIPPING_MODULE_CONFIGURED);
        return shippingQuote;
      }

      /** uses this module name * */
      String moduleName = null;
      IntegrationConfiguration configuration = null;
      for (String module : modules.keySet()) {
        moduleName = module;
        configuration = modules.get(module);
        // use the first active module
        if (configuration.isActive()) {
          shippingQuoteModule = this.shippingModules.get(module);
          if (shippingQuoteModule instanceof ShippingQuotePrePostProcessModule) {
            shippingQuoteModule = null;
            continue;
          } else {
            break;
          }
        }
      }

      if (shippingQuoteModule == null) {
        shippingQuote.setShippingReturnCode(ShippingQuote.NO_SHIPPING_MODULE_CONFIGURED);
        return shippingQuote;
      }

      /** merchant module configs * */
      List<IntegrationModule> shippingMethods = this.getShippingMethods(store);
      IntegrationModule shippingModule = null;
      for (IntegrationModule mod : shippingMethods) {
        if (mod.getCode().equals(moduleName)) {
          shippingModule = mod;
          break;
        }
      }

      /** general module configs * */
      if (shippingModule == null) {
        shippingQuote.setShippingReturnCode(ShippingQuote.NO_SHIPPING_MODULE_CONFIGURED);
        return shippingQuote;
      }

      // calculate order total
      BigDecimal orderTotal = calculateOrderTotal(products, store);
      List<PackageDetails> packages = this.getPackagesDetails(products, store);

      // free shipping ?
      if (shippingConfiguration.isFreeShippingEnabled()) {
        BigDecimal freeShippingAmount = shippingConfiguration.getOrderTotalFreeShipping();
        if (freeShippingAmount != null) {
          if (orderTotal.doubleValue() > freeShippingAmount.doubleValue()) {
            if (shippingConfiguration.getFreeShippingType() == ShippingType.NATIONAL) {
              if (store.getCountry().getIsoCode().equals(shipCountry.getIsoCode())) {
                shippingQuote.setFreeShipping(true);
                shippingQuote.setFreeShippingAmount(freeShippingAmount);
                return shippingQuote;
              }
            } else { // international all
              shippingQuote.setFreeShipping(true);
              shippingQuote.setFreeShippingAmount(freeShippingAmount);
              return shippingQuote;
            }
          }
        }
      }

      // handling fees
      BigDecimal handlingFees = shippingConfiguration.getHandlingFees();
      if (handlingFees != null) {
        shippingQuote.setHandlingFees(handlingFees);
      }

      // tax basis
      shippingQuote.setApplyTaxOnShipping(shippingConfiguration.isTaxOnShipping());

      Locale locale = languageService.toLocale(language);

      // invoke pre processors
      if (!CollectionUtils.isEmpty(shippingModulePreProcessors)) {
        for (ShippingQuotePrePostProcessModule preProcessor : shippingModulePreProcessors) {
          // System.out.println("Using pre-processor " + preProcessor.getModuleCode());
          preProcessor.prePostProcessShippingQuotes(
              shippingQuote,
              packages,
              orderTotal,
              delivery,
              shippingOrigin,
              store,
              configuration,
              shippingModule,
              shippingConfiguration,
              shippingMethods,
              locale);
          // TODO switch module if required
          if (shippingQuote.getCurrentShippingModule() != null
              && !shippingQuote
                  .getCurrentShippingModule()
                  .getCode()
                  .equals(shippingModule.getCode())) {
            shippingModule = shippingQuote.getCurrentShippingModule();
            moduleName = shippingModule.getCode();
            shippingQuoteModule = this.shippingModules.get(shippingModule.getCode());
            configuration = modules.get(shippingModule.getCode());
          }
        }
      }

      // invoke module
      List<ShippingOption> shippingOptions = null;

      try {
        shippingOptions =
            shippingQuoteModule.getShippingQuotes(
                shippingQuote,
                packages,
                orderTotal,
                delivery,
                shippingOrigin,
                store,
                configuration,
                shippingModule,
                shippingConfiguration,
                locale);
      } catch (Exception e) {
        LOGGER.error("Error while calculating shipping", e);
        merchantLogService.save(
            new MerchantLog(
                store, "Can't process " + shippingModule.getModule() + " -> " + e.getMessage()));
        shippingQuote.setQuoteError(e.getMessage());
        shippingQuote.setShippingReturnCode(ShippingQuote.ERROR);
        return shippingQuote;
      }

      if (shippingOptions == null && !StringUtils.isBlank(delivery.getPostalCode())) {
        shippingQuote.setShippingReturnCode(ShippingQuote.NO_SHIPPING_TO_SELECTED_COUNTRY);
      }

      shippingQuote.setShippingModuleCode(moduleName);

      // filter shipping options
      ShippingOptionPriceType shippingOptionPriceType =
          shippingConfiguration.getShippingOptionPriceType();
      ShippingOption selectedOption = null;

      if (shippingOptions != null) {

        for (ShippingOption option : shippingOptions) {
          if (selectedOption == null) {
            selectedOption = option;
          }
          // set price text
          String priceText = pricingService.getDisplayAmount(option.getOptionPrice(), store);
          option.setOptionPriceText(priceText);
          option.setShippingModuleCode(moduleName);

          if (StringUtils.isBlank(option.getOptionName())) {

            String countryName = delivery.getCountry().getName();
            if (countryName == null) {
              Map<String, Country> deliveryCountries = countryService.getCountriesMap(language);
              Country dCountry =
                  (Country) deliveryCountries.get(delivery.getCountry().getIsoCode());
              if (dCountry != null) {
                countryName = dCountry.getName();
              } else {
                countryName = delivery.getCountry().getIsoCode();
              }
            }
            option.setOptionName(countryName);
          }

          if (shippingOptionPriceType.name().equals(ShippingOptionPriceType.HIGHEST.name())) {

            if (option.getOptionPrice().longValue() > selectedOption.getOptionPrice().longValue()) {
              selectedOption = option;
            }
          }

          if (shippingOptionPriceType.name().equals(ShippingOptionPriceType.LEAST.name())) {

            if (option.getOptionPrice().longValue() < selectedOption.getOptionPrice().longValue()) {
              selectedOption = option;
            }
          }

          if (shippingOptionPriceType.name().equals(ShippingOptionPriceType.ALL.name())) {

            if (option.getOptionPrice().longValue() < selectedOption.getOptionPrice().longValue()) {
              selectedOption = option;
            }
          }
        }

        shippingQuote.setSelectedShippingOption(selectedOption);

        if (selectedOption != null
            && !shippingOptionPriceType.name().equals(ShippingOptionPriceType.ALL.name())) {
          shippingOptions = new ArrayList<ShippingOption>();
          shippingOptions.add(selectedOption);
        }
      }

      /** set final delivery address * */
      shippingQuote.setDeliveryAddress(delivery);

      shippingQuote.setShippingOptions(shippingOptions);

      /** post processors * */
      // invoke pre processors
      if (!CollectionUtils.isEmpty(shippingModulePostProcessors)) {
        for (ShippingQuotePrePostProcessModule postProcessor : shippingModulePostProcessors) {
          // get module info

          // get module configuration
          IntegrationConfiguration integrationConfiguration =
              modules.get(postProcessor.getModuleCode());

          IntegrationModule postProcessModule = null;
          for (IntegrationModule mod : shippingMethods) {
            if (mod.getCode().equals(postProcessor.getModuleCode())) {
              postProcessModule = mod;
              break;
            }
          }

          IntegrationModule module = postProcessModule;
          postProcessor.prePostProcessShippingQuotes(
              shippingQuote,
              packages,
              orderTotal,
              delivery,
              shippingOrigin,
              store,
              integrationConfiguration,
              module,
              shippingConfiguration,
              shippingMethods,
              locale);
        }
      }

    } catch (Exception e) {
      throw new ServiceException(e);
    }

    return shippingQuote;
  }