package com.braintree.facade.impl;

import com.braintree.command.result.BrainTreeCreatePaymentMethodResult;
import com.braintree.command.result.BrainTreePaymentMethodNonceResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeConstants;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.enums.BraintreePageType;
import com.braintree.enums.BraintreeThreeDSecureFlow;
import com.braintree.enums.ExpirationStatus;
import com.braintree.exceptions.BrainTreeCardVerifyException;
import com.braintree.exceptions.BrainTreeDeliveryAddressNotFoundException;
import com.braintree.exceptions.BrainTreePaymentMethodLimitedException;
import com.braintree.exceptions.BrainTreeThreeDSVerificationException;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.hybris.data.BrainTreePaymentInfoData;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.BraintreeCardDetailsData;
import com.braintree.hybris.data.BraintreePaymentMethodNonceData;
import com.braintree.hybris.data.PaymentMethodsLimitationData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeThreeDSecure;
import com.braintree.hybris.data.graphql.commands.response.BraintreeAuthenticationInsight;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.model.BraintreeLocalPaymentMethodsModel;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.payment.local.methods.service.BraintreeLocalPaymentMethodsService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintree.util.BrainTreeUtils;
import com.braintree.util.GenericBuilder;
import com.braintreegateway.WebhookNotification;
import de.hybris.platform.acceleratorfacades.payment.impl.DefaultPaymentFacade;
import de.hybris.platform.braintree.data.BrainTreeWebhookNotificationRequest;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.customer.CustomerEmailResolutionService;
import de.hybris.platform.commerceservices.enums.CustomerType;
import de.hybris.platform.commerceservices.order.CommerceCartService;
import de.hybris.platform.core.model.order.AbstractOrderModel;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.PaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.core.model.user.UserModel;
import de.hybris.platform.order.CartService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.store.BaseStoreModel;
import de.hybris.platform.store.services.BaseStoreService;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import javax.servlet.http.HttpServletRequest;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.Map;
import java.util.HashMap;

import static com.braintree.constants.BraintreeConstants.BRAINTREE_3D_SECURE_ENVIRONMENT_PSD2;
import static com.braintree.constants.BraintreeConstants.BRAINTREE_3D_SECURE_ENVIRONMENT_UNAVAILABLE;
import static com.braintree.constants.BraintreeConstants.PayPalIntent.PAYPAL_INTENT_SALE;
import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS;
import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.FAILED_VERIFICATION_ERROR_MESSAGE;
import static com.braintree.enums.BraintreeThreeDSecureFlow.DISABLED;
import static com.braintree.enums.BraintreeThreeDSecureFlow.AUTO;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

public class DefaultBrainTreePaymentFacade extends DefaultPaymentFacade {

    private static final Logger LOG = Logger.getLogger(DefaultBrainTreePaymentFacade.class);
    private static final String PSD_2 = "PSDTWO";
    private static final String UNAVAILABLE = "UNAVAILABLE";
    private static final String LIMITATION_ERROR = "Failed to create payment method. The maximum number of payment method is reached.";

    private BrainTreeUserFacade brainTreeUserFacade;
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    private BrainTreePaymentService brainTreePaymentService;
    private CartService cartService;
    private BaseStoreService baseStoreService;
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BraintreePaymentInfoService braintreePaymentInfoService;
    private CommerceCartService commerceCartService;
    private ModelService modelService;
    private CustomerEmailResolutionService customerEmailResolutionService;
    private BrainTreeTransactionService brainTreeTransactionService;
    private BrainTreeConfigService brainTreeConfigService;
    private BraintreeLocalPaymentMethodsService braintreeLocalPaymentMethodsService;

    private Converter<BrainTreePaymentInfoModel, BrainTreePaymentInfoData> brainTreePaymentInfoDataConverter;
    private Converter<AddressModel, AddressData> addressConverter;
    private Converter<AddressData, AddressModel> addressReverseConverter;
    private Converter<OrderModel, OrderData> orderConverter;
    private Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter;

