Shopify Checkout MFPP code

zip-checkout-mfpp.liquid

{% comment %}<!-- Zip MFPP -->{% endcomment %}
{% assign zip_mfpp_url = "https://gateway.quadpay.com/orders/calculate-merchant-fees" -%}
{% if zip_test -%}
{% assign zip_mfpp_url = "https://sandbox.gateway.quadpay.com/orders/calculate-merchant-fees" -%}
{% endif -%}
<script type="application/javascript">
// Zip Checkout MFPP - v1.2.0
{% assign zip_default_money_format = '${{amount}}' -%}
window.QuadPayCheckoutOptions = {
    merchantId: "{{zip_merchant_id}}",
    state: "{{checkout.shipping_address.province_code | default: checkout.billing_address.province_code}}",
    country: "{{checkout.shipping_address.country_code | default: checkout.billing_address.country_code}}",
    currency: "{{shop.currency}}",
    moneyFormat: "{{zip_money_format | default: zip_default_money_format}}",
    quadpayFeeApi: "{{ zip_mfpp_url }}",
    quadpayRegion: "{{ zip_region | default: "US" }}"
};

{% if zip_selector %}
window.QuadPayCheckoutOptions.quadpayGatewaySelector = "{{zip_selector}}";
{% endif %}

{% if order.attributes.zip-mfpp-amount -%}
{% comment %}/* Thank You page and paid with Zip. */{% endcomment %}
window.QuadPayCheckoutOptions.thankYouPageFee = {{ order.attributes.zip-mfpp-amount }};
{% endif -%}

