function usePrecisionPayPaymentGateway($) {
  // ONLY RUN IF PLAID IS AVAILABLE
  if (typeof Plaid === 'undefined') {
    console.error('...MISSING PLAID...');
    return;
  }

  // Session variables
  var SESSION_STORAGE_PLAID = 'mcPlaidData';
  var SESSION_STORAGE_PRECISION_PAY = 'mcPrecisionPayData';

  // From class-precisionpay-payments-for-wc.php
  var precisionPayNonce = precisionpay_data.precisionPayNonce;
  var ajaxUrl = precisionpay_data.ajaxUrl;
  var orderAmount = precisionpay_data.orderAmount;
  var errorMessageTokenExpired = precisionpay_data.errorMessageTokenExpired;
  var errorMessagePlaidTokenExpired = precisionpay_data.errorMessagePlaidTokenExpired;
  var errorMessageNoValidAccounts = precisionpay_data.errorMessageNoValidAccounts;
  var errorMessageInsufficientFunds = precisionpay_data.errorMessageInsufficientFunds;
  var defaultButtonTitle = precisionpay_data.defaultButtonTitle;
  var buttonLogo = precisionpay_data.buttonLogo;
  var loadingImg = precisionpay_data.loadingImg;
  var loadingImgLong = precisionpay_data.loadingImgLong;
  var plaidEnv = precisionpay_data.plaidEnv;
  var checkoutPortalURL = precisionpay_data.checkoutPortalURL;

  var mc_merchantNonce = '';

  function init() {
    // If already authorized payment but hasn't paid yet, set button as authorized
    if (sessionStorage.getItem(SESSION_STORAGE_PLAID)) {
      var mcPlaidData = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_PLAID));
      addDataToHiddenFields(mcPlaidData);
      updateUIToSuccess();
    } else if (sessionStorage.getItem(SESSION_STORAGE_PRECISION_PAY)) {
      var precisionPayToken = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_PRECISION_PAY));
      addPPDataToHiddenField(precisionPayToken);
      updateUIToSuccess();
    }

    // Legacy: We are now listening for submit of the form to add our own loader if user is using PrecisionPay
    if ($('.woocommerce-checkout').length > 0) {
      $('.woocommerce-checkout').on('submit', function () {
        setPrecisionPayLoader($, loadingImg, loadingImgLong);
      });
    }

    // launch checkout portal on PP button click
    $('#precisionpay-link-button').click(authorizePayment);

    // keep an eye out for certain errors we need to handle
    $(document.body).on('checkout_error', handleCheckoutError);
  }

  function handleCheckoutError(e, errorMessage) {
    if (
      errorMessage &&
      (errorMessage.indexOf(errorMessageTokenExpired) !== -1 ||
        errorMessage.indexOf(errorMessagePlaidTokenExpired) !== -1 ||
        errorMessage.indexOf(errorMessageNoValidAccounts) !== -1 ||
        errorMessage.indexOf(errorMessageInsufficientFunds) !== -1)
    ) {
      resetButtonUI();
      removePPDataFromHiddenField();
      sessionStorage.removeItem(SESSION_STORAGE_PRECISION_PAY);
      removePlaidDataFromHiddenField();
      sessionStorage.removeItem(SESSION_STORAGE_PLAID);
    }
  }

  function updateUIToSuccess() {
    $('#precisionpay-link-button')
      .html('✓ Payment Authorized')
      .addClass('payment-authorized')
      .prop('disabled', true);
  }

  function resetButtonUI() {
    $('#precisionpay-link-button')
      .html(defaultButtonTitle + '<img src="' + buttonLogo + '" alt="PrecisionPay logo" />')
      .removeClass('payment-authorized')
      .prop('disabled', false);
  }

  function addDataToHiddenFields(pd) {
    $('#precisionpay_public_token').val(pd.public_token);
    $('#precisionpay_account_id').val(pd.accountId);
    $('#precisionpay_plaid_user_id').val(pd.precisionPayPlaidUserId);
    $('#precisionpay_registered_user_id').val(pd.precisionPayRegisteredUserId); // Used if a user does one time payment after logging in
  }

  function addPPDataToHiddenField(precisionPayToken) {
    $('#precisionpay_checkout_token').val(precisionPayToken);
  }

  function removePPDataFromHiddenField() {
    $('#precisionpay_checkout_token').val('');
  }

  function removePlaidDataFromHiddenField() {
    $('#precisionpay_public_token').val('');
    $('#precisionpay_account_id').val('');
    $('#precisionpay_plaid_user_id').val('');
    $('#precisionpay_registered_user_id').val('');
  }

  function handlePlaidData(plaidData) {
    // Add data to hidden fields
    addDataToHiddenFields(plaidData);
    // Also add to session storage in case we get "refreshed"
    sessionStorage.setItem(SESSION_STORAGE_PLAID, JSON.stringify(plaidData));
    updateUIToSuccess();
  }

  function handlePPData(precisionPayToken) {
    addPPDataToHiddenField(precisionPayToken);
    sessionStorage.setItem(SESSION_STORAGE_PRECISION_PAY, JSON.stringify(precisionPayToken));
    updateUIToSuccess();
  }

  function authorizePayment(e) {
    e.preventDefault();

    let data = {
      precisionPayNonce: precisionPayNonce,
      action: 'prcsnpy_get_merch_nonce',
    };

    let paymentSection = $('#payment').length > 0
      ? $('#payment')
      : $('#payment-method .wc-block-components-checkout-step__content');

    paymentSection.block({
      message: null,
      overlayCSS: {
        background: '#fff',
        opacity: 0.6,
      },
    });

    $.ajax({
      type: 'POST',
      url: ajaxUrl,
      data: data,
      success: function (data) {
        if (data && data.body) {
          // Remove any errors
          getPaymentBoxErrors().remove();
          mc_merchantNonce = data.body.merchantNonce;
          paymentSection.unblock();
          openPrecisionPay(mc_merchantNonce, orderAmount);
        } else {
          if (data.result === 'failed') {
            displayError('', data.message);
          } else {
            console.log('Whoops. Error.', data);
          }
          paymentSection.unblock();
        }
      },
      error: function (err) {
        console.log(err);
        if (err && err.message) {
          displayError('', err.message);
        }
        paymentSection.unblock();
      },
    });
  }

  function openPrecisionPay(merchantNonce, amount) {
    var mcPaymentWindow = $('.mc-payment-portal');

    function removePPEventListener() {
      window.removeEventListener('message', handleCompletedLogin);
    }

    function handleCompletedLogin(event) {
      var message = event.data.message;
      var success = 'PrecisionPay::success';
      var failed = 'PrecisionPay::failed';
      var canceled = 'PrecisionPay::canceled'

      // Make sure we didn't get a message from somewhere else
      if (message !== success && message !== failed && message !== canceled) {
        return;
      }

      switch (message) {
        case success:
          var precisionPayToken = event.data.precisionPayToken;
          if (precisionPayToken) {
            handlePPData(precisionPayToken);
          } else {
            var plaidData = event.data.plaidData;
            if (plaidData) {
              handlePlaidData(plaidData);
            }
          }

          mcPaymentWindow.remove();
          break;
        case failed:
          var error = event.data.error_message;
          displayError('', error);
          break;
        case canceled:
          mcPaymentWindow.hide();
          break;
      }

      removePPEventListener();
    }

    if (mcPaymentWindow.length) {
      mcPaymentWindow.show();
      removePPEventListener(); // clear any existing listeners first
      window.addEventListener('message', handleCompletedLogin);
    } else {
      mcPaymentWindow = $(`
      <div class="mc-payment-portal mc-overlay">
        <iframe class="mc-payment-window" src="${checkoutPortalURL}/checkout-login/${encodeURI(
        merchantNonce
      )}/amount/${encodeURI(amount)}/env/${plaidEnv}" title="Log in to PrecisionPay"></iframe>
      </div>
      `);
      var mcPaymentStyles = `
      <style>
        .mc-overlay {
          display:none;
          background: rgba(255,255,255,0.5);
          position: fixed;
          top: 0vh;
          left: 0vw;
          width: 100vw;
          height: 100vh;
          z-index: 524287;
          transition: 0.5s;
        }
        iframe.mc-payment-window { 
          position: fixed;
          top: 0vh;
          left: 0vw;
          border-width: 0px;
          width: 100vw;
          height: 100vh;
        }
      </style>
      `;
      $('body').append(mcPaymentStyles);
      $('body').append(mcPaymentWindow);
      $(mcPaymentWindow).show();

      window.addEventListener('message', handleCompletedLogin);
    }
  }

  function displayError(status, msg) {
    formattedMsg = msg.replace('<h3>', '').replace('</h3>', ''); // remove headings if present
    var errorMessage = status ? status + ': ' + formattedMsg : formattedMsg;
    var mcErrorMessage = '<p class="error" style="color: red">' + errorMessage + '</p>';
    var paymentBox = getPaymentBox();

    paymentBox.prepend(mcErrorMessage);
  }

  function getPaymentBox() {
    return $(getPaymentBoxName());
  }

  function getPaymentBoxErrors() {
    return $(getPaymentBoxName() + ' .error')
  }

  function getPaymentBoxName() {
    return $('.payment_box.payment_method_wc_gateway_precisionpay').length > 0
    ? '.payment_box.payment_method_wc_gateway_precisionpay'
    : '.precsionpay-block-checkout';
  }

  return {
    handleCheckoutError: handleCheckoutError,
    init: init,
    plaidStorage: SESSION_STORAGE_PLAID,
    precisionPayStorage: SESSION_STORAGE_PRECISION_PAY,
  };
}