    public void createLocalPaymentMethodSubscription(String paymentId) {
        final CartModel cart = cartService.getSessionCart();
        completeCreateLocalPaymentSubscription(paymentId, cart);

    }

    public CCPaymentInfoData updateLocalPaymentMethodSubscription(final String nonce, final String deviceData)
            throws BrainTreeDeliveryAddressNotFoundException {
        validateParameterNotNullStandardMessage("nonce", nonce);
        validateParameterNotNullStandardMessage("deviceData", deviceData);

        PaymentInfoModel paymentInfo = cartService.getSessionCart().getPaymentInfo();
        AddressModel address = cartService.getSessionCart().getDeliveryAddress();
        if (address == null) {
            throw new BrainTreeDeliveryAddressNotFoundException();
        }
        AddressModel billingAddress = modelService.clone(address);

        if (paymentInfo instanceof BrainTreePaymentInfoModel) {
            UserModel user = cartService.getSessionCart().getUser();
            String userUid = cartService.getSessionCart().getUser().getUid();

            if (CustomerType.GUEST.getCode().equalsIgnoreCase(user.getName())) {
                String[] splatUid = userUid.split(Pattern.quote("|"));
                String email = splatUid[splatUid.length - 1];
                ((BrainTreePaymentInfoModel) paymentInfo).setPayer(email);
                billingAddress.setEmail(email);
            } else {
                ((BrainTreePaymentInfoModel) paymentInfo).setPayer(userUid);
            }
            ((BrainTreePaymentInfoModel) paymentInfo).setNonce(nonce);
            ((BrainTreePaymentInfoModel) paymentInfo).setDeviceData(deviceData);
            billingAddress.setOwner(paymentInfo);
            paymentInfo.setBillingAddress(billingAddress);
            modelService.saveAll(paymentInfo, billingAddress);
            return brainTreePaymentInfoConverter.convert((BrainTreePaymentInfoModel) paymentInfo);
        }
        return null;
    }

    private void completeCreateLocalPaymentSubscription(String paymentId, CartModel cart) {
        validateParameterNotNullStandardMessage("cart", cart);

        final BrainTreePaymentInfoModel paymentInfo = modelService.create(BrainTreePaymentInfoModel.class);
        final CustomerModel customerModel = getCurrentUserForCheckout();

        paymentInfo.setUser(customerModel);
        paymentInfo.setCode(customerModel.getUid() + "_" + UUID.randomUUID());
        paymentInfo.setThreeDSecureConfiguration(brainTreeConfigService.is3dSecureConfiguration());
        paymentInfo.setPaymentId(paymentId);
        paymentInfo.setUsePaymentMethodToken(false);
        paymentInfo.setPayPalIntent(PAYPAL_INTENT_SALE);
        paymentInfo.setPaymentProvider(BrainTreePaymentMethod.LOCALPAYMENT.toString());
        paymentInfo.setOwner(cart);
        paymentInfo.setMerchantAccountIdForCurrentSite(
                brainTreeConfigService.getMerchantAccountIdForCurrentSiteAndCurrency());
        paymentInfo.setAdvancedFraudTools(brainTreeConfigService.isAdvancedFraudTools());

        cart.setPaymentInfo(paymentInfo);
        modelService.saveAll(paymentInfo, cart);
    }

