public class PaymentsResponseConverter implements Converter<PaymentResult, PaymentsResponse> {
    @Override
    public PaymentsResponse convert(PaymentResult paymentResult) {
        if (paymentResult == null) {
            throw new IllegalArgumentException("Null PaymentResult");
        }
        PaymentsResponse paymentsResponse = new PaymentsResponse();
        paymentsResponse.setPspReference(paymentResult.getPspReference());
        paymentsResponse.setFraudResult(paymentResult.getFraudResult());

        paymentsResponse.setAdditionalData(paymentResult.getAdditionalData());
        paymentsResponse.putAdditionalDataItem(AUTH_CODE, paymentResult.getAuthCode());

        return paymentsResponse;
    }
} public class AdyenNonAuthorizedPaymentException extends Exception {
    private PaymentResult paymentResult;
    private PaymentsResponse paymentsResponse;
    private TerminalAPIResponse terminalApiResponse;
    private PaymentsDetailsResponse paymentsDetailsResponse;

    public AdyenNonAuthorizedPaymentException(PaymentResult paymentResult) {
        this.paymentResult = paymentResult;
    }

    public AdyenNonAuthorizedPaymentException(PaymentsResponse paymentsResponse) {
        this.paymentsResponse = paymentsResponse;
    }

    public AdyenNonAuthorizedPaymentException(TerminalAPIResponse terminalApiResponse) {
        this.terminalApiResponse = terminalApiResponse;
    }

    public AdyenNonAuthorizedPaymentException(PaymentsDetailsResponse paymentsDetailsResponse) {
        this.paymentsDetailsResponse = paymentsDetailsResponse;
    }

    public AdyenNonAuthorizedPaymentException(String message) {
        super(message);
    }

    public PaymentResult getPaymentResult() {
        return paymentResult;
    }

    public void setPaymentResult(PaymentResult paymentResult) {
        this.paymentResult = paymentResult;
    }

    public PaymentsResponse getPaymentsResponse() {
        return paymentsResponse;
    }

    public void setPaymentsResponse(PaymentsResponse paymentsResponse) {
        this.paymentsResponse = paymentsResponse;
    }

    public TerminalAPIResponse getTerminalApiResponse() {
        return terminalApiResponse;
    }

    public void setTerminalApiResponse(TerminalAPIResponse terminalApiResponse) {
        this.terminalApiResponse = terminalApiResponse;
    }

    public PaymentsDetailsResponse getPaymentsDetailsResponse() {
        return paymentsDetailsResponse;
    }

    public void setPaymentsDetailsResponse(PaymentsDetailsResponse paymentsDetailsResponse) {
        this.paymentsDetailsResponse = paymentsDetailsResponse;
    }
} public class AdyenPaymentServiceFactory {
    private PaymentMethodConverter paymentMethodConverter;
    private AdyenRequestFactory adyenRequestFactory;

    public AdyenPaymentService createFromBaseStore(final BaseStoreModel baseStoreModel) {
        DefaultAdyenPaymentService adyenPaymentService = new DefaultAdyenPaymentService(baseStoreModel);
        adyenPaymentService.setPaymentMethodConverter(paymentMethodConverter);
        adyenPaymentService.setAdyenRequestFactory(adyenRequestFactory);
        return adyenPaymentService;
    }

    public PaymentMethodConverter getPaymentMethodConverter() {
        return paymentMethodConverter;
    }

    public void setPaymentMethodConverter(PaymentMethodConverter paymentMethodConverter) {
        this.paymentMethodConverter = paymentMethodConverter;
    }

    public AdyenRequestFactory getAdyenRequestFactory() {
        return adyenRequestFactory;
    }

    public void setAdyenRequestFactory(AdyenRequestFactory adyenRequestFactory) {
        this.adyenRequestFactory = adyenRequestFactory;
    }
} public class RequestInfo {

    public static final String USER_AGENT_HEADER = "User-Agent";
    public static final String ACCEPT_HEADER = "Accept";

    private String userAgent;
    private String acceptHeader;
    private String shopperIp;
    private String origin;
    private String shopperLocale;

    public RequestInfo(HttpServletRequest request) {
        this.userAgent = request.getHeader(USER_AGENT_HEADER);
        this.acceptHeader = request.getHeader(ACCEPT_HEADER);
        this.shopperIp = request.getRemoteAddr();
        this.origin = getOrigin(request);
    }

    private RequestInfo() {
    }

    public String getOrigin(HttpServletRequest request) {
        String currentRequestURL = request.getRequestURL().toString();
        int requestUrlLength = currentRequestURL.length();
        int requestUriLength = request.getRequestURI().length();

        String baseURL = currentRequestURL.substring(0, requestUrlLength - requestUriLength);
        return baseURL;
    }

    public String getOrigin() {
        return this.origin;
    }

    public static RequestInfo empty() {
        return new RequestInfo();
    }

    public String getUserAgent() {
        return userAgent;
    }

    public String getAcceptHeader() {
        return acceptHeader;
    }

    public String getShopperIp() {
        return shopperIp;
    }

    public String getShopperLocale() {
        return shopperLocale;
    }

    public void setShopperLocale(String shopperLocale) {
        this.shopperLocale = shopperLocale;
    }
} public class OrderRepository extends AbstractRepository {
    private static final Logger LOG = Logger.getLogger(OrderRepository.class);

    public OrderModel getOrderModel(String code) {
        final Map queryParams = new HashMap();
        queryParams.put("code", code);

        //Adding "{versionID} IS NULL" to get the original order regardless of modification history
        final FlexibleSearchQuery selectOrderQuery = new FlexibleSearchQuery(
                "SELECT {pk} FROM {" + OrderModel._TYPECODE + "}"
                        + " WHERE {" + OrderModel.CODE + "} = ?code"
                        + " AND {versionID} IS NULL",
                queryParams
        );

        LOG.debug("Finding order with code: " + code);

        return (OrderModel) getOneOrNull(selectOrderQuery);
    }
} public interface AdyenOrderService {
    void updateOrderFromPaymentsResponse(OrderModel order, PaymentsResponse paymentsResponse);

    /**
     * Updates order's metadata using the API Payment response
     * Covers fraud, avs, cc authorisation data
     */
    void updateOrderFromPaymentResult(OrderModel order, PaymentResult paymentResult);

    FraudReportModel createFraudReportFromPaymentsResponse(PaymentsResponse paymentsResponse);

    /**
     * Creates FraudReportModel from PaymentResult
     * @return FraudReportModel|null
     */
    FraudReportModel createFraudReportFromPaymentResult(PaymentResult paymentResult);

    /**
     * Store FraudReportModel
     */
    void storeFraudReport(FraudReportModel fraudReport);

    void storeFraudReportFromPaymentsResponse(OrderModel order, PaymentsResponse paymentsResponse);

    /**
     * Create FraudReportModel from PaymentResult and assigns it to order
     */
    void storeFraudReportFromPaymentResult(OrderModel order, PaymentResult paymentResult);
} public interface AdyenTransactionService {
    /**
     * Get TX entry by type and status
     */
    static PaymentTransactionEntryModel getTransactionEntry(PaymentTransactionModel paymentTransactionModel, PaymentTransactionType paymentTransactionType, TransactionStatus transactionStatus) {
        PaymentTransactionEntryModel result = paymentTransactionModel.getEntries().stream()
                .filter(
                        entry -> paymentTransactionType.equals(entry.getType())
                                && transactionStatus.name().equals(entry.getTransactionStatus())
                )
                .findFirst()
                .orElse(null);

        return result;
    }

    /**
     * Get TX entry by type, status and details
     */
    static PaymentTransactionEntryModel getTransactionEntry(PaymentTransactionModel paymentTransactionModel,
                                                            PaymentTransactionType paymentTransactionType,
                                                            TransactionStatus transactionStatus,
                                                            TransactionStatusDetails transactionStatusDetails) {
        PaymentTransactionEntryModel result = paymentTransactionModel.getEntries().stream()
                .filter(
                        entry -> paymentTransactionType.equals(entry.getType())
                                && transactionStatus.name().equals(entry.getTransactionStatus())
                                && transactionStatusDetails.name().equals(entry.getTransactionStatusDetails())
                )
                .findFirst()
                .orElse(null);

        return result;
    }

    /**
     * Creates a PaymentTransactionEntryModel with type=CAPTURE from NotificationItemModel
     */
    PaymentTransactionEntryModel createCapturedTransactionFromNotification(PaymentTransactionModel paymentTransaction, NotificationItemModel notificationItemModel);

    /**
     * Creates a PaymentTransactionEntryModel with type=REFUND_FOLLOW_ON from NotificationItemModel
     */
    PaymentTransactionEntryModel createRefundedTransactionFromNotification(PaymentTransactionModel paymentTransaction, NotificationItemModel notificationItemModel);

    /**
     * Stores the authorization transactions for an order
     */
    PaymentTransactionModel authorizeOrderModel(AbstractOrderModel abstractOrderModel, String merchantTransactionCode, String pspReference);

    /**
     * Store failed authorization transaction entry
     */
    PaymentTransactionModel storeFailedAuthorizationFromNotification(NotificationItemModel notificationItemModel, AbstractOrderModel abstractOrderModel);

    /**
     * Creates a PaymentTransactionEntryModel with type=CANCEL
     */
    PaymentTransactionEntryModel createCancellationTransaction(PaymentTransactionModel paymentTransaction, String merchantCode, String pspReference);

    /**
     * Creates a PaymentTransactionModel
     */
    PaymentTransactionModel createPaymentTransactionFromResultCode(AbstractOrderModel abstractOrderModel, String merchantTransactionCode, String pspReference, PaymentsResponse.ResultCodeEnum resultCodeEnum);

    /**
     * Stores the authorization transactions for an order
     */
    PaymentTransactionModel authorizeOrderModel(AbstractOrderModel abstractOrderModel, String merchantTransactionCode, String pspReference, BigDecimal paymentAmount);
} public class DefaultAdyenPaymentService implements AdyenPaymentService {
    private BaseStoreModel baseStore;
    private AdyenRequestFactory adyenRequestFactory;
    private Config config;
    private Client client;
    private Config posConfig;
    private Client posClient;

    private PaymentMethodConverter paymentMethodConverter;

    private static final int POS_REQUEST_TIMEOUT = 25000;

    private static final Logger LOG = Logger.getLogger(DefaultAdyenPaymentService.class);

    /**
     * Prevent initialization without base store
     */
    private DefaultAdyenPaymentService() {
    }

    public DefaultAdyenPaymentService(final BaseStoreModel baseStore) {
        this.baseStore = baseStore;

        String apiKey = baseStore.getAdyenAPIKey();
        String merchantAccount = baseStore.getAdyenMerchantAccount();
        String apiEndpointPrefix = baseStore.getAdyenAPIEndpointPrefix();
        boolean isTestMode = baseStore.getAdyenTestMode();
        boolean isPosEnabled = baseStore.getAdyenPosEnabled();
        if (isPosEnabled) {
            String posApiKey = baseStore.getAdyenPosApiKey();
            String posMerchantAccount = baseStore.getAdyenPosMerchantAccount();
            posConfig = new Config();
            posConfig.setApiKey(posApiKey);
            posConfig.setMerchantAccount(posMerchantAccount);
            posConfig.setReadTimeoutMillis(POS_REQUEST_TIMEOUT);
            posConfig.setApplicationName(PLUGIN_NAME + " v" + PLUGIN_VERSION);
            posClient = new Client(posConfig);

            if (isTestMode) {
                posClient.setEnvironment(Environment.TEST, null);
            } else {
                posClient.setEnvironment(Environment.LIVE, null);
            }
        }
        Assert.notNull(merchantAccount);

        config = new Config();
        config.setApiKey(apiKey);
        config.setMerchantAccount(merchantAccount);
        config.setApplicationName(PLUGIN_NAME + " v" + PLUGIN_VERSION);
        client = new Client(config);

        if (isTestMode) {
            client.setEnvironment(Environment.TEST, null);
        } else {
            client.setEnvironment(Environment.LIVE, apiEndpointPrefix);
        }
    }

    @Override
    public PaymentResult authorise(final CartData cartData, final HttpServletRequest request, final CustomerModel customerModel) throws Exception {
        Payment payment = new Payment(client);

        PaymentRequest paymentRequest = getAdyenRequestFactory().createAuthorizationRequest(client.getConfig().getMerchantAccount(),
                                                                                            cartData,
                                                                                            request,
                                                                                            customerModel,
                                                                                            baseStore.getAdyenRecurringContractMode());


        LOG.debug(paymentRequest);
        PaymentResult paymentResult = payment.authorise(paymentRequest);
        LOG.debug(paymentResult);

        return paymentResult;
    }

    @Override
    public ConnectedTerminalsResponse getConnectedTerminals() throws IOException, ApiException  {
        PosPayment posPayment = new PosPayment(posClient);
        ConnectedTerminalsRequest connectedTerminalsRequest = new ConnectedTerminalsRequest();
        connectedTerminalsRequest.setMerchantAccount(posConfig.getMerchantAccount());
        if (baseStore.getAdyenPosStoreId() != null && StringUtils.isNotEmpty(baseStore.getAdyenPosStoreId())) {
            connectedTerminalsRequest.setStore(baseStore.getAdyenPosStoreId());
        }
        LOG.debug(connectedTerminalsRequest);
        ConnectedTerminalsResponse connectedTerminalsResponse = posPayment.connectedTerminals(connectedTerminalsRequest);
        LOG.debug(connectedTerminalsResponse);
        return connectedTerminalsResponse;

    }

    @Override
    public PaymentsResponse authorisePayment(final CartData cartData, final RequestInfo requestInfo, final CustomerModel customerModel) throws Exception {
        Checkout checkout = new Checkout(client);

        PaymentsRequest paymentsRequest = getAdyenRequestFactory().createPaymentsRequest(client.getConfig().getMerchantAccount(),
                                                                                         cartData,
                                                                                         requestInfo,
                                                                                         customerModel,
                                                                                         baseStore.getAdyenRecurringContractMode(),
                                                                                         baseStore.getAdyenGuestUserTokenization());

        LOG.debug(paymentsRequest);
        PaymentsResponse paymentsResponse = checkout.payments(paymentsRequest);
        LOG.debug(paymentsResponse);

        return paymentsResponse;
    }

    @Override
    public PaymentsResponse componentPayment(final CartData cartData, final PaymentMethodDetails paymentMethodDetails, final RequestInfo requestInfo, final CustomerModel customerModel) throws Exception {
        Checkout checkout = new Checkout(client);

        PaymentsRequest paymentsRequest = getAdyenRequestFactory().createPaymentsRequest(client.getConfig().getMerchantAccount(),
                cartData,
                paymentMethodDetails,
                requestInfo,
                customerModel);

        LOG.debug(paymentsRequest);
        PaymentsResponse paymentsResponse = checkout.payments(paymentsRequest);
        LOG.debug(paymentsResponse);

        return paymentsResponse;
    }

    @Override
    public PaymentsDetailsResponse authorise3DSPayment(Map<String, String> details) throws Exception {
        Checkout checkout = new Checkout(client);
        PaymentsDetailsRequest paymentsDetailsRequest = getAdyenRequestFactory().create3DSPaymentsRequest(details);

        LOG.debug(paymentsDetailsRequest);
        PaymentsDetailsResponse paymentsDetailsResponse = checkout.paymentsDetails(paymentsDetailsRequest);
        LOG.debug(paymentsDetailsResponse);

        return paymentsDetailsResponse;
    }

    @Override
    public ModificationResult capture(final BigDecimal amount, final Currency currency, final String authReference, final String merchantReference) throws Exception {
        Modification modification = new Modification(client);

        CaptureRequest captureRequest = getAdyenRequestFactory().createCaptureRequest(client.getConfig().getMerchantAccount(), amount, currency, authReference, merchantReference);

        LOG.debug(captureRequest);
        ModificationResult modificationResult = modification.capture(captureRequest);
        LOG.debug(modificationResult);

        return modificationResult;
    }

    @Override
    public ModificationResult cancelOrRefund(final String authReference, final String merchantReference) throws Exception {
        Modification modification = new Modification(client);

        CancelOrRefundRequest cancelRequest = getAdyenRequestFactory().createCancelOrRefundRequest(client.getConfig().getMerchantAccount(), authReference, merchantReference);

        LOG.debug(cancelRequest);
        ModificationResult modificationResult = modification.cancelOrRefund(cancelRequest);
        LOG.debug(modificationResult);

        return modificationResult;
    }

    @Override
    public ModificationResult refund(final BigDecimal amount, final Currency currency, final String authReference, final String merchantReference) throws Exception {
        Modification modification = new Modification(client);

        RefundRequest refundRequest = getAdyenRequestFactory().createRefundRequest(client.getConfig().getMerchantAccount(), amount, currency, authReference, merchantReference);

        LOG.debug(refundRequest);
        ModificationResult modificationResult = modification.refund(refundRequest);
        LOG.debug(modificationResult);

        return modificationResult;
    }

    @Override
    public List<PaymentMethod> getPaymentMethods(final BigDecimal amount,
                                                 final String currency,
                                                 final String countryCode,
                                                 final String shopperLocale,
                                                 final String shopperReference) throws IOException, ApiException {

        PaymentMethodsResponse response =getPaymentMethodsResponse(amount,currency, countryCode, shopperLocale, shopperReference);
        return response.getPaymentMethods();
    }

    @Override
    public PaymentMethodsResponse getPaymentMethodsResponse(final BigDecimal amount,
                                                 final String currency,
                                                 final String countryCode,
                                                 final String shopperLocale,
                                                 final String shopperReference) throws IOException, ApiException {
        Checkout checkout = new Checkout(client);
        PaymentMethodsRequest request = new PaymentMethodsRequest();
        request.merchantAccount(client.getConfig().getMerchantAccount()).amount(Util.createAmount(amount, currency)).countryCode(countryCode);

        if (! StringUtils.isEmpty(shopperLocale)) {
            request.setShopperLocale(shopperLocale);
        }

        if (! StringUtils.isEmpty(shopperReference)) {
            request.setShopperReference(shopperReference);
        }

        LOG.debug(request);
        PaymentMethodsResponse response = checkout.paymentMethods(request);
        LOG.debug(response);

        return response;
    }

    @Override
    @Deprecated
    public List<com.adyen.model.hpp.PaymentMethod> getPaymentMethods(final BigDecimal amount,
                                                                     final String currency,
                                                                     final String countryCode,
                                                                     final String shopperLocale) throws HTTPClientException, SignatureException, IOException {
        try {
            List<PaymentMethod> checkoutPaymentMethods = getPaymentMethods(amount, currency, countryCode, shopperLocale, null);
            return checkoutPaymentMethods.stream().map(paymentMethodConverter::convert).collect(Collectors.toList());
        } catch (ApiException e) {
            LOG.error(e);
        }
        return null;
    }

    @Override
    @Deprecated
    public List<RecurringDetail> getStoredCards(final String customerId) throws IOException, ApiException {
        if (customerId == null) {
            return new ArrayList<>();
        }

        com.adyen.service.Recurring recurring = new com.adyen.service.Recurring(client);

        RecurringDetailsRequest request = getAdyenRequestFactory().createListRecurringDetailsRequest(client.getConfig().getMerchantAccount(), customerId);

        LOG.debug(request);
        RecurringDetailsResult result = recurring.listRecurringDetails(request);
        LOG.debug(result);

        //Return only cards
        List<RecurringDetail> storedCards = result.getRecurringDetails()
                                                  .stream()
                                                  .filter(detail -> (detail.getCard() != null && detail.getRecurringDetailReference() != null))
                                                  .collect(Collectors.toList());

        return storedCards;
    }

    @Override
    public boolean disableStoredCard(final String customerId, final String recurringReference) throws IOException, ApiException {
        com.adyen.service.Recurring recurring = new com.adyen.service.Recurring(client);

        DisableRequest request = getAdyenRequestFactory().createDisableRequest(client.getConfig().getMerchantAccount(), customerId, recurringReference);

        LOG.debug(request);
        DisableResult result = recurring.disable(request);
        LOG.debug(result);

        return ("[detail-successfully-disabled]".equals(result.getResponse()) || "[all-details-successfully-disabled]".equals(result.getResponse()));
    }

    @Override
    public PaymentsDetailsResponse getPaymentDetailsFromPayload(HashMap<String, String> details) throws Exception {
        Checkout checkout = new Checkout(client);

        PaymentsDetailsRequest paymentsDetailsRequest = new PaymentsDetailsRequest();
        paymentsDetailsRequest.setDetails(details);

        LOG.debug(paymentsDetailsRequest);
        PaymentsDetailsResponse paymentsResponse = checkout.paymentsDetails(paymentsDetailsRequest);
        LOG.debug(paymentsResponse);

        return paymentsResponse;
    }

    @Override
    public PaymentsDetailsResponse getPaymentDetailsFromPayload(Map<String, String> details, String paymentData) throws Exception {
        Checkout checkout = new Checkout(client);
        PaymentsDetailsRequest paymentsDetailsRequest = new PaymentsDetailsRequest();
        paymentsDetailsRequest.setDetails(details);
        paymentsDetailsRequest.setPaymentData(paymentData);

        LOG.debug(paymentsDetailsRequest);
        PaymentsDetailsResponse paymentsResponse = checkout.paymentsDetails(paymentsDetailsRequest);
        LOG.debug(paymentsResponse);

        return paymentsResponse;
    }

    @Override
    public String getDeviceFingerprintUrl() {
        DateFormat df = new SimpleDateFormat("yyyyMMdd");
        Date today = Calendar.getInstance().getTime();
        return "https://live.adyen.com/hpp/js/df.js?v=" + df.format(today);
    }

    /**
     * Send POS Payment Request using Adyen Terminal API
     */
    @Override
    public TerminalAPIResponse sendSyncPosPaymentRequest(CartData cartData, CustomerModel customer, String serviceId) throws Exception {
        TerminalCloudAPI terminalCloudAPI = new TerminalCloudAPI(posClient);

        RecurringContractMode recurringContractMode = getBaseStore().getAdyenPosRecurringContractMode();
        TerminalAPIRequest terminalApiRequest = adyenRequestFactory.createTerminalAPIRequest(cartData, customer, recurringContractMode, serviceId);

        LOG.debug(TerminalAPIGsonBuilder.create().toJson(terminalApiRequest));
        TerminalAPIResponse terminalApiResponse = terminalCloudAPI.sync(terminalApiRequest);

        LOG.debug(TerminalAPIGsonBuilder.create().toJson(terminalApiResponse));
        return terminalApiResponse;
    }

    /**
     * Send POS Status Request using Adyen Terminal API
     */
    @Override
    public TerminalAPIResponse sendSyncPosStatusRequest(CartData cartData, String originalServiceId) throws Exception {
        TerminalCloudAPI terminalCloudAPI = new TerminalCloudAPI(posClient);

        TerminalAPIRequest terminalApiRequest = adyenRequestFactory.createTerminalAPIRequestForStatus(cartData, originalServiceId);

        LOG.debug(TerminalAPIGsonBuilder.create().toJson(terminalApiRequest));
        TerminalAPIResponse terminalApiResponse = terminalCloudAPI.sync(terminalApiRequest);

        LOG.debug(TerminalAPIGsonBuilder.create().toJson(terminalApiResponse));
        return terminalApiResponse;
    }

    public AdyenRequestFactory getAdyenRequestFactory() {
        return adyenRequestFactory;
    }

    public void setAdyenRequestFactory(AdyenRequestFactory adyenRequestFactory) {
        this.adyenRequestFactory = adyenRequestFactory;
    }

    public BaseStoreModel getBaseStore() {
        return baseStore;
    }

    public void setBaseStore(BaseStoreModel baseStore) {
        this.baseStore = baseStore;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    public Client getClient() {
        return client;
    }

    public void setClient(Client client) {
        this.client = client;
    }

    public Config getConfig() {
        return config;
    }

    public PaymentMethodConverter getPaymentMethodConverter() {
        return paymentMethodConverter;
    }

    public void setPaymentMethodConverter(PaymentMethodConverter paymentMethodConverter) {
        this.paymentMethodConverter = paymentMethodConverter;
    }
} final public static String PAYMENT_METHOD_CC = "adyen_cc"; final public static String PAYMENT_METHOD_ONECLICK = "adyen_oneclick_"; public static final String SESSION_LOCKED_CART = "adyen_cart";
import com.adyen.model.PaymentResult;
import com.adyen.model.checkout.CheckoutPaymentsAction;
import com.adyen.model.checkout.PaymentsDetailsResponse;
import com.adyen.model.checkout.PaymentsResponse;
import com.adyen.v6.converters.PaymentsResponseConverter;
import com.adyen.v6.exceptions.AdyenNonAuthorizedPaymentException;
import com.adyen.v6.factory.AdyenPaymentServiceFactory;
import com.adyen.v6.model.RequestInfo;
import com.adyen.v6.repository.OrderRepository;
import com.adyen.v6.service.AdyenOrderService;
import com.adyen.v6.service.AdyenTransactionService;
import com.adyen.v6.service.DefaultAdyenPaymentService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.commercefacades.order.CheckoutFacade;
import de.hybris.platform.commercefacades.order.OrderFacade;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.product.data.PriceData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercefacades.user.data.CountryData;
import de.hybris.platform.commerceservices.strategies.CheckoutCustomerStrategy;
import de.hybris.platform.core.model.c2l.LanguageModel;
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.CustomerModel;
import de.hybris.platform.order.CartService;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.servicelayer.i18n.CommonI18NService;
import de.hybris.platform.servicelayer.keygenerator.KeyGenerator;
import de.hybris.platform.servicelayer.session.SessionService;
import de.hybris.platform.store.BaseStoreModel;
import de.hybris.platform.store.services.BaseStoreService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.security.SignatureException;
import java.util.Collections;
import java.util.Map;
import static com.adyen.constants.ApiConstants.Redirect.Data.MD;
import static com.adyen.v6.constants.Adyenv6coreConstants.PAYMENT_METHOD_CC;
import static com.adyen.v6.constants.Adyenv6coreConstants.PAYMENT_METHOD_ONECLICK;
import static com.adyen.v6.facades.DefaultAdyenCheckoutFacade.SESSION_LOCKED_CART;
import static de.hybris.platform.order.impl.DefaultCartService.SESSION_CART_PARAMETER_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/*
 *                        ######
 *                        ######
 *  ############    ####( ######  #####. ######  ############   ############
 *  #############  #####( ######  #####. ######  #############  #############
 *         ######  #####( ######  #####. ######  #####  ######  #####  ######
 *  ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
 *  ###### ######  #####( ######  #####. ######  #####          #####  ######
 *  #############  #############  #############  #############  #####  ######
 *   ############   ############  #############   ############  #####  ######
 *                                       ######
 *                                #############
 *                                ############
 *
 *  Adyen Hybris Extension
 *
 *  Copyright (c) 2017 Adyen B.V.
 *  This file is open source and available under the MIT license.
 *  See the LICENSE file for more info.
 */
package com.adyen.v6.facades;




@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class AdyenCheckoutFacadeTest {
    @Mock
    private BaseStoreService baseStoreServiceMock;

    @Mock
    private SessionService sessionServiceMock;

    @Mock
    private CartService cartServiceMock;

    @Mock
    private OrderFacade orderFacadeMock;

    @Mock
    private CheckoutFacade checkoutFacadeMock;

    @Mock
    private DefaultAdyenPaymentService adyenPaymentServiceMock;

    @Mock
    private AdyenTransactionService adyenTransactionServiceMock;

    @Mock
    private OrderRepository orderRepositoryMock;

    @Mock
    private AdyenOrderService adyenOrderServiceMock;

    @Mock
    private CheckoutCustomerStrategy checkoutCustomerStrategyMock;

    @Mock
    private AdyenPaymentServiceFactory adyenPaymentServiceFactoryMock;

    @Mock
    private CommonI18NService commonI18NServiceMock;

    @Mock
    private KeyGenerator keyGeneratorMock;

    @InjectMocks
    private DefaultAdyenCheckoutFacade adyenCheckoutFacade;

    private CartModel cartModelMock;

    private PaymentResult paymentResultMock;
    private PaymentsResponse paymentsResponseMock;
    private PaymentsDetailsResponse paymentsDetailsResponseMock;

    private PaymentsResponse paymentsResponse;

    @Before
    public void setUp() throws SignatureException, InvalidCartException {
        BaseStoreModel baseStoreModelMock = mock(BaseStoreModel.class);
        cartModelMock = mock(CartModel.class);
        OrderData orderDataMock = mock(OrderData.class);
        paymentResultMock = mock(PaymentResult.class);
        paymentsResponseMock = mock(PaymentsResponse.class);
        paymentsDetailsResponseMock = mock(PaymentsDetailsResponse.class);
        CartData cartDataMock = mock(CartData.class);

        when(baseStoreModelMock.getAdyenMerchantAccount()).thenReturn("merchantAccount");
        when(baseStoreServiceMock.getCurrentBaseStore()).thenReturn(baseStoreModelMock);

        when(cartModelMock.getCode()).thenReturn("code");
        when(cartServiceMock.getSessionCart()).thenReturn(cartModelMock);

        when(orderDataMock.getCode()).thenReturn("code");
        when(checkoutFacadeMock.placeOrder()).thenReturn(orderDataMock);

        when(cartDataMock.getCode()).thenReturn("code");
        when(checkoutFacadeMock.getCheckoutCart()).thenReturn(cartDataMock);

        when(paymentResultMock.getPspReference()).thenReturn("pspRef");
        when(paymentResultMock.getMd()).thenReturn("md");

        when(paymentsResponseMock.getPspReference()).thenReturn("pspRef");
        paymentsResponse = new PaymentsResponse();
        paymentsResponse.setPspReference("pspRef");
        CheckoutPaymentsAction action = new CheckoutPaymentsAction();
        action.setData(Collections.singletonMap(MD, "md"));
        action.setPaymentData("This is test payment data");
        paymentsResponse.setAction(action);

        when(adyenPaymentServiceFactoryMock.createFromBaseStore(baseStoreModelMock)).thenReturn(adyenPaymentServiceMock);

        LanguageModel languageModel = new LanguageModel();
        languageModel.setIsocode("en");

        when(commonI18NServiceMock.getCurrentLanguage()).thenReturn(languageModel);
        when(keyGeneratorMock.generate()).thenReturn(new Object());
        adyenCheckoutFacade.setPaymentsResponseConverter(new PaymentsResponseConverter());
    }

    @Test
    public void testRestoreSessionCart() throws InvalidCartException {
        when(sessionServiceMock.getAttribute(SESSION_LOCKED_CART)).thenReturn(null);
        try {
            adyenCheckoutFacade.restoreSessionCart();
            fail("Expecting exception");
        } catch (InvalidCartException ignored) {
        }

        when(sessionServiceMock.getAttribute(SESSION_LOCKED_CART)).thenReturn(cartModelMock);
        adyenCheckoutFacade.restoreSessionCart();

        verify(cartServiceMock).setSessionCart(cartModelMock);
        verify(sessionServiceMock).removeAttribute(SESSION_LOCKED_CART);
    }

    @Test
    public void testLockSessionCart() {
        when(sessionServiceMock.getAttribute(SESSION_LOCKED_CART)).thenReturn(null);

        adyenCheckoutFacade.lockSessionCart();

        verify(sessionServiceMock).setAttribute(SESSION_LOCKED_CART, cartModelMock);
        verify(sessionServiceMock).removeAttribute(SESSION_CART_PARAMETER_NAME);
    }

    @Test
    public void testAuthorizeCardPayment() throws Exception {
        HttpServletRequest requestMock = mock(HttpServletRequest.class);
        CartData cartDataMock = mock(CartData.class);
        OrderModel orderModelMock = mock(OrderModel.class);

        //When payment is authorized
        paymentsResponse.setResultCode(PaymentsResponse.ResultCodeEnum.AUTHORISED);
        when(checkoutCustomerStrategyMock.isAnonymousCheckout()).thenReturn(true);
        when(checkoutCustomerStrategyMock.getCurrentUserForCheckout()).thenReturn(null);
        when(adyenPaymentServiceMock.authorisePayment(any(CartData.class), any(RequestInfo.class), any(CustomerModel.class))).thenReturn(paymentsResponse);
        when(orderRepositoryMock.getOrderModel("code")).thenReturn(orderModelMock);
        when(cartDataMock.getAdyenPaymentMethod()).thenReturn(PAYMENT_METHOD_CC);

        adyenCheckoutFacade.authorisePayment(requestMock, cartDataMock);

        verifyAuthorized(orderModelMock);

        //When payment is 3D secure
        paymentsResponse.setResultCode(PaymentsResponse.ResultCodeEnum.REDIRECTSHOPPER);

        try {
            adyenCheckoutFacade.authorisePayment(requestMock, cartDataMock);
            fail("Expecting exception");
        } catch (AdyenNonAuthorizedPaymentException e) {