@Override
  public void validate(final Object object, final Errors errors) {
    final PaymentDetailsForm form = (PaymentDetailsForm) object;

    final Calendar start = parseDate(form.getStartMonth(), form.getStartYear());
    final Calendar expiration = parseDate(form.getExpiryMonth(), form.getExpiryYear());

    if (start != null && expiration != null && start.after(expiration)) {
      errors.rejectValue("startMonth", "payment.startDate.invalid");
    }

    final boolean editMode = StringUtils.isNotBlank(form.getPaymentId());
    if (editMode || Boolean.TRUE.equals(form.getNewBillingAddress())) {
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.titleCode", "address.title.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.firstName", "address.firstName.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.lastName", "address.lastName.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.line1", "address.line1.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.townCity", "address.townCity.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.postcode", "address.postcode.invalid");
      ValidationUtils.rejectIfEmptyOrWhitespace(
          errors, "billingAddress.countryIso", "address.country.invalid");
      // ValidationUtils.rejectIfEmptyOrWhitespace(errors, "billingAddress.line2",
      // "address.line2.invalid"); // for some addresses this field is required by cybersource
    }
  }
  @RequestMapping(value = "/summary/createUpdatePaymentDetails.json", method = RequestMethod.POST)
  @RequireHardLogIn
  public String createUpdatePaymentDetails(
      final Model model, @Valid final PaymentDetailsForm form, final BindingResult bindingResult) {
    paymentDetailsValidator.validate(form, bindingResult);

    final boolean editMode = StringUtils.isNotBlank(form.getPaymentId());

    if (bindingResult.hasErrors()) {
      model.addAttribute("edit", Boolean.valueOf(editMode));

      return ControllerConstants.Views.Fragments.SingleStepCheckout.PaymentDetailsFormPopup;
    }

    final CCPaymentInfoData paymentInfoData = new CCPaymentInfoData();
    paymentInfoData.setId(form.getPaymentId());
    paymentInfoData.setCardType(form.getCardTypeCode());
    paymentInfoData.setAccountHolderName(form.getNameOnCard());
    paymentInfoData.setCardNumber(form.getCardNumber());
    paymentInfoData.setStartMonth(form.getStartMonth());
    paymentInfoData.setStartYear(form.getStartYear());
    paymentInfoData.setExpiryMonth(form.getExpiryMonth());
    paymentInfoData.setExpiryYear(form.getExpiryYear());
    paymentInfoData.setSaved(Boolean.TRUE.equals(form.getSaveInAccount()));
    paymentInfoData.setIssueNumber(form.getIssueNumber());

    final AddressData addressData;
    if (!editMode && Boolean.FALSE.equals(form.getNewBillingAddress())) {
      addressData = getCheckoutCart().getDeliveryAddress();
      if (addressData == null) {
        GlobalMessages.addErrorMessage(
            model, "checkout.paymentMethod.createSubscription.billingAddress.noneSelected");

        model.addAttribute("edit", Boolean.valueOf(editMode));
        return ControllerConstants.Views.Fragments.SingleStepCheckout.PaymentDetailsFormPopup;
      }

      addressData.setBillingAddress(true); // mark this as billing address
    } else {
      final AddressForm addressForm = form.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()));
        addressData.setShippingAddress(Boolean.TRUE.equals(addressForm.getShippingAddress()));
        addressData.setBillingAddress(Boolean.TRUE.equals(addressForm.getBillingAddress()));
      }
    }

    paymentInfoData.setBillingAddress(addressData);

    final CCPaymentInfoData newPaymentSubscription =
        getCheckoutFacade().createPaymentSubscription(paymentInfoData);
    if (newPaymentSubscription != null
        && StringUtils.isNotBlank(newPaymentSubscription.getSubscriptionId())) {
      if (Boolean.TRUE.equals(form.getSaveInAccount())
          && getUserFacade().getCCPaymentInfos(true).size() <= 1) {
        getUserFacade().setDefaultPaymentInfo(newPaymentSubscription);
      }
      getCheckoutFacade().setPaymentDetails(newPaymentSubscription.getId());
    } else {
      GlobalMessages.addErrorMessage(model, "checkout.paymentMethod.createSubscription.failed");

      model.addAttribute("edit", Boolean.valueOf(editMode));
      return ControllerConstants.Views.Fragments.SingleStepCheckout.PaymentDetailsFormPopup;
    }

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

    return REDIRECT_PREFIX
        + "/checkout/single/summary/getPaymentDetailsForm.json?paymentId="
        + paymentInfoData.getId()
        + "&createUpdateStatus=Success";
  }
  @RequestMapping(
      value = "/summary/getPaymentDetailsForm.json",
      method = {RequestMethod.GET, RequestMethod.POST})
  @RequireHardLogIn
  public String getPaymentDetailsForm(
      final Model model,
      @RequestParam(value = "paymentId") final String paymentId,
      @RequestParam(value = "createUpdateStatus") final String createUpdateStatus) {
    CCPaymentInfoData paymentInfoData = null;
    if (StringUtils.isNotBlank(paymentId)) {
      paymentInfoData = getUserFacade().getCCPaymentInfoForCode(paymentId);
    }

    final PaymentDetailsForm paymentDetailsForm = new PaymentDetailsForm();

    if (paymentInfoData != null) {
      paymentDetailsForm.setPaymentId(paymentInfoData.getId());
      paymentDetailsForm.setCardTypeCode(paymentInfoData.getCardType());
      paymentDetailsForm.setNameOnCard(paymentInfoData.getAccountHolderName());
      paymentDetailsForm.setCardNumber(paymentInfoData.getCardNumber());
      paymentDetailsForm.setStartMonth(paymentInfoData.getStartMonth());
      paymentDetailsForm.setStartYear(paymentInfoData.getStartYear());
      paymentDetailsForm.setExpiryMonth(paymentInfoData.getExpiryMonth());
      paymentDetailsForm.setExpiryYear(paymentInfoData.getExpiryYear());
      paymentDetailsForm.setSaveInAccount(Boolean.valueOf(paymentInfoData.isSaved()));
      paymentDetailsForm.setIssueNumber(paymentInfoData.getIssueNumber());

      final AddressForm addressForm = new AddressForm();
      final AddressData addressData = paymentInfoData.getBillingAddress();
      if (addressData != null) {
        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.setShippingAddress(Boolean.valueOf(addressData.isShippingAddress()));
        addressForm.setBillingAddress(Boolean.valueOf(addressData.isBillingAddress()));
      }

      paymentDetailsForm.setBillingAddress(addressForm);
    }

    model.addAttribute("edit", Boolean.valueOf(paymentInfoData != null));
    model.addAttribute("paymentInfoData", getUserFacade().getCCPaymentInfos(true));
    model.addAttribute(paymentDetailsForm);
    model.addAttribute("createUpdateStatus", createUpdateStatus);
    return ControllerConstants.Views.Fragments.SingleStepCheckout.PaymentDetailsFormPopup;
  }