    public CCPaymentInfoData completeCreateSubscription(final BrainTreeSubscriptionInfoData subscriptionData) {
        final CustomerModel customer = getCurrentUserForCheckout();
        final CartModel cart = cartService.getSessionCart();
        final BraintreePaymentMethodData brainTreePaymentMethod = brainTreePaymentService.getPaymentDetailsByNonce(subscriptionData.getNonce());

        completeBraintreeSubscriptionInfoData(subscriptionData, brainTreePaymentMethod);

        final AddressData addressData = subscriptionData.getAddressData();
        final AddressModel billingAddress = resolveBillingAddress(addressData, cart, customer,
                subscriptionData);
        if(BrainTreeUtils.isPaymentSupportThreeDS(subscriptionData.getPaymentProvider())) {
            verifyThreeDSecure(brainTreePaymentMethod.getDetails().getThreeDSecure());
        }

        if (brainTreeConfigService.isStoreInVault() && StringUtils.isEmpty(customer.getBraintreeCustomerId())) {
            LOG.debug("... creating customer on the braintree side");
            brainTreePaymentService.createCustomer(customer, billingAddress);
        }

        if (BrainTreeUtils.isUsBankAccountPayment(subscriptionData.getPaymentProvider())) {
            checkOnDuplicateAndVaultingAndVerifyingUsBankAccountPaymentMethod(subscriptionData, customer, billingAddress);
        }

        final BrainTreePaymentInfoModel paymentInfoModel = brainTreeTransactionService
                .createPaymentMethodInfo(billingAddress, customer, subscriptionData);
        cart.setPaymentInfo(paymentInfoModel);
        modelService.save(cart);

        return brainTreePaymentInfoConverter.convert(paymentInfoModel);
    }

    private void completeBraintreeSubscriptionInfoData(BrainTreeSubscriptionInfoData subscriptionData, BraintreePaymentMethodData brainTreePaymentMethod){
        subscriptionData.setLast4(brainTreePaymentMethod.getDetails().getLast4());
        subscriptionData.setPaymentProvider(
                BrainTreeUtils.determinePaymentProvider(brainTreePaymentMethod.getDetails())
                .map(BrainTreePaymentMethod::getCode)
                .orElse(StringUtils.EMPTY));
        subscriptionData.setCardType(brainTreePaymentMethod.getDetails().getBrandCode());
        subscriptionData.setCardNumber(BrainTreeUtils.formatCardNumber(brainTreePaymentMethod.getDetails().getLast4()));
        subscriptionData.setExpirationMonth(brainTreePaymentMethod.getDetails().getExpirationMonth());
        subscriptionData.setExpirationYear(brainTreePaymentMethod.getDetails().getExpirationYear());
        subscriptionData.setImageSource(brainTreePaymentService.getImageSourceForPaymentMethod(brainTreePaymentMethod.getDetails().getBrandCode(),
                BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS));
    }

    private void verifyThreeDSecure(BrainTreeThreeDSecure threeDSDetailsByNonce){
            if(BraintreeThreeDSecureFlow.ALWAYS.equals(brainTreeConfigService.getThreeDSecureFlow())
                    || isThreeDsPossibleForSCAAuto(threeDSDetailsByNonce.getAuthenticationInsight())) {
                Optional.ofNullable(threeDSDetailsByNonce)
                        .map(BrainTreeThreeDSecure::getAuthentication)
                        .filter(x -> x.isLiabilityShifted() || !x.isLiabilityShiftPossible())
                        .orElseThrow(() -> new BrainTreeThreeDSVerificationException("ThreeDSecure verification failed"));
            }
    }

    private boolean isThreeDsPossibleForSCAAuto(BraintreeAuthenticationInsight authenticationInsight){
        return  BraintreeThreeDSecureFlow.AUTO.equals(brainTreeConfigService.getThreeDSecureFlow())
                && (authenticationInsight.getCustomerAuthenticationRegulationEnvironment().equals(PSD_2)
                    || authenticationInsight.getCustomerAuthenticationRegulationEnvironment().equals(UNAVAILABLE));
    }

    public void verifyThreeDSForSelectedPayment(String paymentId, String nonce) {
        BrainTreePaymentInfoModel brainTreePaymentInfo = brainTreeCustomerAccountService.getBrainTreePaymentInfoForCode((CustomerModel) getUserService().getCurrentUser(), paymentId);
        verifyThreeDSForSelectedPayment(brainTreePaymentInfo, nonce);
    }

    public void verifyThreeDSForSelectedPayment(BrainTreePaymentInfoModel paymentInfoModel, String nonce) {
        if (BrainTreeUtils.isSavedPaymentSupportThreeDS(paymentInfoModel.getPaymentProvider())) {
            verifyThreeDSecure(brainTreePaymentService.getPaymentDetailsByNonce(nonce)
                    .getDetails().getThreeDSecure());
        }
    }

