CyberStore Documentation
ProcessPayment_Popup Widget

A widget designed to pop up a window that catpures and processes invoice payments. Introduced in v2.20.0. 

 Remarks

The ProcessPayment_Popup widget is used in conjunction with the BillingHistory_Grid widget and is used to popup a payment form that accepts credit card information for processing payments. The popup is activated by a shopper interaction selecting a pay action in the billing widget.

 

Payment by Bank Account (ACH) Method

The "Pay with Bank Account" option is available when there is a payment method linked to the Bank Account type and then the visitor can enter information about their bank account to directly request an ACH payment (US Only)

Payment by Credit Card Method

When first loaded, the contact information of the current logged in shopper is populated including the billing address of record for the Customer. The shopper may elect to change any or all of the information to match the requirements of the credit card in use. 
   
The card number field will validate that the number entered is a possible valid card by confirming the type of card (American Express, Visa, Mastercard or Discover) and that the number sequence passes industry standard Luhn checksum (Luhn Checksum). As the user enters their number, when the type of card is determined the appropriate credit card logo appears (grayed out) in the input box.

       

 When the user completes entering a card sequence and it passes validation the input box will colorize the card logo and the number will be formatted in the expected format for the card, and the CVN field placeholder matches the expected input length based on the card type (4 for Amex, 3 for Visa, Mastercard or Discover). 

The form must be validated before it can be submitted. The validation requires that all fields have data, that the email address is a plausible address, that the card type has been determined and the Luhn checksum passes, the month and year of expiration is a valid number from the list (year is populated with current year plus next 10), and the CVN value is the correct number of characters.

When the shopper clicks the Submit Payment button, a confirmation will be presented ensuring the action is desired. Upon clicking YES the payment will be processed using CyberStore's payment provider configuration. Clicking No does not submit the payment and returns the shopper to the payment popup. The payment, if approved will be issued as an Authorize and Capture event (even if the provider is set to Authorize Only) and does not add any value to the amount entered. 

If the payment is successful, the the process will apply payments to the invoices selected and confirm that to the user via a popup alert. Clicking OK on the alert closes the payment popup and reloads the billing grid. 

If the payment is declined or fails for any reason, the user will be presented an error with details, which when closing the popup returns them to the payment popup to make possible adjustments. 
 Widget Option Usage

Widget options provide a way to customize widgets in various ways. Widget options are sent to the widget when loaded with the LoadWidgetControl.

 

 Widget Conventions
 Example

See an example of how to load and configure this widget in SitePages.config.

<Control src="LoadWidgetControl.ascx" 
    FileLocation="ProcessPayment_Popup.html" 
    RequireLogin="true" 
/>
 Widget Source

The following is the source code for this widget.

Developer's Note:

To create a custom version of the widget, copy all of the code below into a file of the same name and place it into your Site's widgets folder (e.g., ../YourSiteFolder/Widgets). The CyberStore page engine will then override the default source with your customized version.

<div id="divWrapper_PaymentPopup">
    <div class="PaymentPopup"></div>
</div>
<style>
    .dx-dropdownlist-popup-wrapper {
        height: 100;
        width;
    }

    div#divWrapper_PaymentMethods {
        display: flex;
        flex-direction: column;
        min-width: 100px !important;
        margin: 10px 30px 10px 20px;
    }

    .btngroup .dx-box-flex.dx-box.dx-widget {
        flex-direction: row !important;
    }

    .btngroup .dx-item.dx-box-item {
        flex: 0 0 0 !important;
    }

    div#btnPayByBank,
    div#btnPayByCC {
        background: #fff;
        border: 1px solid gray;
        text-align: center;
        color: #000;
        margin-bottom: 20px;
        max-width: 130px;
    }

        div#btnPayByBank.dx-state-focused,
        div#btnPayByCC.dx-state-focused,
        div#btnPayByBank.active,
        div#btnPayByCC.active {
            background: #f0f0f0;
            border: 1px solid gray;
            text-align: center;
            color: #000;
            margin-bottom: 20px;
        }

        div#btnPayByBank .dx-button-content,
        div#btnPayByCC .dx-button-content {
            height: fit-content;
            height: -moz-fit-content;
            padding: 7px 18px;
        }

        div#btnPayByBank .dx-button-content {
            background: url(/ecommerce/images/bankAccount.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        div#btnPayByCC .dx-button-content {
            background: url(/ecommerce/images/creditCard.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        div#btnPayByBank.dx-state-focused .dx-button-content {
            background: url(/ecommerce/images/bankAccount-hover.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        /* NOTE */
        div#btnPayByBank.active .dx-button-content {
            background: url(/ecommerce/images/bankAccount-hover.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        /* NOTE */
        div#btnPayByCC.active .dx-button-content {
            background: url(/ecommerce/images/creditCard-hover.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        div#btnPayByCC.dx-state-focused .dx-button-content {
            background: url(/ecommerce/images/creditCard-hover.png);
            background-repeat: no-repeat;
            background-position: center top;
            background-size: 65px;
        }

        div#btnPayByBank span.dx-button-text,
        div#btnPayByCC span.dx-button-text {
            white-space: normal;
            line-height: 1em;
            margin-top: 60px;
            color:#000;
        }

    span.dx-button-text {
        display: inline-block;
    }

   .PaymentPopup .dx-popup-content, .paymentWrapper   .dx-popup-content/* , .dx-popup-content */{
        display: flex;
/*        flex-direction: row;*/
        justify-content: flex-start;
        height: fit-content !important;
    }

    .paymentWrapper .dx-popup-content {
        justify-content: space-around;
        display: flex;
    }

    #divWrapper_PaymentForm {
        width: 80;
    }

    .referenceField {
        margin-bottom: 40px;
    }

    #eCheckDisclaimer {
        width;
        margin: 20px auto;
    }

    #confirmationText {
        width: 85;
        margin: 20px auto;
        text-align: center;
        display: none;
    }

    #btnWrapper_PaymentPopup {
        text-align: center;
    }

    .dx-button-mode-text {
        background-color: transparent;
        border-color: #333;
        color: #333;
    }

        .dx-button-mode-text.dx-state-hover {
            background-color: #106e0b;
            border-color: transparent;
            color: #fff;
        }

        .dx-button-mode-text.dx-state-focused {
            background-color: #106e0b;
            border-color: transparent;
            color: #fff;
        }

    .card input {
        background-image: url('/ecommerce/images/icons/empty-curved-32px.png');
        background-position: 5px 5px;
        background-repeat: no-repeat;
        background-size: 40px;
        padding-top: 3px;
        padding-bottom: 3px;
        padding-left: 50px;
        padding-right: 5px;
        filter);
    }

    .card div.dx-placeholder {
        left: 42px !important;
        top: 4px;
    }


    .card input.amex {
        background-image: url('/ecommerce/images/icons/american-express-curved-32px.png') !important;
    }

    .card input.discover {
        background-image: url('/ecommerce/images/icons/discover-curved-32px.png') !important;
    }

    .card input.mastercard {
        background-image: url('/ecommerce/images/icons/mastercard-curved-32px.png') !important;
    }

    .card input.visa {
        background-image: url('/ecommerce/images/icons/visa-curved-32px.png') !important;
    }

    .card input.valid {
        filter: grayscale(0);
    }

    .dx-button-mode-text.dx-button-danger {
        background-color: #d9534f;
        border-color: transparent;
        color: #fff;
    }

        .dx-button-mode-text.dx-button-danger.dx-state-hover {
            background-color: #aa4340;
        }

        .dx-button-mode-text.dx-button-danger .dx-icon {
            color: #fafafa;
        }

    .dx-overlay-content.dx-popup-normal.dx-resizable {
        width !important;
        overflow: scroll;
    }

    .paymentSubmit {
        width: 155px;
        padding: 0 !important;
    }

    .paymentCancel {
        width: 155px;
        padding: 0 !important;
    }

    @media (max-width: 768px) {
        #divWrapper_PaymentForm {
            width: 95;
        }

        .dx-overlay-content.dx-popup-normal.dx-resizable {
            min-width: 400px;
        }

        .dx-popup-content,
        .PaymentPopup .dx-popup-content {
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
            padding: 0 15px !important;
            /* height: 85vh !important; */
            height: 95vh !important;
            overflow: scroll;
        }

        div#divWrapper_PaymentMethods {
            flex-direction: row;
            width;
            margin: 10px 15px 10px 0;
        }

        div#btnPayByBank,
        div#btnPayByCC {
            margin: 20px auto;
        }

            div#btnPayByBank .dx-button-content,
            div#btnPayByCC .dx-button-content {
                padding: 7px 5px 8px;
            }
    }