/* global document, window */
window.QuadPayCheckout = {
    selectors: {
        orderSummaryTotalLines: '.total-line-table__tbody',
        paymentMethodSection: '.section--payment-method .section__content',
        total: '.total-line-table__footer .total-line__price .payment-due__price',
        totalRecap: '.total-recap__final-price',
        mainForm: "form[data-payment-form]",
        paymentMethodTransactions: ".payment-method-list__item__amount",
        orderAttributesInputName: "checkout[attributes][zip-mfpp-amount]",
    },
    quadpayFeeTotalLineClass: '.total-line--quadpay-fee',
    init: function(options, step) {
        this.options = options;

        if (step === 'payment_method') {
            this.initPaymentMethodStep();
        } else {
            this.initThankYouPage();
        }
    },
    initPaymentMethodStep: function() {
        const quadpayGatewaySelector = this.options.quadpayGatewaySelector || this.findGatewaySelector();
        if (!quadpayGatewaySelector) {
            const style = "background-color:white;font-weight:bold;";
            console.log(
                "%c[ZIP] Cannot find Zip gateway selector, please refer to documentation at: %chttps://docs.us.zip.co/docs/mfpp-shopify-plus",
                "color:#411361;" + style,
                "color:#6542be;" + style
            );
            return;
        }

        this.shopifyTotal = this.getShopifyTotal();
        this.monitorGatewaySelection(quadpayGatewaySelector);
        this.monitorCurrencySelection();
        this.refresh(true);
    },
    initThankYouPage: function() {
        let fee = this.options.thankYouPageFee;
        if (fee) {
            this.shopifyTotal = this.getShopifyTotal();
            this.setTotalLinePriceIncrease(fee, this.options.currency);

            this.setQuadPayLinePrice(fee);
        }
    },
    findGatewaySelector: function() {
		const identifyingElement = document.querySelector('[data-payment-icon="zip"]') ||
            Array.prototype.find.call(document.querySelectorAll('.radio__label__primary'), function (el) {
                return el.innerText.match(/(Zip|Quadpay|QuadPay)/);
            });

        if (!identifyingElement) {
			return null;
        }

      	const wrapperElement = this.findClosestParentElement(identifyingElement, ".radio-wrapper");
        if (!wrapperElement || !wrapperElement.dataset.selectGateway) {
          	return null;
        }

      	return "#checkout_payment_gateway_" + wrapperElement.dataset.selectGateway;
    },
    findClosestParentElement: function(element, selector) {
        if (element.closest && typeof element.closest === "function") {
            return element.closest(selector);
        }

        const findClosest = function (element, selector) {
            if (element.matches(selector)) {
                return element;
            } else if (!element.parentNode) {
                return null;
            }

            return findClosest(element.parentNode, selector);
        };

        return findClosest(element, selector);
    },
    refresh: function(reloadFee) {
        if (reloadFee === true) {
            this.fee = null;
        }
        this.getQuadPayFee().then((function (fee) {
            this.fee = fee;
            if (this.quadpayIsSelected === true) {
                this.repaintFee(this.fee);
                this.generateOrderAttributesInput(this.fee);
            } else {
                this.hideFee();
                this.removeOrderAttributesInput();
            }
        }).bind(this));
    },
    repaintFee: function(fee) {
        this.setTotalLinePriceIncrease(fee);
        this.setQuadPayLinePrice(fee);
    },
    hideFee: function() {
        this.setTotalLinePriceIncrease(0);
        this.setQuadPayLinePrice(null);
    },
    setTotalLinePriceIncrease: function(fee, currency) {
        const money = this.formatInCurrency(this.shopifyTotal + fee, currency);
        const selector = this.selectors.total + ',' + this.selectors.totalRecap + ',' + this.selectors.paymentMethodTransactions;
        document.querySelectorAll(selector).forEach(function(el) {
            el.innerHTML = money;
        });
    },
    setQuadPayLinePrice: function(fee) {
        let quadPayLine = document.querySelector(this.quadpayFeeTotalLineClass);
        if (quadPayLine) {
            quadPayLine.parentNode.removeChild(quadPayLine);
        }

        if (fee === null || fee < 0.01) {
            return;
        }

        this.generateQuadPayFeeLine(fee);
    },
    monitorGatewaySelection: function(quadpayGatewaySelector) {
        this.quadpayIsSelected = null;
        const gatewaySelected = (function() {
            const radio = document.querySelector(quadpayGatewaySelector);
            const isSelected = radio && radio.checked;
            const shouldRefresh = this.quadpayIsSelected !== null && this.quadpayIsSelected !== isSelected;
            this.quadpayIsSelected = isSelected;
            if (shouldRefresh) {
                this.refresh(false);
            }
        }).bind(this);
        const paymentMethodSection = document.querySelector(this.selectors.paymentMethodSection);
        if (!paymentMethodSection) {
            return;
        }
        paymentMethodSection.addEventListener('click', gatewaySelected);
        gatewaySelected();
    },
    monitorCurrencySelection: function() {
        const currencies = document.querySelector("#currencies");
        if (!currencies) {
            return;
        }

        currencies.addEventListener('change', (function () {
            this.refresh(false);
        }).bind(this));
    },
    generateQuadPayFeeLine: function(fee) {
        let subtotalsElement = document.querySelector(this.selectors.orderSummaryTotalLines);
        if (subtotalsElement) {
            subtotalsElement.insertAdjacentHTML('beforeend', this.generateLine(fee));
        }
    },
    removeOrderAttributesInput: function() {
    	document.getElementsByName(this.selectors.orderAttributesInputName).forEach(function (input) {
            try {
              	input.parentNode.removeChild(input);
            } catch (_) {}
        });
    },
    generateOrderAttributesInput: function(fee) {
        const orderAttributeInputs = document.getElementsByName(this.selectors.orderAttributesInputName);

        let orderAttributeInput = orderAttributeInputs.length > 0 ? orderAttributeInputs[0] : null;
        if (!orderAttributeInput) {
            const form = document.querySelector(this.selectors.mainForm);
            if (!form) {
                return;
            }

            orderAttributeInput = document.createElement("input");
            orderAttributeInput.setAttribute("type", "hidden");
            orderAttributeInput.setAttribute("name", this.selectors.orderAttributesInputName);
            form.appendChild(orderAttributeInput);
        }

        orderAttributeInput.setAttribute("value", fee);
    },
    generateLine: function(fee) {
        const money = this.formatInCurrency(fee);
        let className = this.quadpayFeeTotalLineClass.replace(/^\./, '');
        return '<tr class="total-line ' + className + '">' +
            '<th class="total-line__name" scope="row">Merchant Fee for Payment Plan</th>' +
            '<td class="total-line__price"><b>' + money + '</b></td>' +
            '</tr>';
    },
    getQuadPayFee: function() {
        if (this.fee !== null) {
            return Promise.resolve(this.fee);
        }
        return this.fetchFee().then(function(data) {
            if (!('merchantFeeForPaymentPlan' in data)) {
                return Promise.resolve(null);
            }
            let fee = parseFloat(data.merchantFeeForPaymentPlan) * 100;
            return Promise.resolve(fee);
        });
    },
    fetchFee: function() {
        let totalForQuadPay = this.shopifyTotal / 100;
        let dataForQuadPay = {
            merchantId: this.options.merchantId,
            currency: this.options.currency,
            customerCountry: this.options.country,
            amount: totalForQuadPay
        };
        if (this.options.state) {
            dataForQuadPay.customerState = this.options.state;
        }
        return window.fetch(this.options.quadpayFeeApi, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'QP-Territory': this.options.quadpayRegion
            },
            body: JSON.stringify(dataForQuadPay)
        }).then(function (response) {
            return response.json();
        });
    },
    getShopifyTotal: function() {
        let totalElement = document.querySelector(this.selectors.total);
        if (!totalElement) {
            return;
        }
        return parseInt(totalElement.getAttribute('data-checkout-payment-due-target'), 10);
    },
    formatInCurrency: function (amount, currency) {
        if (window.Currency) {
            const currentCurrency = currency || window.Currency.cookie.read();
            const total = window.Currency.convert(amount, window.Shopify.Checkout.currency || this.options.currency, currentCurrency);
            const moneyFormat = window.Currency.moneyFormats[currentCurrency][window.Currency.format];
            return window.Currency.formatMoney(total, moneyFormat);
        }

        return this.shopifyFormatMoney(amount);
    },
    shopifyFormatMoney: function(cents) {
        if (typeof cents == 'string') {
            cents = cents.replace('.','');
        }
        let value = '';
        let placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
        let formatString = this.options.moneyFormat;

        function defaultOption(opt, def) {
            return (typeof opt == 'undefined' ? def : opt);
        }

        function formatWithDelimiters(number, precision, thousands, decimal) {
            precision = defaultOption(precision, 2);
            thousands = defaultOption(thousands, ',');
            decimal   = defaultOption(decimal, '.');

            if (isNaN(number) || number == null) { return 0; }

            number = (number/100.0).toFixed(precision);
            let parts   = number.split('.'),
                dollars = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands),
                cents   = parts[1] ? (decimal + parts[1]) : '';

            return dollars + cents;
        }

        switch(formatString.match(placeholderRegex)[1]) {
            case 'amount':
                value = formatWithDelimiters(cents, 2);
                break;
            case 'amount_no_decimals':
                value = formatWithDelimiters(cents, 0);
                break;
            case 'amount_with_comma_separator':
                value = formatWithDelimiters(cents, 2, '.', ',');
                break;
            case 'amount_no_decimals_with_comma_separator':
                value = formatWithDelimiters(cents, 0, '.', ',');
                break;
        }

        return formatString.replace(placeholderRegex, value);
    }
};
(function() {
    let step = window.Shopify.Checkout.step;
    if (!step && window.QuadPayCheckoutOptions.thankYouPageFee) {
        step = 'thank_you';
    }
    if (['payment_method', 'thank_you'].indexOf(step) !== -1) {
        window.QuadPayCheckout.init(window.QuadPayCheckoutOptions, step);
    }
    // re-init on apply gift card and calculating taxes
    if (step === 'payment_method') {
        document.addEventListener('page:change', function () {
            window.setTimeout(function () {
                window.QuadPayCheckout.init(window.QuadPayCheckoutOptions, step);
            }, 500);
        });
    }
})();

</script>