    public CCPaymentInfoData completeCreateSubscriptionForMyAccount(
            final BrainTreeSubscriptionInfoData subscriptionData, CustomerModel customer) {
        AddressModel billingAddress = null;
        final BraintreePaymentMethodData brainTreePaymentMethod = brainTreePaymentService.getPaymentDetailsByNonce(subscriptionData.getNonce());

        completeBraintreeSubscriptionInfoData(subscriptionData, brainTreePaymentMethod);

        checkPaymentLimitation(subscriptionData.getPaymentProvider());

        if(BrainTreeUtils.isPaymentSupportThreeDS(subscriptionData.getPaymentProvider())) {
            verifyThreeDSecure(brainTreePaymentMethod.getDetails().getThreeDSecure());
        }

        if (!isBillingAddressOptionalForMyAccountSubscription(subscriptionData)) {
            billingAddress = buildBillingAddress(subscriptionData.getAddressData(), customer);
        }
        final BrainTreeCreatePaymentMethodResult braintreePaymentMethod = brainTreePaymentService
                .createPaymentMethodOnBraintreeOrGetExisting(customer, billingAddress, subscriptionData);
        checkBraintreeResult(braintreePaymentMethod);
        if (BrainTreeUtils.isUsBankAccountPayment(subscriptionData.getPaymentProvider()) && !braintreePaymentMethod.getVerified()) {
            brainTreeUserFacade.removeBTCCPaymentInfo(braintreePaymentMethod.getPaymentMethodToken());
            throw new BrainTreeCardVerifyException(FAILED_VERIFICATION_ERROR_MESSAGE);
        }
        addAdditionalPaymentMethodFields(subscriptionData, braintreePaymentMethod);

        final BrainTreePaymentInfoModel paymentInfoModel = brainTreeTransactionService
                .createPaymentMethodInfo(billingAddress, customer, subscriptionData);
        braintreePaymentInfoService.checkOnDuplicatePaymentMethod(paymentInfoModel, customer, billingAddress);
        return brainTreePaymentInfoConverter.convert(paymentInfoModel);
    }

    private boolean isBillingAddressOptionalForMyAccountSubscription(BrainTreeSubscriptionInfoData subscriptionData) {
        return BrainTreeUtils.isVenmoPayment(subscriptionData.getPaymentProvider())
                || (BrainTreeUtils.isCreditCardPayment(subscriptionData.getPaymentProvider())
                && brainTreeConfigService.isDropInEnabled());
    }

    public CCPaymentInfoData completeCreateSubscriptionForMyAccount(
            final BrainTreeSubscriptionInfoData subscriptionData) {
        final CustomerModel customer = getCurrentUserForCheckout();
        return completeCreateSubscriptionForMyAccount(subscriptionData, customer);
    }

    public CCPaymentInfoData createPaymentInfoForLogin(
            final BrainTreeSubscriptionInfoData subscriptionData,
            final String email) {
        final CustomerModel customer = (CustomerModel) getUserService().getUserForUID(email);
        return completeCreateSubscriptionForMyAccount(subscriptionData, customer);
    }

    public void setPaymentMethodNonce(final CCPaymentInfoData paymentInfoData) {
        final String token = paymentInfoData.getPaymentMethodToken();
        if (token != null) {
            final BrainTreePaymentMethodNonceResult nonceResult = brainTreePaymentService.createPaymentMethodNonce(token);
            paymentInfoData.setPaymentMethodNonce(nonceResult.getNonce());
        }
    }

    public BraintreePaymentMethodNonceData createPaymentMethodNonce(CCPaymentInfoData paymentInfoData) {
        final BrainTreePaymentMethodNonceResult nonceResult = brainTreePaymentService.createPaymentMethodNonce(paymentInfoData.getPaymentMethodToken());
        return GenericBuilder.of(BraintreePaymentMethodNonceData::new)
                .with(BraintreePaymentMethodNonceData::setNonce, nonceResult.getNonce())
                .with(BraintreePaymentMethodNonceData::setDetails,
                        GenericBuilder.of(BraintreeCardDetailsData::new)
                                .with(BraintreeCardDetailsData::setBin, nonceResult.getBin())
                                .build()
                )
                .with(BraintreePaymentMethodNonceData::setShouldPerform3dSecure, decideOnShouldPerform3DSecure(nonceResult, paymentInfoData.getSubscriptionId()))
                .build();
    }

