package com.braintree.facade.impl;

import com.braintree.command.result.BrainTreeCreatePaymentMethodResult;
import com.braintree.command.result.BrainTreePaymentMethodNonceResult;
import com.braintree.command.result.BrainTreePaymentMethodResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeConstants.PayPalIntent;
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.BraintreeCheckoutData;
import com.braintree.hybris.data.PaymentMethodsLimitationData;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.BraintreePaymentMethodNonceData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeAuthentication;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodDetails;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodOrigin;
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.payment.dto.BraintreeInfo;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.payment.local.methods.service.BraintreeLocalPaymentMethodsService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintreegateway.WebhookNotification;
import de.hybris.bootstrap.annotations.UnitTest;
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.commerceservices.strategies.CheckoutCustomerStrategy;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.CreditCardPaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
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.servicelayer.user.UserService;
import de.hybris.platform.store.BaseStoreModel;
import de.hybris.platform.store.services.BaseStoreService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;

import static com.braintree.enums.BrainTreePaymentMethod.ANDROIDPAYCARD;
import static com.braintree.enums.BrainTreePaymentMethod.CREDITCARD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static com.braintree.enums.BraintreeThreeDSecureFlow.AUTO;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class DefaultBrainTreePaymentFacadeTest {

    private static final String LAST4 = "1111";
    private static final String EMPTY_STRING = "";
    private static final String US_BANK_ACCOUNT_DETAILS = "UsBankAccountDetails";
    private static final String EXPIRATION_MONTH = "01";
    private static final String NO_THREE_DS_ENV = "no_three_ds_env";
    private static final String ORDER_CODE = "123456";
    private static final String PAYMENT_ID = "testId";
    private static final String BRAINTREE_CUSTOMER_ID = "testId";
    private static final String NONCE = "nonce";
    private static final String TOKEN = "token";
    private static final String DEVICE_DATA = "deviceData";
    private static final String UID = "12341234";
    private static final String ADDRESS_ID = "addressId";
    private static final String UNREGULATED = "unregulated";
    private static final String EMAIL = "test@email.com";
    private static final String PAY_PAL_ACCOUNT_DETAILS = "PayPalAccountDetails";
    private static final String CREDIT_CARD_DETAILS = "CreditCardDetails";
    private static final String PSD_2 = "PSDTWO";
    private static final String GOOGLE_PAY = "GOOGLE_PAY";
    private static final String VISA = "Visa";
    private static final String EXPIRATION_YEAR = "27";

    @Mock
    private BrainTreePaymentService brainTreePaymentService;

    @Mock
    private CheckoutCustomerStrategy checkoutCustomerStrategy;

    @Mock
    private CartService cartService;

    @Mock
    private BaseStoreService baseStoreService;

    @Mock
    private BraintreeCheckoutData checkoutData;

    @Mock
    private PaymentMethodsLimitationData limitationData;

    @Mock
    private BrainTreeCustomerAccountService customerAccountService;

    @Mock
    private BraintreeLocalPaymentMethodsService braintreeLocalPaymentMethodsService;

    @Mock
    private Converter<BrainTreePaymentInfoModel, BrainTreePaymentInfoData> brainTreePaymentInfoDataConverter;

    @Mock
    private Converter<OrderModel, OrderData> orderConverter;

    @Mock
    private Converter<AddressData, AddressModel> addressReverseConverter;

    @Mock
    private Converter<AddressModel, AddressData> addressConverter;

    @Mock
    private Converter<BrainTreeSubscriptionInfoData, BraintreeInfo> brainTreeSubscriptionInfoConverter;

    @Mock
    private Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Mock
    private ModelService modelService;

    @Mock
    private CustomerEmailResolutionService customerEmailResolutionService;

    @Mock
    private BrainTreeUserFacade brainTreeUserFacade;

    @Mock
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;

    @Mock
    private UserService userService;

    @Mock
    private BraintreePaymentInfoService braintreePaymentInfoService;

    @Mock
    private CommerceCartService commerceCartService;

    @Spy
    @InjectMocks
    private DefaultBrainTreePaymentFacade unit;

    @Mock
    private BrainTreePaymentInfoModel paymentInfo;
    @Mock
    private BrainTreePaymentInfoModel secondPaymentInfo;
    @Mock
    private CreditCardPaymentInfoModel creditCardPaymentInfoModel;
    @Mock
    private CartModel cart;
    @Mock
    private CustomerModel customer;
    @Mock
    private AddressModel address;
    @Mock
    private AddressModel billingAddress;
    @Mock
    private OrderModel order;
    @Mock
    private BaseStoreModel baseStore;
    @Mock
    private WebhookNotification webhookNotification;
    @Mock
    BraintreePaymentMethodData brainTreePaymentMethod;
    @Mock
    BrainTreePaymentMethodDetails brainTreePaymentMethodDetails;
    @Mock
    BrainTreePaymentMethodOrigin paymentMethodOrigin;
    @Mock
    BrainTreeThreeDSecure brainTreeThreeDSecure;
    @Mock
    BraintreeAuthenticationInsight braintreeAuthenticationInsight;
    @Mock
    CCPaymentInfoData paymentInfoData;


    @Test
    public void shouldCreateLocalPaymentMethodSubscription() {
        when(cartService.getSessionCart()).thenReturn(cart);
        when(modelService.create(BrainTreePaymentInfoModel.class)).thenReturn(paymentInfo);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(customer.getUid()).thenReturn(BRAINTREE_CUSTOMER_ID);

        unit.createLocalPaymentMethodSubscription(PAYMENT_ID);

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

    @Test
    public void shouldUpdateLocalPaymentMethodSubscriptionForGuest() throws BrainTreeDeliveryAddressNotFoundException {
        final CCPaymentInfoData ccPaymentInfoData = new CCPaymentInfoData();

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(modelService.clone(address)).thenReturn(address);
        when(cart.getUser()).thenReturn(customer);
        when(customer.getUid()).thenReturn(UID);
        when(customer.getName()).thenReturn(CustomerType.GUEST.getCode());
        when(brainTreePaymentInfoConverter.convert(paymentInfo)).thenReturn(ccPaymentInfoData);

        CCPaymentInfoData ccPaymentInfo = unit.updateLocalPaymentMethodSubscription(NONCE, DEVICE_DATA);

        assertEquals(ccPaymentInfoData, ccPaymentInfo);
        verify(modelService).saveAll(paymentInfo, address);
    }

    @Test
    public void shouldUpdateLocalPaymentMethodSubscriptionForRegisteredUser() throws BrainTreeDeliveryAddressNotFoundException {
        final CCPaymentInfoData ccPaymentInfoData = new CCPaymentInfoData();

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(modelService.clone(address)).thenReturn(address);
        when(cart.getUser()).thenReturn(customer);
        when(customer.getUid()).thenReturn(UID);
        when(customer.getName()).thenReturn(CustomerType.REGISTERED.getCode());
        when(brainTreePaymentInfoConverter.convert(paymentInfo)).thenReturn(ccPaymentInfoData);

        CCPaymentInfoData ccPaymentInfo = unit.updateLocalPaymentMethodSubscription(NONCE, DEVICE_DATA);

        assertEquals(ccPaymentInfoData, ccPaymentInfo);
        verify(modelService).saveAll(paymentInfo, address);
    }

    @Test(expected = BrainTreeDeliveryAddressNotFoundException.class)
    public void shouldUpdateLocalPaymentMethodSubscriptionMustThrowException() throws BrainTreeDeliveryAddressNotFoundException {
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.updateLocalPaymentMethodSubscription(NONCE, DEVICE_DATA);
    }

    @Test
    public void shouldUpdateLocalPaymentMethodSubscription() {
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(modelService.clone(address)).thenReturn(billingAddress);

        unit.updateLocalPaymentMethodSubscription(NONCE, cart);

        verify(modelService).saveAll(paymentInfo, cart, billingAddress);
    }

    @Test
    public void shouldReturnNullForNonBraintreePaymentMethod() throws BrainTreeDeliveryAddressNotFoundException {
        when(cartService.getSessionCart()).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(creditCardPaymentInfoModel);
        when(cart.getDeliveryAddress()).thenReturn(address);

        CCPaymentInfoData result = unit.updateLocalPaymentMethodSubscription(NONCE, DEVICE_DATA);

        assertNull(result);
    }

    @Test
    public void shouldGetBrainTreePaymentInfoData() {
        final BrainTreePaymentInfoData brainTreePaymentInfoDataExpected = new BrainTreePaymentInfoData();

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(brainTreePaymentInfoDataConverter.convert(paymentInfo)).thenReturn(brainTreePaymentInfoDataExpected);
        unit.setBrainTreePaymentInfoDataConverter(brainTreePaymentInfoDataConverter);

        final BrainTreePaymentInfoData brainTreePaymentInfoDataActual = unit
                .getBrainTreePaymentInfoData();

        assertEquals(brainTreePaymentInfoDataExpected, brainTreePaymentInfoDataActual);

    }

    @Test
    public void shouldGetExpirationStatusExpiredForCustomer() {
        when(userService.getCurrentUser()).thenReturn(customer);
        when(customer.getBraintreeCustomerId()).thenReturn(BRAINTREE_CUSTOMER_ID);
        when(braintreePaymentInfoService.getAllSavedCreditCardByCustomer(BRAINTREE_CUSTOMER_ID)).thenReturn(List.of(paymentInfo, secondPaymentInfo));

        when(paymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.EXPIRED);

        assertEquals(ExpirationStatus.EXPIRED.getCode(), unit.getExpirationStatusForCustomer());
    }

    @Test
    public void shouldGetExpirationStatusExpiredSoonForCustomer() {
        when(userService.getCurrentUser()).thenReturn(customer);
        when(customer.getBraintreeCustomerId()).thenReturn(BRAINTREE_CUSTOMER_ID);
        when(braintreePaymentInfoService.getAllSavedCreditCardByCustomer(BRAINTREE_CUSTOMER_ID)).thenReturn(List.of(paymentInfo, secondPaymentInfo));

        when(paymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.EXPIRE_SOON);
        when(secondPaymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.NOT_EXPIRED);
        when(customer.isCustomerHasNewExpireSoonCard()).thenReturn(true);

        assertEquals(ExpirationStatus.EXPIRE_SOON.getCode(), unit.getExpirationStatusForCustomer());
        verify(modelService).save(customer);
    }

    @Test
    public void shouldGetExpirationStatusNotExpiredForCustomer() {
        when(userService.getCurrentUser()).thenReturn(customer);
        when(customer.getBraintreeCustomerId()).thenReturn(BRAINTREE_CUSTOMER_ID);
        when(braintreePaymentInfoService.getAllSavedCreditCardByCustomer(BRAINTREE_CUSTOMER_ID)).thenReturn(List.of(paymentInfo, secondPaymentInfo));

        when(paymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.NOT_EXPIRED);
        when(secondPaymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.NOT_EXPIRED);

        assertEquals(ExpirationStatus.NOT_EXPIRED.getCode(), unit.getExpirationStatusForCustomer());

        // case when one card expired and one expired soon
        when(paymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.EXPIRED);

        assertEquals(ExpirationStatus.EXPIRED.getCode(), unit.getExpirationStatusForCustomer());
    }

    @Test
    public void shouldGetExpirationStatusExpiredForCustomerWhenNotStatusesPresent() {
        when(userService.getCurrentUser()).thenReturn(customer);
        when(customer.getBraintreeCustomerId()).thenReturn(BRAINTREE_CUSTOMER_ID);
        when(braintreePaymentInfoService.getAllSavedCreditCardByCustomer(BRAINTREE_CUSTOMER_ID)).thenReturn(List.of(paymentInfo, secondPaymentInfo));

        when(paymentInfo.getExpirationStatus()).thenReturn(ExpirationStatus.EXPIRED);

        assertEquals(ExpirationStatus.EXPIRED.getCode(), unit.getExpirationStatusForCustomer());
    }

    @Test
    public void shouldGetBrainTreePaymentInfoDataByOrderCode() {
        final BrainTreePaymentInfoData brainTreePaymentInfoDataExpected = new BrainTreePaymentInfoData();

        when(baseStoreService.getCurrentBaseStore()).thenReturn(baseStore);
        when(customerAccountService.getOrderForCode(ORDER_CODE, baseStore)).thenReturn(order);
        when(order.getPaymentInfo()).thenReturn(paymentInfo);
        when(brainTreePaymentInfoDataConverter.convert(paymentInfo)).thenReturn(brainTreePaymentInfoDataExpected);
        unit.setBrainTreePaymentInfoDataConverter(brainTreePaymentInfoDataConverter);

        final BrainTreePaymentInfoData brainTreePaymentInfoDataActual = unit
                .getBrainTreePaymentInfoData(ORDER_CODE);

        assertEquals(brainTreePaymentInfoDataExpected, brainTreePaymentInfoDataActual);
    }

    @Test
    public void shouldGetWebhookNotification() {
        when(brainTreePaymentService.getWebhookNotification(any())).thenReturn(webhookNotification);

        unit.getWebhookNotification(mock(HttpServletRequest.class));

        verify(brainTreePaymentService).getWebhookNotification(any());
    }

    @Test
    public void shouldGetOrderByPaymentId() {
        when(braintreeLocalPaymentMethodsService.getOrderByPaymentId(PAYMENT_ID)).thenReturn(order);

        unit.getOrderByPaymentId(PAYMENT_ID);

        verify(braintreeLocalPaymentMethodsService).getOrderByPaymentId(PAYMENT_ID);
    }

    @Test
    public void shouldCreatePaymentMethodIfOrderIntentAndCreditEnabledAndChangePaymentButtonEnabled() {
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);

        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);

        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreeTransactionService).createPaymentMethodInfo(address, customer, subscriptionInfo);
        verify(modelService).save(cart);
    }

    @Test
    public void shouldNotCreatePaymentMethodIfOrderSaleAndCreditEnabledAndChangePaymentButtonEnabled() {
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setIntent(PayPalIntent.PAYPAL_INTENT_SALE);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);

        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);

        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreePaymentService, never()).createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo);
    }

    @Test
    public void shouldCreatePaymentMethodIfOrderSaleAndCreditNotEnabledAndChangePaymentButtonNotEnabled() {
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setIntent(PayPalIntent.PAYPAL_INTENT_SALE);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);

        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);

        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreeTransactionService).createPaymentMethodInfo(address, customer, subscriptionInfo);
        verify(modelService).save(cart);
    }

    @Test
    public void shouldCreatePaymentMethodForGooglePayWhenVaultTrue() {
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);

        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);

        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreeTransactionService).createPaymentMethodInfo(address, customer, subscriptionInfo);
        verify(modelService).save(cart);
    }

    @Test
    public void shouldCreatePaymentMethodForVenmoWhenVaultFalse() {
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);

        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreeTransactionService).createPaymentMethodInfo(address, customer, subscriptionInfo);
        verify(modelService).save(cart);
    }

    @Test
    public void shouldCreatePaymentMethodForACHTest() {
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(true);
        createPaymentMethod.setVerified(true);
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(US_BANK_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);
        when(brainTreeTransactionService.createPaymentMethodInfo(any(), any(), any())).thenReturn(paymentInfo);

        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo)).thenReturn(createPaymentMethod);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreeTransactionService).createPaymentMethodInfo(address, customer, subscriptionInfo);
        verify(modelService).save(cart);
    }

    @Test(expected = BrainTreeCardVerifyException.class)
    public void shouldThrowBrainTreeCardVerifyExceptionForAch() {
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(true);
        createPaymentMethod.setVerified(false);
        createPaymentMethod.setPaymentMethodToken(TOKEN);
        final AddressData addressData = new AddressData();
        BraintreeInfo braintreeInfo = new BraintreeInfo();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(US_BANK_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);

        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo)).thenReturn(createPaymentMethod);
        doNothing().when(brainTreeUserFacade).removeBTCCPaymentInfo(TOKEN);

        unit.completeCreateSubscription(subscriptionInfo);
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowAdapterExceptionForAch() {
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(false);
        final AddressData addressData = new AddressData();
        final BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(US_BANK_ACCOUNT_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);

        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo)).thenReturn(createPaymentMethod);

        unit.completeCreateSubscription(subscriptionInfo);
    }

    @Test
    public void shouldVerify3DSForCartDuringCompleteCreateSubscription() {
        final AddressData addressData = new AddressData();
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        authentication.setLiabilityShifted(true);
        authentication.setLiabilityShiftPossible(true);
        treeThreeDSecure.setAuthentication(authentication);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EMPTY_STRING);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EMPTY_STRING);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldVerify3DSForGooglePayDuringCompleteCreateSubscription() {
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(false);
        final AddressData addressData = new AddressData();
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        BraintreeAuthenticationInsight authenticationInsight = new BraintreeAuthenticationInsight();
        authenticationInsight.setCustomerAuthenticationRegulationEnvironment(PSD_2);
        authentication.setLiabilityShifted(true);
        authentication.setLiabilityShiftPossible(true);
        treeThreeDSecure.setAuthentication(authentication);
        treeThreeDSecure.setAuthenticationInsight(authenticationInsight);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getOrigin()).thenReturn(paymentMethodOrigin);
        when(paymentMethodOrigin.getType()).thenReturn(GOOGLE_PAY);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.FALSE);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.AUTO);


        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test(expected = BrainTreeThreeDSVerificationException.class)
    public void shouldThrowBrainTreeThreeDSVerificationExceptionWhen3DSVerificationUnsuccessful() {
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(false);
        final AddressData addressData = new AddressData();
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        authentication.setLiabilityShifted(false);
        authentication.setLiabilityShiftPossible(true);
        treeThreeDSecure.setAuthentication(authentication);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldResolveBilliingAddressDuringCompleteCreateSubscription() {
        AddressData deliveryAddress = new AddressData();
        final BrainTreeCreatePaymentMethodResult createPaymentMethod = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethod.setSuccess(false);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setNonce(NONCE);
        subscriptionInfo.setEmail(EMAIL);

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(modelService.create(AddressModel.class)).thenReturn(billingAddress);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(addressConverter.convert(address)).thenReturn(deliveryAddress);
        when(addressReverseConverter.convert(deliveryAddress, billingAddress)).thenReturn(billingAddress);
        when(address.getBrainTreeAddressId()).thenReturn(UID);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.DISABLED);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(brainTreeThreeDSecure);
        when(brainTreeThreeDSecure.getAuthenticationInsight()).thenReturn(braintreeAuthenticationInsight);

        unit.completeCreateSubscription(subscriptionInfo);

        verify(billingAddress).setBrainTreeAddressId(UID);
        verify(billingAddress).setEmail(EMAIL);
    }

    @Test
    public void shouldCompleteCreateSubscriptionForMyAccount() {
        final AddressData addressData = new AddressData();
        addressData.setBrainTreeAddressId(ADDRESS_ID);
        addressData.setEmail(EMAIL);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);
        final BrainTreeCreatePaymentMethodResult createPaymentMethodResult = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethodResult.setSuccess(true);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(customer.getType()).thenReturn(CustomerType.REGISTERED);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo)).thenReturn(createPaymentMethodResult);
        when(brainTreeTransactionService.createPaymentMethodInfo(address, customer, subscriptionInfo)).thenReturn(paymentInfo);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(PAY_PAL_ACCOUNT_DETAILS);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionInfo, customer);

        verify(braintreePaymentInfoService).checkOnDuplicatePaymentMethod(paymentInfo, customer, address);
    }

    @Test(expected = BrainTreeCardVerifyException.class)
    public void shouldThrowBrainTreeCardVerifyExceptionOnMyAccount() {
        final AddressData addressData = new AddressData();
        addressData.setBrainTreeAddressId(ADDRESS_ID);
        addressData.setEmail(EMAIL);
        final BrainTreeSubscriptionInfoData subscriptionInfo = new BrainTreeSubscriptionInfoData();
        subscriptionInfo.setAddressData(addressData);
        subscriptionInfo.setNonce(NONCE);
        final BrainTreeCreatePaymentMethodResult createPaymentMethodResult = new BrainTreeCreatePaymentMethodResult();
        createPaymentMethodResult.setSuccess(true);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(customer.getType()).thenReturn(CustomerType.REGISTERED);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionInfo)).thenReturn(createPaymentMethodResult);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(US_BANK_ACCOUNT_DETAILS);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionInfo, customer);
    }

    @Test
    public void shouldCompleteCreateSubscriptionForMyAccountWithOneParameter() {
        final BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        final CCPaymentInfoData ccPaymentInfoData = new CCPaymentInfoData();

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        doReturn(ccPaymentInfoData).when(unit).completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData);

        verify(checkoutCustomerStrategy).getCurrentUserForCheckout();
        verify(unit).completeCreateSubscriptionForMyAccount(subscriptionData, customer);
    }

    @Test
    public void shouldCreatePaymentInfoForLogin() {
        final BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        final CCPaymentInfoData ccPaymentInfoData = new CCPaymentInfoData();

        when(userService.getUserForUID(EMAIL)).thenReturn(customer);
        doReturn(ccPaymentInfoData).when(unit).completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        unit.createPaymentInfoForLogin(subscriptionData, EMAIL);

        verify(userService).getUserForUID(EMAIL);
        verify(unit).completeCreateSubscriptionForMyAccount(subscriptionData, customer);
    }

    @Test
    public void shouldVerify3DSInCompleteCreateSubscriptionForMyAccount() {
        AddressData addressData = new AddressData();
        BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        subscriptionData.setAddressData(addressData);
        subscriptionData.setNonce(NONCE);
        BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreeCreatePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        authentication.setLiabilityShiftPossible(true);
        authentication.setLiabilityShifted(true);
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        treeThreeDSecure.setAuthentication(authentication);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionData))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldVerify3DSForGooglePayInCompleteCreateSubscriptionForMyAccount() {
        AddressData addressData = new AddressData();
        BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        subscriptionData.setAddressData(addressData);
        subscriptionData.setNonce(NONCE);
        BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreeCreatePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        authentication.setLiabilityShifted(true);
        authentication.setLiabilityShiftPossible(true);
        treeThreeDSecure.setAuthentication(authentication);


        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getOrigin()).thenReturn(paymentMethodOrigin);
        when(paymentMethodOrigin.getType()).thenReturn(GOOGLE_PAY);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionData))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldNotVerify3DSInCompleteCreateSubscriptionForMyAccount() {
        AddressData addressData = new AddressData();
        BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        subscriptionData.setAddressData(addressData);
        subscriptionData.setNonce(NONCE);
        BrainTreeCreatePaymentMethodResult brainTreeCreatePaymentMethodResult = new BrainTreeCreatePaymentMethodResult();
        brainTreeCreatePaymentMethodResult.setSuccess(true);

        when(modelService.create(AddressModel.class)).thenReturn(address);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.AUTO);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(brainTreeThreeDSecure);
        when(brainTreeThreeDSecure.getAuthenticationInsight()).thenReturn(braintreeAuthenticationInsight);
        when(braintreeAuthenticationInsight.getCustomerAuthenticationRegulationEnvironment()).thenReturn(NO_THREE_DS_ENV);
        when(brainTreePaymentService.createPaymentMethodOnBraintreeOrGetExisting(customer, address, subscriptionData))
                .thenReturn(brainTreeCreatePaymentMethodResult);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        verify(brainTreeThreeDSecure, never()).getAuthentication();
    }

    @Test(expected = BrainTreePaymentMethodLimitedException.class)
    public void shouldThrowPaymentMethodLimitedExceptionWhenPaymentIsReachedMaximum() {
        AddressData addressData = new AddressData();
        BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        subscriptionData.setAddressData(addressData);
        subscriptionData.setNonce(NONCE);

        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);
        when(limitationData.isCreditCardOnLimit()).thenReturn(true);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData, customer);
    }

    @Test(expected = BrainTreeThreeDSVerificationException.class)
    public void shouldThrowExceptionOnVerify3DSInCompleteCreateSubscriptionForMyAccount() {
        AddressData addressData = new AddressData();
        BrainTreeSubscriptionInfoData subscriptionData = new BrainTreeSubscriptionInfoData();
        subscriptionData.setAddressData(addressData);
        subscriptionData.setNonce(NONCE);

        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.AUTO);
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getLast4()).thenReturn(LAST4);
        when(brainTreePaymentMethodDetails.get__typename()).thenReturn(CREDIT_CARD_DETAILS);
        when(brainTreePaymentMethodDetails.getBrandCode()).thenReturn(VISA);
        when(brainTreePaymentMethodDetails.getExpirationMonth()).thenReturn(EXPIRATION_MONTH);
        when(brainTreePaymentMethodDetails.getExpirationYear()).thenReturn(EXPIRATION_YEAR);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(brainTreeThreeDSecure);
        when(brainTreeThreeDSecure.getAuthenticationInsight()).thenReturn(braintreeAuthenticationInsight);
        when(braintreeAuthenticationInsight.getCustomerAuthenticationRegulationEnvironment()).thenReturn(PSD_2);
        when(brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.MARK)).thenReturn(checkoutData);
        when(checkoutData.getPaymentMethodsLimitation()).thenReturn(limitationData);

        unit.completeCreateSubscriptionForMyAccount(subscriptionData, customer);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldSetPaymentMethodNonce() {
        final CCPaymentInfoData ccPaymentInfoData = new CCPaymentInfoData();
        ccPaymentInfoData.setPaymentMethodToken(TOKEN);
        final BrainTreePaymentMethodNonceResult nonceResult = new BrainTreePaymentMethodNonceResult();
        nonceResult.setNonce(NONCE);

        when(brainTreePaymentService.createPaymentMethodNonce(TOKEN)).thenReturn(nonceResult);

        unit.setPaymentMethodNonce(ccPaymentInfoData);

        assertEquals(NONCE, ccPaymentInfoData.getPaymentMethodNonce());
    }

    @Test
    public void shouldCreatePaymentMethodNonce() {
        final BrainTreePaymentMethodNonceResult nonceResult = new BrainTreePaymentMethodNonceResult();
        nonceResult.setNonce(NONCE);
        nonceResult.setBin(UID);
        nonceResult.setEnvironment(UNREGULATED);

        when(brainTreePaymentService.createPaymentMethodNonce(TOKEN)).thenReturn(nonceResult);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(paymentInfoData.getPaymentMethodToken()).thenReturn(TOKEN);
        when(paymentInfoData.getSubscriptionId()).thenReturn(CREDITCARD.getCode());

        BraintreePaymentMethodNonceData paymentMethodNonceData = unit.createPaymentMethodNonce(paymentInfoData);

        assertEquals(NONCE, paymentMethodNonceData.getNonce());
        assertEquals(UID, paymentMethodNonceData.getDetails().getBin());
        assertTrue(paymentMethodNonceData.isShouldPerform3dSecure());
    }

    @Test
    public void shouldCreatePaymentMethodNonceWithDisabled3DSecureWhenEnvironmentUnregulated() {
        final BrainTreePaymentMethodNonceResult nonceResult = new BrainTreePaymentMethodNonceResult();
        nonceResult.setNonce(NONCE);
        nonceResult.setBin(UID);
        nonceResult.setEnvironment(UNREGULATED);

        when(brainTreePaymentService.createPaymentMethodNonce(TOKEN)).thenReturn(nonceResult);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(AUTO);
        when(paymentInfoData.getPaymentMethodToken()).thenReturn(TOKEN);
        when(paymentInfoData.getSubscriptionId()).thenReturn(CREDITCARD.getCode());

        BraintreePaymentMethodNonceData paymentMethodNonceData = unit.createPaymentMethodNonce(paymentInfoData);

        assertEquals(NONCE, paymentMethodNonceData.getNonce());
        assertEquals(UID, paymentMethodNonceData.getDetails().getBin());
        assertFalse(paymentMethodNonceData.isShouldPerform3dSecure());
    }

    @Test
    public void shouldCreatePaymentMethodNonceWithAuto3DSecureWhenPaymentIsGooglePay() {
        final BrainTreePaymentMethodNonceResult nonceResult = new BrainTreePaymentMethodNonceResult();
        nonceResult.setNonce(NONCE);
        nonceResult.setBin(UID);
        nonceResult.setEnvironment(UNREGULATED);

        when(brainTreePaymentService.createPaymentMethodNonce(TOKEN)).thenReturn(nonceResult);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(AUTO);
        when(paymentInfoData.getPaymentMethodToken()).thenReturn(TOKEN);
        when(paymentInfoData.getSubscriptionId()).thenReturn(ANDROIDPAYCARD.getCode());

        BraintreePaymentMethodNonceData paymentMethodNonceData = unit.createPaymentMethodNonce(paymentInfoData);

        assertEquals(NONCE, paymentMethodNonceData.getNonce());
        assertEquals(UID, paymentMethodNonceData.getDetails().getBin());
        assertTrue(paymentMethodNonceData.isShouldPerform3dSecure());
    }

    @Test
    public void shouldSetDeviceData() {
        unit.setDeviceData(DEVICE_DATA, paymentInfo);

        verify(modelService).save(paymentInfo);
        verify(paymentInfo).setDeviceData(DEVICE_DATA);
    }

    @Test
    public void shouldGetBrainTreePaymentInfoDataByCart() {
        final BrainTreePaymentInfoData brainTreePaymentInfoDataExpected = new BrainTreePaymentInfoData();

        when(userService.getCurrentUser()).thenReturn(customer);
        when(commerceCartService.getCartForCodeAndUser(UID, customer)).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(brainTreePaymentInfoDataConverter.convert(paymentInfo)).thenReturn(brainTreePaymentInfoDataExpected);
        unit.setBrainTreePaymentInfoDataConverter(brainTreePaymentInfoDataConverter);

        BrainTreePaymentInfoData result = unit.getBrainTreePaymentInfoDataByCart(UID);

        assertEquals(brainTreePaymentInfoDataExpected, result);
    }

    @Test
    public void shouldForceUnduplicateCartForReplenishment() {
        when(userService.getCurrentUser()).thenReturn(customer);
        when(commerceCartService.getCartForCodeAndUser(UID, customer)).thenReturn(cart);
        when(cart.getPaymentInfo()).thenReturn(paymentInfo);
        when(paymentInfo.getDuplicate()).thenReturn(true);
        when(paymentInfo.isSaved()).thenReturn(true);

        unit.forceUnduplicateCartForReplenishment(UID);

        verify(modelService).save(paymentInfo);
    }

    @Test
    public void shouldGetLocalPaymentMethods() {
        when(braintreeLocalPaymentMethodsService.getAllLocalPaymentMethods()).thenReturn(Collections.emptyList());

        unit.getLocalPaymentMethods();

        verify(braintreeLocalPaymentMethodsService).getAllLocalPaymentMethods();
    }

    @Test
    public void shouldGetOrderByPaymentMethodNonce() {
        final OrderData orderData = new OrderData();

        when(braintreeLocalPaymentMethodsService.getOrderByPaymentMethodNonce(NONCE)).thenReturn(order);

        unit.getOrderByPaymentMethodNonce(NONCE);

        verify(braintreeLocalPaymentMethodsService, times(2)).getOrderByPaymentMethodNonce(NONCE);
    }

    @Test
    public void shouldGetCartByPaymentId() {
        final CartModel cart = mock(CartModel.class);

        when(braintreeLocalPaymentMethodsService.getCartByPaymentId(PAYMENT_ID)).thenReturn(cart);

        unit.getCartByPaymentId(PAYMENT_ID);

        verify(braintreeLocalPaymentMethodsService).getCartByPaymentId(PAYMENT_ID);
    }

    @Test
    public void shouldGetAvailablePayments() {
        final CCPaymentInfoData paymentInfoData = new CCPaymentInfoData();
        paymentInfoData.setPaymentMethodNonce(NONCE);

        List<CCPaymentInfoData> result = unit.getAvailablePayments(List.of(paymentInfoData));

        assertEquals(paymentInfoData, result.get(0));
    }

    @Test
    public void shouldVerify3DSForSelectedPayment(){
        BrainTreeAuthentication authentication = new BrainTreeAuthentication();
        authentication.setLiabilityShiftPossible(true);
        authentication.setLiabilityShifted(true);
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();
        treeThreeDSecure.setAuthentication(authentication);

        when(userService.getCurrentUser()).thenReturn(customer);
        when(customerAccountService.getBrainTreePaymentInfoForCode(customer, PAYMENT_ID)).thenReturn(paymentInfo);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(paymentInfo.getPaymentProvider()).thenReturn(CREDITCARD.getCode());
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);

        unit.verifyThreeDSForSelectedPayment(PAYMENT_ID, NONCE);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test(expected = BrainTreeThreeDSVerificationException.class)
    public void shouldThrowExceptionForVerify3DSForSelectedPayment(){
        BrainTreeThreeDSecure treeThreeDSecure = new BrainTreeThreeDSecure();

        when(userService.getCurrentUser()).thenReturn(customer);
        when(customerAccountService.getBrainTreePaymentInfoForCode(customer, PAYMENT_ID)).thenReturn(paymentInfo);
        when(brainTreeConfigService.getThreeDSecureFlow()).thenReturn(BraintreeThreeDSecureFlow.ALWAYS);
        when(paymentInfo.getPaymentProvider()).thenReturn(CREDITCARD.getCode());
        when(brainTreePaymentService.getPaymentDetailsByNonce(NONCE)).thenReturn(brainTreePaymentMethod);
        when(brainTreePaymentMethod.getDetails()).thenReturn(brainTreePaymentMethodDetails);
        when(brainTreePaymentMethodDetails.getThreeDSecure()).thenReturn(treeThreeDSecure);

        unit.verifyThreeDSForSelectedPayment(PAYMENT_ID, NONCE);

        verify(brainTreePaymentService).getPaymentDetailsByNonce(NONCE);
    }

    @Test
    public void shouldReturnFalseWhenPaymentContainBillingAddress(){
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(customerAccountService.getBrainTreePaymentInfoForCode(customer, PAYMENT_ID)).thenReturn(paymentInfo);
        when(paymentInfo.getBillingAddress()).thenReturn(billingAddress);

        boolean result = unit.isBillingAddressEmptyOnPaymentInfo(PAYMENT_ID);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenNoPaymentWithGivenIdExist(){
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);

        boolean result = unit.isBillingAddressEmptyOnPaymentInfo(PAYMENT_ID);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTrueWhenPaymentWithGivenIdDoesNoHaveBillingAddress(){
        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customer);
        when(customerAccountService.getBrainTreePaymentInfoForCode(customer, PAYMENT_ID)).thenReturn(paymentInfo);

        boolean result = unit.isBillingAddressEmptyOnPaymentInfo(PAYMENT_ID);

        assertTrue(result);
    }
}