</style>

<script type="text/javascript">
    /*'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' Widget:  ProcessPayment_Popup.html
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' (c) 2021 by Dovetail Internet Technologies, LLC
     '              www.dovetailinternet.com
     '              info@dovetailinternet.com
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' All rights reserved. Not to be used without permission.
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' Development Date: May - September 2021
     ' Release Version:  2.20.0
     ' Revision:         2.0
     ' Last Modified:    10/6/2021
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' Purpose: dspalay invoice payment options to a shopper
     '          and process selected payments by credit card
     '          or ACH based on provider options and configuration
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' Usage:   Requires B2B login shopper context
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
     ' Example SitePages usage:

         <Control src="LoadWidgetControl.ascx"
             FileLocation="ProcessPayment_Popup.html"
             RequireLogin="true"
         />
     '
     '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''  */
    //
    // GLOBAL scope -- variables and methods that may be required from other scripts outside this widget
    //

    // load some global script pre-requisites
    $(function () {
        LoadScript(SITE_PATHS.application + '/Site/Shop/js/geographicData.js');
        LoadScript(SITE_PATHS.application + '/js/jquery-creditcardvalidator/jquery.creditCardValidator.js');
    });

    var PaymentPopup = null,
        PaymentForm = null,
        EditAmount = null,
        InvoiceTotal = 0,
        Payment = {
            Amount: 0,
            Reference: 'Payment on Account',
            Number: '',
            CClastFour: '',
            Type: '',
            Verification: '',
            Month: '',
            Year: '',
            Address1: '',
            Address2: '',
            City: '',
            State: '',
            ZipCode: '',
            Country: '',
            FirstName: '',
            LastName: '',
            Email: '',
            Telephone: '',
            BankAccountType: '',
            BankTypeOfAccount: '',
            SelectedPaymentType: '',
            BaNameOnAccount1: '',
            BaNameOnAccount2: '',
            BaRoutingNumber: '',
            BaAccountNumber: '',
            AccountProcessedNumber: '',
            AccountProcessedNameFirst: '',
            AccountProcessedNameLast: '',
            BaEmailOnAccount: '',
            AddedInvoicePaymentFee: { Amount: 0.00, PaymentMethod_ID: '', Description: '' }
        },
        Years = GetYears(),
        Months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];

    var DisclaimerText = 'By checking the "I Agree" checkbox below, you authorize the information you ve provided on the above account to be used for the creation of a charge to the account listed above.  You also affirm that the information you provided is correct, that you are a signer on the account above and there are available funds to cover the amount of any transactions that you authorize.  If your payment cannot be completed for any reason, including insufficient funds or error in the information which you submitted, you will retain the same liability, which is your sole responsibility, for payment as though you had not attempted to make the payment.';

    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Shows the PaymentPopup.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function ShowPaymentPopup(amount, allowEditPayment) {
        Payment.Amount = amount;
        InvoiceTotal = amount;
        EditAmount = allowEditPayment;
        if (PaymentPopup) {
            $('.PaymentPopup').remove();
        }

        if (PreferredPaymentType !== 'BA' && PreferredPaymentType !== 'CC') {
            return;
        }

        var $paymentPopupContainer = $('<div />')
            .addClass('PaymentPopup')
            .appendTo($('#divWrapper_PaymentPopup'));

        PaymentPopup = $paymentPopupContainer.dxPopup({
            contentTemplate: $('#tmplPaymentPopup'),
            wrapperAttr: {
                class: "paymentWrapper"
            },
            animation: {
                show: {
                    type: 'slideIn',
                    duration: 600,
                    direction: 'top'
                },
                hide: {
                    type: 'slideOut',
                    duration: 500,
                    direction: 'top'
                }
            },
            title: 'Payment Information',
            showTitle: (widgetOptions.popupShowTitle != null)
                ? widgetOptions.popupShowTitle
                : true,
            showCloseButton: (widgetOptions.popupShowCloseButton != null)
                ? widgetOptions.popupShowCloseButton
                : true,
            closeOnOutsideClick: (widgetOptions.popupCloseOnOutsideClick != null)
                ? widgetOptions.popupCloseOnOutsideClick
                : true,
            width: widgetOptions.popupWidth || null,
            height: widgetOptions.popupHeight || null,
            minWidth: widgetOptions.popupMinWidth || null,
            minHeight: widgetOptions.popupMinHeight || 700,
            maxWidth: widgetOptions.popupMaxWidth || 850,
            maxHeight: widgetOptions.popupMaxHeight || 800,
            dragEnabled: (widgetOptions.popupEnableDrag != null)
                ? widgetOptions.popupEnableDrag
                : true,
            resizeEnabled: (widgetOptions.popupEnableResize != null)
                ? widgetOptions.popupEnableResize
                : true,
            shading: (widgetOptions.popupModal != null)
                ? widgetOptions.popupModal
                : false,

            position: 'center center',

            onShowing: function (e) {
                BuildPaymentForm();
            },
            onHiding: function (e) {
                $('#confirmationText').remove;
                InvoiceGrid.deselectAll();
            }
        }).dxPopup("instance");


        PaymentPopup.show();
    };


    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Complete clears the payment popup form.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function ClearPaymentForm() {
        $('#divWrapper_PaymentForm').dxForm('dispose');
        PaymentForm.resetValues;
    }

    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     returns an Array of States based on the countryCode.
    /// </summary>
    ///
    /// <param name='countryCode'>
    ///     countryCode inthis case currency CountyCode
    /// </param>
    ///
    /// <returns>
    ///     returns an Array of States
    /// </returns>
    ///-------------------------------------------------------------------------------------------------
    function GetStateStoreByCountry(CountryCode) {
        try {
            var statesArr;
            var vCountries = [];
            var vCountries = _CountryStore._array.filter(getVisibleCountries => getVisibleCountries.UseCountry == true);

            if (!vCountries.some(item => item.CountryCode === CountryCode.toUpperCase())) {
                statesArr = [];
                return statesArr;
            }
            if (CountryCode === null || CountryCode == "undefined" || CountryCode === "") {
                statesArr = [];
                return statesArr;
            }
            var c = GetObjectFromArrayByKeyValue(_CountryStore._array, 'CountryCode', CountryCode.toUpperCase());
            if (c[0].States.length > 0) {
                allStatesArr = c[0].States;
                statesArr = allStatesArr.filter(getvisibleStates => getvisibleStates.UseState == true);
            } else {
                statesArr = [];
            }
            return statesArr;
        } catch (err) {
            statesArr = [];
            return statesArr;
        }
    }

    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Builds payment form.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function BuildPaymentForm() {

        PaymentForm = null;
        var ValidationMsg = '';

        if (PreferredPaymentType !== 'BA' && PreferredPaymentType !== 'CC') {
            return;
        }

        PaymentForm = $('#divWrapper_PaymentForm').dxForm({
            validationGroup: 'paymentInfoValidation',
            items: [
                {//SECTION 1
                    itemType: 'group',
                    cssClass: 'inputRow',
                    name: 'section1',
                    colCount: 1,
                    items: [
                        {
                            // If only 1 invoice is selected then the payment amount can not exceed the total due for that invoice
                            dataField: 'amount',
                            validationRules: [{
                                type: 'required',
                                message: 'The payment ammount is required.'
                            },
                            {
                                type: "custom",
                                reevaluate: true,
                                validationCallback: function (e) {
                                    var realVal = e.value.replace('$', '');
                                    //remove unneeded string chars so the value can be compaired to 0
                                    realVal = Number(realVal.replace(/[^0-9\.]+/g, ""));
                                    return (realVal > 0);
                                },
                                message: "The payment amount must be greater than $1.00."
                            }
                            ],
                            editorOptions: {
                                value: FormatCurrency(Payment.Amount),
                                // read only if it can't be edited
                                readOnly: !EditAmount,
                                onChange: function (e) {
                                    // Get number for compare
                                    var inputVal = e.component._options._optionManager._options.value.replace('$', '');
                                    inputVal = inputVal.replace(',', '');
                                    inputVal = Number(inputVal.replace(/[^0-9\.]+/g, ""));
                                    //if inputVal <= Payment.Amount then update the payment amount
                                    if (inputVal <= InvoiceTotal) {
                                        Payment.Amount = inputVal;
                                    }
                                    // if input is larger than actual owed then revert to actual owed no change to Payment.Amount
                                    if (inputVal > InvoiceTotal) {
                                        Payment.Amount = InvoiceTotal;
                                    }
                                    if (InvoicesToPay.length == 1) {
                                        InvoicesToPay[0].AmountToPay = Payment.Amount;
                                    }
                                },
                                onFocusOut: function (e) {
                                    e.component.option('value', FormatCurrency(Payment.Amount));
                                    checkForProcessingFee(Payment);
                                }
                            }
                        },
                        //Display fee if needed for InvoicePaymentFee
                        {
                            editorType: 'dxTextBox',
                            name: 'FeeBox',
                            visible: false,
                            label: {
                                location: 'left'
                            },
                            editorOptions: {
                            }
                        },
                        {
                            editorType: 'dxTextBox',
                            label: {
                                location: 'left',
                                text: 'Reference',
                            },
                            editorOptions: {
                                readOnly: true,
                                name: 'Reference',
                                value: function (e) {
                                    var invoiceNumber = InvoicesToPay.map((item) => { return item.Invoice })
                                    var invoiceNumberStr = invoiceNumber.join();
                                    return invoiceNumberStr;
                                },
                            }
                        }
                    ]
                },
                {//SECTION 2
                    itemType: 'group',
                    name: 'PayByCredit',
                    colCount: 1,
                    items: [{
                        itemType: 'group',
                        cssClass: 'inputRow',
                        name: 'section2',
                        colCount: 1,
                        items: [
                            {
                                dataField: 'CardHolder',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left'
                                },
                                validationRules: [{ type: 'required', message: 'The name of the card holder is required.' }],
                                editorOptions: {
                                    value: (Payment.FirstName + ' ' + Payment.LastName).trim(),
                                    onValueChanged: function (e) {
                                        Payment.FirstName = e.value.substring(0, e.value.indexOf(' ')).trim();
                                        Payment.LastName = e.value.substring(Payment.FirstName.length).trim();
                                    }
                                }
                            },
                            {
                                dataField: 'Email',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left',
                                    text: 'Receipt Email'
                                },
                                validationRules: [{
                                    type: 'required',
                                    message: 'The email address is required.'
                                },
                                {
                                    type: 'email',
                                    message: 'The email address is not valid.'
                                }
                                ],
                                editorOptions: {
                                    value: Payment.Email,
                                    onValueChanged: function (e) {
                                        Payment.Email = e.value;
                                    }
                                }
                            },
                            {
                                dataField: 'Address1',
                                editorType: 'dxTextBox',
                                validationRules: [{
                                    type: 'required', message: 'The address is required.'
                                }],
                                label: {
                                    location: 'top',
                                    text: 'Address'
                                },
                                editorOptions: {
                                    value: Payment.Address1,
                                    onValueChanged: function (e) {
                                        Payment.Address1 = e.value;
                                    },
                                }
                            },
                            {
                                dataField: 'Address2',
                                editorType: 'dxTextBox',
                                label: {
                                    visible: false,
                                },
                                editorOptions: {
                                    value: Payment.Address2,
                                    onValueChanged: function (e) {
                                        Payment.Address2 = e.value;
                                    },
                                }
                            }
                        ]
                    },
                    {//SECTION 3  group of items ,city,state,zip
                        itemType: 'group',
                        name: 'section3',
                        cssClass: 'inputRow',
                        colCount: 3,
                        items: [
                            {
                                dataField: 'City',
                                editorType: 'dxTextBox',
                                validationRules: [{ type: 'required', message: 'The city is required.' }
                                ],
                                label: {
                                    location: 'top',
                                    text: 'City'
                                },
                                editorOptions: {
                                    value: Payment.City,
                                    onValueChanged: function (e) {
                                        Payment.City = e.value;
                                    },
                                }
                            },
                            {
                                dataField: 'State',
                                editorType: 'dxSelectBox',
                                validationRules: [],
                                label: {
                                    location: 'top',
                                    text: 'State'
                                },
                                editorOptions: {
                                    dataSource: {
                                        store: GetStateStoreByCountry(_Currency.CountryCode),
                                        pageSize: 5,
                                        paginate: true,
                                    },
                                    elementAttr: {
                                        id: 'stateSelectId',
                                    },
                                    inputAttr: {
                                        autocomplete: 'test-autocomplete'
                                    },
                                    displayExpr: 'StateName',
                                    valueExpr: 'StateCode',
                                    searchEnabled: true,
                                    onValueChanged: function (e) {
                                        Payment.State = e.value;
                                    },
                                    onInitialized: function (e) {
                                        e.component.option('value', Payment.State);
                                    }
                                }
                            },
                            {
                                dataField: 'Zip',
                                editorType: 'dxTextBox',
                                validationRules: [{ type: 'required', message: 'The zip code is required.' },
                                { type: 'stringLength', min: 4, message: 'The zip code must be at least 4 digits long.' }
                                ],
                                label: {
                                    location: 'top',
                                    text: 'Zip'
                                },
                                editorOptions: {
                                    value: Payment.ZipCode,
                                    onValueChanged: function (e) {
                                        Payment.ZipCode = e.value;
                                    },
                                }
                            },
                        ]
                    },
                    {//SECTION 4  group of items country, phone
                        itemType: 'group',
                        name: 'section4',
                        cssClass: 'inputRow',
                        colCount: 2,
                        items: [{
                            dataField: 'Country',
                            editorType: 'dxSelectBox',
                            validationRules: [{ type: 'required', message: 'The country is required.' }],
                            label: {
                                location: 'top',
                                text: 'Country'
                            },
                            editorOptions: {
                                dataSource: {
                                    store: _CountryStore._array.filter(getVisibleCountries => getVisibleCountries.UseCountry == true),
                                    pageSize: 5,
                                    paginate: true,
                                },
                                inputAttr: {
                                    autocomplete: 'test-autocomplete'
                                },
                                displayExpr: 'CountryName',
                                valueExpr: 'CountryCode',
                                searchEnabled: true,
                                onValueChanged: function (e) {
                                    Payment.Country = e.value;
                                    PaymentForm.getEditor('State').reset();
                                    PaymentForm.getEditor('State').option('dataSource', GetStateStoreByCountry(e.value));
                                },
                                onInitialized: function (e) {
                                    var c = GetObjectFromArrayByKeyValue(_CountryStore._array, 'CountryCode', _Currency.CountryCode.toUpperCase());
                                    e.component.option('value', c[0].CountryCode.toUpperCase());
                                    Payment.Country = c[0].CountryCode.toUpperCase();
                                }
                            }
                        },
                        {
                            dataField: 'Phone',
                            editorType: 'dxTextBox',
                            validationRules: [{ type: 'required', message: 'The telephone number is required.' },
                            { type: 'stringLength', min: 10, message: 'Phone number must be 10 or more digits long.' }
                            ],
                            label: {
                                location: 'top',
                                text: 'Phone'
                            },
                            editorOptions: {
                                maskChar: ' ',
                                mask: '(###) ###-####',
                                maskRules: {
                                    '#': /[01-9]/
                                },
                                maskInvalidMessage: 'Phone number must be 10 or more digits long.',
                                useMaskedValue: false,
                                value: Payment.Telephone,
                                onValueChanged: function (e) {
                                    Payment.Telephone = e.value;
                                },
                            }
                        }]
                    },
                    {//SECTION 5 Card number
                        itemType: 'group',
                        name: 'section5',
                        colCount: 1,
                        items: [{
                            dataField: 'CardNumber',
                            editorType: 'dxTextBox',
                            label: {
                                location: 'left'
                            },
                            editorOptions: {
                                showClearButton: true,
                                placeholder: '0000 0000 0000 0000',
                                elementAttr: {
                                    id: 'txtCreditCardNumber',
                                    class: 'card'
                                },
                                onValueChanged: function (e) {
                                    Payment.Number = e.value;
                                    if (e.value.length > 3) {
                                        Payment.CClastFour = e.value.substring(e.value.length - 4);
                                    } else {
                                        Payment.CClastFour = 'XXXX';
                                    }
                                }
                            },
                        }]
                    },
                    {//SECTION 6  group of items month year cvn
                        itemType: 'group',
                        name: 'section6',
                        colCount: 3,
                        items: [{
                            dataField: 'CardMonth',
                            editorType: 'dxAutocomplete',
                            validationRules: [{
                                type: 'required',
                                message: 'The expiration month is required.'
                            },
                            {
                                type: 'custom',
                                reevaluate: true,
                                validationCallback: function (e) {
                                    return _.contains(Months, e.value);
                                },
                                message: 'The expiration month must be between 01 and 12.'
                            }
                            ],
                            label: {
                                name: 'monthLbl',
                                location: 'left',
                                text: 'Expiration'
                            },
                            editorOptions: {
                                dataSource: Months,
                                placeholder: 'MM',
                                searchEnabled: true,
                                maxLength: 2,
                                onValueChanged: function (e) {
                                    Payment.Month = e.value;
                                }
                            }
                        },
                        {
                            dataField: 'CardYear',
                            editorType: 'dxAutocomplete',
                            validationRules: [{
                                type: 'required',
                                message: 'The expiration year is required.'
                            },
                            {
                                type: 'custom',
                                reevaluate: true,
                                validationCallback: function (e) {
                                    if (isNaN(e.value)) {
                                        return false;
                                    }
                                    return _.contains(Years, parseInt(e.value));
                                },
                                message: 'The expiration year is invalid.'
                            }],
                            label: {
                                location: 'left',
                                text: "/",
                                showColon: false,
                            },
                            editorOptions: {
                                items: Years,
                                placeholder: 'YY',
                                searchEnabled: true,
                                maxLength: 2,
                                onValueChanged: function (e) {
                                    Payment.Year = e.value;
                                }
                            }
                        },
                        {
                            dataField: 'CVN',
                            editorType: 'dxTextBox',
                            validationRules: [{ type: 'required', message: 'The CVN is required.' }],
                            label: {
                                location: 'left'
                            },
                            editorOptions: {
                                placeholder: 'XXX',
                                maskChar: 'X',
                                mask: '000',
                                onValueChanged: function (e) {
                                    Payment.Verification = e.value;
                                }
                            }
                        }],
                    }
                    ]
                },
                {//SECTION 7  -  Bank Fields
                    itemType: 'group',
                    name: 'PayByBank',
                    colCount: 1,
                    items: [{
                        itemType: 'group',
                        name: 'bankFields',
                        colCount: 1,
                        items: [
                            {
                                dataField: 'AccountType',
                                editorType: 'dxRadioGroup',
                                label: {
                                    location: 'left',
                                },
                                validationRules: [{ type: 'required', message: 'The account type is required. Business or Personal' }],
                                editorOptions: {
                                    dataSource: ['Business', 'Personal'],
                                    layout: 'horizontal',
                                    onValueChanged: function (e) {
                                        Payment.BankTypeOfAccount = e.value.toUpperCase();
                                    },
                                },
                            },
                            {
                                dataField: 'NameOnAccount',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left'
                                },
                                validationRules: [{
                                    type: 'required', message: 'The name on the account is required.'
                                }],
                                editorOptions: {
                                    value: (Payment.BaNameOnAccount1 + ' ' + Payment.BaNameOnAccount2).trim(),
                                    onValueChanged: function (e) {
                                        Payment.BaNameOnAccount1 = e.value.substring(0, e.value.indexOf(' ')).trim();
                                        Payment.BaNameOnAccount2 = e.value.substring(Payment.BaNameOnAccount1.length).trim();
                                    }

                                }
                            },
                            {
                                dataField: 'EmailOnAccount',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left',
                                    text: 'Receipt Email'
                                },
                                validationRules: [{ type: 'required', message: 'The email address is required' }],
                                editorOptions: {
                                    value: Payment.Email,
                                    onValueChanged: function (e) {
                                        Payment.BaEmailOnAccount = e.value;
                                    }
                                }
                            },
                            {
                                dataField: 'BankingType',
                                editorType: 'dxRadioGroup',
                                label: {
                                    location: 'left',
                                },
                                validationRules: [{ type: 'required', message: 'The banking type is required. Checking or Savings' }],
                                editorOptions: {
                                    dataSource: ['Checking', 'Savings'],
                                    layout: 'horizontal',
                                    onValueChanged: function (e) {
                                        Payment.BankAccountType = e.value.toUpperCase();
                                    },
                                },
                            },
                            {
                                dataField: 'routingNumber',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left'
                                },
                                validationRules: [{ type: 'required', message: 'The routing number is required.' }],
                                editorOptions: {
                                    onValueChanged: function (e) {
                                        Payment.BaRoutingNumber = e.value;
                                    }
                                }
                            },
                            {
                                dataField: 'accountNumber',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left'
                                },
                                validationRules: [{ type: 'required', message: 'The account number is required.' }],
                                editorOptions: {
                                    onValueChanged: function (e) {
                                        Payment.BaAccountNumber = e.value;
                                    }
                                }
                            },
                            {
                                dataField: 'confirmAccountNumber',
                                editorType: 'dxTextBox',
                                label: {
                                    location: 'left'
                                },
                                validationRules: [
                                    {
                                        type: "compare", comparisonTarget: function () {
                                            var bankAccountNumber = PaymentForm.getEditor('accountNumber');
                                            if (bankAccountNumber) {
                                                return bankAccountNumber.option("value");
                                            }
                                        }, message: "'Account Number' and 'Confirm Account Number' do not match."
                                    },
                                    {
                                        type: 'required', message: 'Please confirm the account number.'
                                    }],
                                editorOptions: {
                                    onValueChanged: function (e) {

                                    }
                                }
                            },
                            {
                                dataField: "picture",
                                template: function (data, itemElement) {
                                    itemElement.append("<img src='/ecommerce/images/check.jpg'>");
                                },
                                label: {
                                    location: 'left',
                                    visible: false
                                },

                            }],
                    },
                    {//SECTION disclaimer section
                        itemType: 'group',
                        name: 'disclaimer',
                        colCount: 1,
                        items: [
                            {
                                editorType: 'dxTextArea',
                                editorOptions: {
                                    value: DisclaimerText,
                                    readOnly: true,
                                    height: 90
                                }

                            },
                            {
                                dataField: "Accepted",
                                editorType: 'dxCheckBox',
                                label: {
                                    visible: false
                                },
                                editorOptions: {
                                    text: 'I Agree',
                                    onValueChanged: function (e) {
                                    }
                                }
                                , validationRules: [{
                                    type: "compare",
                                    comparisonTarget: function () { return true; },
                                    message: "You must agree to the Terms and Conditions"
                                }]
                            }]
                    }
                    ]
                },
                ///////////////////////////
                {//SECTION btnSection
                    itemType: 'group',
                    cssClass: "btngroup",
                    name: 'btnSection',
                    direction: 'row',
                    // colCount: 2,
                    items: [{
                        itemType: 'button',
                        cssClass: 'paymentSubmit',
                        name: 'subBtn',
                        validationGroup: 'paymentInfoValidation',
                        // horizontalAlignment: 'left',
                        buttonOptions: {
                            text: 'Submit Payment',
                            type: 'default',
                            useSubmitBehavior: false,
                            elementAttr: {
                                style: 'margin-top:30px;'
                            },
                            onClick: function (e) {
                                ValidationMsg = '';
                                var result = e.validationGroup.validate();
                                if (result) {

                                    //if the form validates and the fields are valid then process the payment
                                    if (result.isValid) {
                                        var msg = '';
                                        var paymentEmail = Payment.Email;
                                        if (Payment.SelectedPaymentType == 'CC') {
                                            Payment.AccountProcessedNumber = Payment.Number;
                                            Payment.AccountProcessedNameFirst = Payment.FirstName;
                                            Payment.AccountProcessedNameLast = Payment.LastName;

                                            if (Payment.AddedInvoicePaymentFee.Amount > 0) {
                                                msg = String.Format('Do you want to proceed with a charge to your card ending {0} in the amount of {1} <br> plus a {2} of {3} as payment to your open account balance.', Payment.CClastFour, FormatCurrency(Payment.Amount), Payment.AddedInvoicePaymentFee.Description, FormatCurrency(Payment.AddedInvoicePaymentFee.Amount));
                                            }
                                            if (Payment.AddedInvoicePaymentFee.Amount === 0) {
                                                msg = String.Format('Do you want to proceed with a charge to your card ending {0} in the amount of {1} as payment to your open account balance.', Payment.CClastFour, FormatCurrency(Payment.Amount));
                                            }
                                        }
                                        if (Payment.SelectedPaymentType == 'BA') {
                                            // {RoutingNumber}::{AccountNumber}::{HolderType}::{AccountType} e.g. 123456789::0000987654321::BUSINESS::SAVINGS
                                            Payment.AccountProcessedNumber = String.Format('{0}::{1}::{2}::{3}', Payment.BaRoutingNumber, Payment.BaAccountNumber, Payment.BankTypeOfAccount, Payment.BankAccountType);
                                            Payment.AccountProcessedNameFirst = Payment.BaNameOnAccount1;
                                            Payment.AccountProcessedNameLast = Payment.BaNameOnAccount2;
                                            paymentEmail = Payment.BaEmailOnAccount;
                                            msg = String.Format('Do you want to proceed with a payment from your {0} account in the amount of {1} as payment to your open account balance.', Payment.BankAccountType, FormatCurrency(Payment.Amount));
                                        }

                                        var dialog = DevExpress.ui.dialog.confirm(msg, 'Confirm Payment');
                                        dialog.done(function (confirmed) {
                                            if (confirmed) {
                                                // user clicked OK so go ahead and process the payment, otherwise, do nothing.
                                                let options = {};
                                                if (Payment.AddedInvoicePaymentFee.Amount > 0) {
                                                    options.PaymentFee = Payment.AddedInvoicePaymentFee.Amount;
                                                    options.PaymentMethod_ID = Payment.AddedInvoicePaymentFee.PaymentMethod_ID ?? '';
                                                }
                                                PaymentForm.option('disabled', true);
                                                BillingLoadPanel.option('message', 'Processing Transaction...');
                                                BillingLoadPanel.show();

                                                MakeAJAXCall("Billing.PayCustomerInvoices", {
                                                    InvoicesToPay: InvoicesToPay,
                                                    PaymentAmount: Payment.Amount,
                                                    CustomerReference: '',
                                                    CreditCardNumber: Payment.AccountProcessedNumber,
                                                    CreditCardType: Payment.Type,
                                                    CreditCardVerification: Payment.Verification,
                                                    CreditCardExpiration: Payment.Month + '/20' + Payment.Year,
                                                    CreditCardAddress: (Payment.Address1 + ' ' + Payment.Address2).trim(),
                                                    CreditCardCity: Payment.City,
                                                    CreditCardState: Payment.State,
                                                    CreditCardZipCode: Payment.ZipCode,
                                                    CreditCardCountry: Payment.Country,
                                                    CreditCardFirstName: Payment.AccountProcessedNameFirst,
                                                    CreditCardLastName: Payment.AccountProcessedNameLast,
                                                    Email: paymentEmail,
                                                    Telephone: Payment.Telephone,
                                                    //TODO Confirm Options
                                                    Options: options
                                                }, function (DATA) {
                                                    if (ValidAJAXResponse(DATA)) {
                                                        var response = JSON.parse(DATA);
                                                        if (response.Result.Success) {
                                                            BillingLoadPanel.hide();
                                                            PaymentPopup.hide();
                                                            ClearPaymentForm()

                                                            var alertResult = DevExpress.ui.dialog.alert(String.Format('{0} Your transaction reference number is {1}.', response.Result.Message, response.Data.TransactionID), 'Payment Successful');

                                                            alertResult.done(function () {
                                                                BillingLoadPanel.option('message', 'Updating invoice history...');
                                                                BillingLoadPanel.show();

                                                                LoadAccountDetails();
                                                            });
                                                        } else {
                                                            setTimeout(function () {
                                                                PaymentForm.option('disabled', false);
                                                                DevExpress.ui.dialog.alert(response.Result.Message, 'Payment Error');
                                                                BillingLoadPanel.hide();
                                                            }, 500);
                                                        }
                                                    }
                                                });
                                            }
                                        })
                                    }
                                    else { // form input is not valid tell user
                                        $.each(result.brokenRules, function () {
                                            ValidationMsg += '<p>' + this.message + '</p>';
                                        });
                                        alertResult = DevExpress.ui.dialog.alert(ValidationMsg,
                                            'Missing Information');
                                    }
                                }
                            }
                        }
                    },
                    {
                        itemType: 'button',
                        // horizontalAlignment: 'right',
                        cssClass: 'paymentCancel',
                        buttonOptions: {
                            text: 'Cancel',
                            stylingMode: 'text',
                            type: 'danger',
                            useSubmitBehavior: false,
                            icon: 'clear',
                            elementAttr: {
                                style: 'margin-top:30px;flex-direction:row;'
                            },
                            onClick: function () {
                                PaymentPopup.hide();
                                ClearPaymentForm();
                            }
                        }
                    }]
                }],

        }).dxForm('instance');

        $("#btnPayByCC").dxButton({
            text: "Pay with Credit Card",
            visible: IsPaymentBtnVisible(PaymentTypes, 'CC'),
            type: 'danger',
            onClick: function (e) {
                PayWithCreditCard();
            },

            onContentReady: function (e) {
            },
            onInitialized: function (e) {
                if (PreferredPaymentType == 'CC') {
                    PayWithCreditCard();
                }
            }
        });

        $("#btnPayByBank").dxButton({
            visible: IsPaymentBtnVisible(PaymentTypes, 'BA'),
            text: "Pay with Bank Account",
            type: 'danger',
            onClick: function (e) {
                PayWithBankAccount();
            },
            onInitialized: function (e) {
                if (PreferredPaymentType == 'BA') {
                    PayWithBankAccount();
                }
            }
        });
    }

    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Is the payment type button visable.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function IsPaymentBtnVisible(PaymentTypes, btnString) {
        for (let i = 0; i < PaymentTypes.length; i++) {
            if (PaymentTypes[i].MethodType == btnString) {
                return true;
            }
        }
        return false;
    }

    /**================================================================================================
     **                                      checkForProcessingFee
     *?  gets the Fee data. Add Fee data to Payment{} ?
     *@param Payment JSON
     *@return JSON in displayProcessingFee()
     *================================================================================================**/
    function checkForProcessingFee({ Amount, SelectedPaymentType }) {
        try {
            const Found = PaymentTypes.find(e => e.MethodType === SelectedPaymentType);
            if (!_.isEmpty(Found.InvoicePaymentFee)) {
                MakeAJAXCall("Billing.GetInvoicePaymentFee", { paymentAmount: Amount, paymentMethod_ID: Found.PaymentMethod_ID },
                    function (DATA) {
                        if (ValidAJAXResponse(DATA)) {
                            let data = JSON.parse(DATA);
                            if (data.Result.Success) {
                                Payment.AddedInvoicePaymentFee.Amount = data?.Data?.InvoicePaymentFee?.Amount ?? 0;
                                Payment.AddedInvoicePaymentFee.PaymentMethod_ID = Found.PaymentMethod_ID ?? '';
                                Payment.AddedInvoicePaymentFee.Description = data?.Data?.InvoicePaymentFee?.Description ?? '';
                                displayProcessingFee(data?.Data?.InvoicePaymentFee);
                            }
                            if (!data.Result.Success) {
                                console.log(data?.Data?.Result?.Message ?? 'Ajax call Billing.GetInvoicePaymentFee returned false with no Result.Message')
                            }
                        }
                    }
                )
            }
            if (_.isEmpty(Found.InvoicePaymentFee)) {
                //NOTE  hide servfield display amount
                Payment.AddedInvoicePaymentFee.Amount = 0.00;
                Payment.AddedInvoicePaymentFee.PaymentMethod_ID = '';
                Payment.AddedInvoicePaymentFee.Description = '';
                PaymentForm.itemOption('section1.FeeBox', { visible: false });

            }
        } catch (error) {
            console.log(error)
        }
    }
    /**================================================================================================
     **                                      displayProcessingFee
     *?  shows or hides the Processing fee on the payment popup. Set defaults { 0 & ''}?
     *@param InvoicePaymentFee JSON
     *================================================================================================**/
    function displayProcessingFee({ Amount = 0.00, Description = 'Processing Fee' } = {}) {
        PaymentForm.itemOption('section1.FeeBox', { label: { text: Description } });
        PaymentForm.itemOption('section1.FeeBox', { editorOptions: { value: FormatCurrency(Amount) } });
        PaymentForm.itemOption('section1.FeeBox', { visible: true });
        PaymentForm.itemOption('section1.amount', { editorOptions: { value: FormatCurrency(Payment.Amount) } });
        PaymentForm.itemOption('section1.FeeBox', { editorOptions: { readOnly: true } });
    }




    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Pay with bank account.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function PayWithBankAccount() {
        Payment.SelectedPaymentType = 'BA';
        checkForProcessingFee(Payment);

        $('#btnPayByBank').addClass('active');
        $('#btnPayByCC').removeClass('active');

        //Show correct fields
        PaymentForm.itemOption('PayByBank', { visible: true });
        PaymentForm.itemOption('PayByCredit', { visible: false });
    }

    ///-------------------------------------------------------------------------------------------------
    /// <summary>
    ///     Pay with credit card.
    /// </summary>
    ///-------------------------------------------------------------------------------------------------
    function PayWithCreditCard() {
        Payment.SelectedPaymentType = 'CC';
        checkForProcessingFee(Payment);
        $('#btnPayByCC').addClass('active');
        $('#btnPayByBank').removeClass('active');

        //Show the correct fields
        PaymentForm.itemOption('PayByCredit', { visible: true });

        //Remove the unneeded fields
        PaymentForm.itemOption('PayByBank', { visible: false });

        // get some editors
        var ccEditor = PaymentForm.getEditor('CardNumber');
        var cvnEditor = PaymentForm.getEditor('CVN');


        ccInput = $('#txtCreditCardNumber').find('input');

        // Setup the credit card pre-submit validation using the jquery-creditcardvalidator
        ccInput.validateCreditCard(function (result) {
            if (result) {
                if (result.card_type !== null && result.card_type !== '') {
                    var val = this.val();
                    Payment.Type = (result.card_type.name == 'amex') ? 'American Express' : result.card_type.name;
                    ccInput.addClass(result.card_type.name);
                    ccEditor.option("isValid", result.valid);

                    if (result.valid) {
                        var newVal = val.replace(/\s+/g, '');
                        ccInput.addClass('valid');

                        if (result.card_type.name == 'visa') {
                            if (val.length == 16) {
                                ccEditor.option('mask', '0000 0000 0000 0000');
                            }
                            if (val.length == 13) {
                                ccEditor.option('mask', '0000 0000 000 00');
                            }
                            cvnEditor.option({
                                placeholder: 'XXX',
                                maskChar: 'X',
                                mask: '000'
                            });
                        }
                        if (result.card_type.name == 'amex') {
                            ccEditor.option('mask', '0000 000000 00000');
                            cvnEditor.option({
                                placeholder: 'XXXX',
                                maskChar: 'X',
                                mask: '0000'
                            });
                        }
                        if (result.card_type.name == 'mastercard') {
                            ccEditor.option('mask', '0000 0000 0000 0000');
                            cvnEditor.option({
                                placeholder: 'XXX',
                                maskChar: 'X',
                                mask: '000'
                            });
                        }
                        if (result.card_type.name == 'discover') {
                            ccEditor.option('mask', '0000 0000 0000 0000');
                            cvnEditor.option({
                                placeholder: 'XXX',
                                maskChar: 'X',
                                mask: '000'
                            });
                        }

                        if (result.card_type.name !== 'amex' && result.card_type.name !== 'visa'
                            && result.card_type.name !== 'mastercard' && result.card_type.name !== 'discover') {
                            ccEditor.option('mask', '');
                            cvnEditor.option({
                                maskChar: 'X',
                                mask: '000'
                            });
                        }
                        var newVal = val.replace(/\s+/g, '');
                        ccEditor.option('value', newVal);

                        //move cursor to end of txt field after format change
                        var ccField = $('#txtCreditCardNumber').dxTextBox("instance");
                        var ccFieldEl = ccField.element();
                        var ccBaseInput = ccFieldEl[0].attributes[1].ownerElement.firstChild.children[0].children[0];
                        ccField.focus()

                        MoveCursorToPos(ccBaseInput, ccField._textValue.length);

                    }
                    if (!result.valid) {
                        ccInput.removeClass('valid');
                        ccEditor.option('mask', '');
                        cvnEditor.option({
                            placeholder: '000',
                            maskChar: 'X',
                            mask: '000'
                        });
                    }
                }
                else {
                    ccInput.removeClass(['visa', 'mastercard', 'amex', 'discover']);
                }
                // set txt to val
                if (typeof val === 'undefined') {
                    ccEditor.option('value', '');
                }

            }
            else {
                ccInput.removeClass(['visa', 'mastercard', 'amex', 'discover']);
            }
        },
            {
                accept: ['visa', 'mastercard', 'amex', 'discover']
            }
        );
    }
</script>

<script type="text/html" id="tmplPaymentPopup">
    <div id='divWrapper_PaymentMethods'>
        <div id='btnPayByBank'></div>
        <div id='btnPayByCC'></div>
    </div>
    <div id="divWrapper_PaymentForm"></div>
</script>