    private boolean decideOnShouldPerform3DSecure(BrainTreePaymentMethodNonceResult nonceResult, String paymentType) {
        if (paymentType.equals(BrainTreePaymentMethod.ANDROIDPAYCARD.getCode())) {
            return getShouldPerform3DSecure();
        }

        if (brainTreeConfigService.getThreeDSecureFlow().equals(AUTO)) {
            return isRegulatedEnvironment(nonceResult.getEnvironment());
        } else {
            return getShouldPerform3DSecure();
        }
    }

    private boolean getShouldPerform3DSecure() {
        return !brainTreeConfigService.getThreeDSecureFlow().equals(DISABLED);
    }

    private boolean isRegulatedEnvironment(String environment) {
        return BRAINTREE_3D_SECURE_ENVIRONMENT_PSD2.equalsIgnoreCase(environment)
                || BRAINTREE_3D_SECURE_ENVIRONMENT_UNAVAILABLE.equalsIgnoreCase(environment);
    }

    public PaymentInfoModel updateLocalPaymentMethodSubscription(final String nonce, final CartModel cart) {
        PaymentInfoModel paymentInfo = cart.getPaymentInfo();
        AddressModel billingAddress = modelService.clone(cart.getDeliveryAddress());
        if (paymentInfo instanceof BrainTreePaymentInfoModel) {
            ((BrainTreePaymentInfoModel) paymentInfo).setNonce(nonce);
            paymentInfo.setBillingAddress(billingAddress);
            billingAddress.setOwner(paymentInfo);
            modelService.saveAll(paymentInfo, cart, billingAddress);
        }
        return paymentInfo;
    }

    public void checkPaymentLimitation(String paymentProvider) {
        Map<String,Boolean> methodLimitationMap = new HashMap<>();
        PaymentMethodsLimitationData limitationData = brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)
                .getPaymentMethodsLimitation();

        methodLimitationMap.put(BrainTreePaymentMethod.PAYPALACCOUNT.getCode(), limitationData.isPayPalOnLimit());
        methodLimitationMap.put(BrainTreePaymentMethod.CREDITCARD.getCode(), limitationData.isCreditCardOnLimit());
        methodLimitationMap.put(BrainTreePaymentMethod.ANDROIDPAYCARD.getCode(), limitationData.isGooglePayOnLimit());
        methodLimitationMap.put(BrainTreePaymentMethod.VENMOACCOUNT.getCode(), limitationData.isVenmoOnLimit());
        methodLimitationMap.put(BrainTreePaymentMethod.VISACHECKOUTCARD.getCode(), limitationData.isSRCOnLimit());
        methodLimitationMap.put(BrainTreePaymentMethod.USBANKACCOUNT.getCode(), limitationData.isSRCOnLimit());
        if(methodLimitationMap.getOrDefault(paymentProvider, false)){
            throw new BrainTreePaymentMethodLimitedException(LIMITATION_ERROR);
        }
    }

    public void setDeviceData(String deviceData, BrainTreePaymentInfoModel paymentInfo) {
        paymentInfo.setDeviceData(deviceData);
        modelService.save(paymentInfo);
    }

    private void checkBraintreeResult(final BrainTreeCreatePaymentMethodResult paymentMethodResult)
            throws AdapterException {
        if (!paymentMethodResult.isSuccess()) {
            throw new AdapterException(paymentMethodResult.getErrorMessage());
        }
    }

    private AddressModel resolveBillingAddress(final AddressData addressBillingData, final AbstractOrderModel cart,
                                               final CustomerModel customer, final BrainTreeSubscriptionInfoData brainTreeSubscriptionInfoData) {
        AddressModel billingAddress = modelService.create(AddressModel.class);

        if (addressBillingData != null) {
            billingAddress = buildBillingAddress(addressBillingData, customer);
        } else {
            // double convert instead of cloning
            final AddressData deliveryAddress = addressConverter.convert(cart.getDeliveryAddress());
            addressReverseConverter.convert(deliveryAddress, billingAddress);

            billingAddress.setBrainTreeAddressId(cart.getDeliveryAddress().getBrainTreeAddressId());
            if (billingAddress.getEmail() == null && brainTreeSubscriptionInfoData.getEmail() != null) {
                billingAddress.setEmail(brainTreeSubscriptionInfoData.getEmail());
            }
        }

        return billingAddress;
    }

    private AddressModel buildBillingAddress(final AddressData addressData, final CustomerModel customer) {
        final AddressModel billingAddress = modelService.create(AddressModel.class);
        addressReverseConverter.convert(addressData, billingAddress);

        billingAddress.setBrainTreeAddressId(addressData.getBrainTreeAddressId());

        if (isNotEmpty(addressData.getEmail()) && !BraintreeConstants.GUEST_USER_TYPE
                .equals(Optional.ofNullable(customer.getType()).orElse(CustomerType.REGISTERED).getCode())) {
            billingAddress.setEmail(addressData.getEmail());
        } else {
            billingAddress.setEmail(customerEmailResolutionService.getEmailForCustomer(customer));
        }

        return billingAddress;
    }

    private void addAdditionalPaymentMethodFields(final BrainTreeSubscriptionInfoData brainTreeSubscriptionInfoData,
                                                  final BrainTreeCreatePaymentMethodResult createPaymentMethodResult) {
        if (createPaymentMethodResult != null) {
            brainTreeSubscriptionInfoData.setPaymentMethodToken(createPaymentMethodResult.getPaymentMethodToken());
            brainTreeSubscriptionInfoData.setExpirationMonth(createPaymentMethodResult.getExpirationMonth());
            brainTreeSubscriptionInfoData.setExpirationYear(createPaymentMethodResult.getExpirationYear());
            brainTreeSubscriptionInfoData.setImageSource(createPaymentMethodResult.getImageSource());
            brainTreeSubscriptionInfoData.setCardNumber(createPaymentMethodResult.getCardNumber());
            brainTreeSubscriptionInfoData.setCardType(createPaymentMethodResult.getCardType());
            brainTreeSubscriptionInfoData.setCardholder(createPaymentMethodResult.getCardholderName());
            brainTreeSubscriptionInfoData.setVerified(createPaymentMethodResult.getVerified());
            if (StringUtils.isNotBlank(createPaymentMethodResult.getEmail())) {
                brainTreeSubscriptionInfoData.setEmail(createPaymentMethodResult.getEmail());
            }
            if (StringUtils.isNotBlank(createPaymentMethodResult.getLast4())) {
                brainTreeSubscriptionInfoData.setLast4(createPaymentMethodResult.getLast4());
            }
            if (StringUtils.isNotBlank(createPaymentMethodResult.getRoutingNumber())) {
                brainTreeSubscriptionInfoData.setRoutingNumber(createPaymentMethodResult.getRoutingNumber());
            }
        }
    }

    private void checkOnDuplicateAndVaultingAndVerifyingUsBankAccountPaymentMethod(
            BrainTreeSubscriptionInfoData subscriptionData, CustomerModel customer, AddressModel billingAddress) {
        final BrainTreeCreatePaymentMethodResult braintreePaymentMethod = brainTreePaymentService
                .createPaymentMethodOnBraintreeOrGetExisting(customer, billingAddress, subscriptionData);
        checkBraintreeResult(braintreePaymentMethod);
        if (!braintreePaymentMethod.getVerified()) {
            brainTreeUserFacade.removeBTCCPaymentInfo(braintreePaymentMethod.getPaymentMethodToken());
            throw new BrainTreeCardVerifyException(FAILED_VERIFICATION_ERROR_MESSAGE);
        }
        addAdditionalPaymentMethodFields(subscriptionData, braintreePaymentMethod);
    }

    public BrainTreePaymentInfoData getBrainTreePaymentInfoData() {
        final PaymentInfoModel paymentInfo = cartService.getSessionCart().getPaymentInfo();
        if (paymentInfo instanceof BrainTreePaymentInfoModel) {

            validateParameterNotNullStandardMessage("paymentInfo", paymentInfo);

            return brainTreePaymentInfoDataConverter
                    .convert((BrainTreePaymentInfoModel) paymentInfo);
        }
        return null;
    }

    public String getExpirationStatusForCustomer() {
        CustomerModel customerModel = (CustomerModel) getUserService().getCurrentUser();
        String brainTreeCustomerId = customerModel.getBraintreeCustomerId();
        ExpirationStatus result = ExpirationStatus.NOT_EXPIRED;
        if (StringUtils.isNotBlank(brainTreeCustomerId)) {
            List<BrainTreePaymentInfoModel> cards = braintreePaymentInfoService.getAllSavedCreditCardByCustomer(brainTreeCustomerId);
            Predicate<ExpirationStatus> isExpirationStatusCart = status -> cards.stream()
                    .anyMatch(card -> card.getExpirationStatus().equals(status));
            if (isExpirationStatusCart.test(ExpirationStatus.EXPIRED)) {
                result = ExpirationStatus.EXPIRED;
            } else if (isExpirationStatusCart.test(ExpirationStatus.EXPIRE_SOON)
                    && customerModel.isCustomerHasNewExpireSoonCard()) {
                customerModel.setCustomerHasNewExpireSoonCard(false);
                modelService.save(customerModel);
                result = ExpirationStatus.EXPIRE_SOON;
            }
        }
        return result.getCode();
    }

    public BrainTreePaymentInfoData getBrainTreePaymentInfoData(final String orderCode) {
        final BaseStoreModel baseStoreModel = baseStoreService.getCurrentBaseStore();
        final OrderModel orderModel = brainTreeCustomerAccountService.getOrderForCode(orderCode, baseStoreModel);
        final PaymentInfoModel paymentInfo = orderModel.getPaymentInfo();
        if (paymentInfo instanceof BrainTreePaymentInfoModel) {

            validateParameterNotNullStandardMessage("paymentInfo", paymentInfo);

            return brainTreePaymentInfoDataConverter.convert((BrainTreePaymentInfoModel) paymentInfo);
        }
        return null;
    }

    public BrainTreePaymentInfoData getBrainTreePaymentInfoDataByCart(final String cartCode) {
        final CartModel cartModel = commerceCartService.getCartForCodeAndUser(cartCode, getUserService().getCurrentUser());
        final BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cartModel.getPaymentInfo();
        validateParameterNotNullStandardMessage("paymentInfo", paymentInfo);
        return brainTreePaymentInfoDataConverter.convert(paymentInfo);
    }

    public void forceUnduplicateCartForReplenishment(final String cartCode) {
        final CartModel cartModel = commerceCartService.getCartForCodeAndUser(cartCode, getUserService().getCurrentUser());
        final BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cartModel.getPaymentInfo();
        if (paymentInfo.getDuplicate() && paymentInfo.isSaved()) {
            paymentInfo.setSaved(false);
        }
        paymentInfo.setDuplicate(false);
        modelService.save(paymentInfo);
    }

    public List<BraintreeLocalPaymentMethodsModel> getLocalPaymentMethods() {
        return braintreeLocalPaymentMethodsService.getAllLocalPaymentMethods();
    }

    public OrderData getOrderByPaymentMethodNonce(String paymentMethodNonce) {
        final OrderData orderData = new OrderData();
        OrderModel orderModel = braintreeLocalPaymentMethodsService.getOrderByPaymentMethodNonce(paymentMethodNonce);
        return (orderModel == null) ?
                null :
                orderConverter
                        .convert(braintreeLocalPaymentMethodsService.getOrderByPaymentMethodNonce(paymentMethodNonce),
                                orderData);
    }

    public OrderModel getOrderByPaymentId(String paymentId) {
        return braintreeLocalPaymentMethodsService.getOrderByPaymentId(paymentId);
    }

    public CartModel getCartByPaymentId(String paymentId) {
        return braintreeLocalPaymentMethodsService.getCartByPaymentId(paymentId);
    }

    public WebhookNotification getWebhookNotification(final HttpServletRequest request) {
        BrainTreeWebhookNotificationRequest webhookNotificationRequest = new BrainTreeWebhookNotificationRequest();
        webhookNotificationRequest.setBtPayload(request.getParameter("bt_payload"));
        webhookNotificationRequest.setBtSignature(request.getParameter("bt_signature"));

        return brainTreePaymentService.getWebhookNotification(webhookNotificationRequest);
    }

    public List<CCPaymentInfoData> getAvailablePayments(List<CCPaymentInfoData> brainTreeCCPaymentInfos) {
        return brainTreeCCPaymentInfos.stream().filter(payment -> payment.getPaymentMethodNonce() != null)
                .collect(Collectors.toList());
    }

    public boolean isBillingAddressEmptyOnPaymentInfo(String paymentInfoCode) {
        BrainTreePaymentInfoModel paymentInfo = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(getCurrentUserForCheckout(), paymentInfoCode);
        return paymentInfo != null && paymentInfo.getBillingAddress() == null;
    }

    public void setBrainTreePaymentService(final BrainTreePaymentService brainTreePaymentService) {
        this.brainTreePaymentService = brainTreePaymentService;
    }

    public void setCartService(final CartService cartService) {
        this.cartService = cartService;
    }

    public void setBaseStoreService(final BaseStoreService baseStoreService) {
        this.baseStoreService = baseStoreService;
    }

    public void setBrainTreeCustomerAccountService(
            final BrainTreeCustomerAccountService brainTreeCustomerAccountService) {
        this.brainTreeCustomerAccountService = brainTreeCustomerAccountService;
    }

    public void setCommerceCartService(final CommerceCartService commerceCartService) {
        this.commerceCartService = commerceCartService;
    }

    public void setModelService(final ModelService modelService) {
        this.modelService = modelService;
    }

    public void setAddressConverter(final Converter<AddressModel, AddressData> addressConverter) {
        this.addressConverter = addressConverter;
    }

    public void setBrainTreeTransactionService(final BrainTreeTransactionService brainTreeTransactionService) {
        this.brainTreeTransactionService = brainTreeTransactionService;
    }

    public void setCustomerEmailResolutionService(final CustomerEmailResolutionService customerEmailResolutionService) {
        this.customerEmailResolutionService = customerEmailResolutionService;
    }

    public void setAddressReverseConverter(final Converter<AddressData, AddressModel> addressReverseConverter) {
        this.addressReverseConverter = addressReverseConverter;
    }

    public void setBrainTreePaymentInfoDataConverter(
            final Converter<BrainTreePaymentInfoModel, BrainTreePaymentInfoData> brainTreePaymentInfoDataConverter) {
        this.brainTreePaymentInfoDataConverter = brainTreePaymentInfoDataConverter;
    }

    public void setBrainTreeCheckoutFacade(DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade) {
        this.brainTreeCheckoutFacade = brainTreeCheckoutFacade;
    }

    public void setBrainTreeConfigService(BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }

    public void setBrainTreeUserFacade(BrainTreeUserFacade brainTreeUserFacade) {
        this.brainTreeUserFacade = brainTreeUserFacade;
    }

    public void setBraintreeLocalPaymentMethodsService(
            BraintreeLocalPaymentMethodsService braintreeLocalPaymentMethodsService) {
        this.braintreeLocalPaymentMethodsService = braintreeLocalPaymentMethodsService;
    }

    public void setOrderConverter(Converter<OrderModel, OrderData> orderConverter) {
        this.orderConverter = orderConverter;
    }

    public void setBrainTreePaymentInfoConverter(
            Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter) {
        this.brainTreePaymentInfoConverter = brainTreePaymentInfoConverter;
    }

    public void setBraintreePaymentInfoService(
            BraintreePaymentInfoService braintreePaymentInfoService) {
        this.braintreePaymentInfoService = braintreePaymentInfoService;
    }

}
