# easy-invoice/2.2.0/assets/js/payment.js

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.2.0. 491 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.2.0/code/assets/js/payment.js
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.2.0/raw/assets/js/payment.js
- Modified: 2025-08-14T09:51:16+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.2.0/code/assets/js/payment.js#L10-L20`.

```javascript
/**
 * Easy Invoice Payment Handler
 *
 * @package EasyInvoice
 * @since 1.0.0
 */
(function($) {
    'use strict';

    // Module pattern for better encapsulation
    const EasyInvoicePayment = {
        // Properties
        initialized: false,
        selectedGateway: null,
        stripeInstance: null,
        cardElement: null,
        stripeClientSecret: null,
        $paymentButton: null,
        originalPayButtonText: '',
        messageClearTimeout: null,
        $paymentPanel: null,
        $mainContentArea: null,
        $openPaymentPanelButton: null,
        $closePaymentPanelButton: null,

        /**
         * Initialize the payment handler
         */
        init: function() {
        // Prevent multiple initializations
            if (window.easyInvoicePaymentInitialized || this.initialized) {
            return;
        }
            this.initialized = true;
        window.easyInvoicePaymentInitialized = true;

            // Cache DOM elements
            this.$paymentButton = $('.payment-button');
            this.originalPayButtonText = this.$paymentButton.text() || 'Pay Now';
            this.$paymentPanel = $('#payment-slideout-panel');
            this.$mainContentArea = $('.easy-invoice-main-content-area');
            this.$openPaymentPanelButton = $('#open-payment-panel-button');
            this.$closePaymentPanelButton = $('#close-payment-panel-button');

            // Setup event handlers
            this.setupEventListeners();

            // Show default payment method if pre-selected
            const $defaultMethod = $('input[name="payment_method_radio"]:checked');
            if ($defaultMethod.length) {
                $defaultMethod.trigger('change');
            }
        },

        /**
         * Setup all event listeners
         */
        setupEventListeners: function() {
            // Payment method selection
            $('input[name="payment_method_radio"]').on('change', this.handlePaymentMethodChange.bind(this));
            
            // Payment button
            this.$paymentButton.on('click', this.handlePaymentButtonClick.bind(this));
            
            // Panel open/close
            this.$openPaymentPanelButton.on('click', this.handleOpenPanel.bind(this));
            this.$closePaymentPanelButton.on('click', this.handleClosePanel.bind(this));
            
            // Close panel with Escape key
            $(document).on('keydown', this.handleKeyDown.bind(this));
        },

        /**
         * Handle payment method selection change
         */
        handlePaymentMethodChange: function(e) {
            this.selectedGateway = $(e.currentTarget).val();
            const $selectedEntry = $(e.currentTarget).closest('.payment-method-entry');

            // Update UI
            $('.payment-method-entry').removeClass('selected-gateway border-indigo-600 shadow-lg')
                                      .addClass('border-gray-300 shadow-sm');
            $selectedEntry.addClass('selected-gateway border-indigo-600 shadow-lg')
                          .removeClass('border-gray-300 shadow-sm');

            // Hide all payment form areas
            $('.easy-invoice-stripe-card-area').addClass('hidden');
            
            // Clear card errors if any
            if (this.cardElement) {
                const $cardErrorsDiv = $('#card-errors');
                if ($cardErrorsDiv.length) {
                    $cardErrorsDiv.text('');
                }
            }

            // Reset payment button
            this.$paymentButton.text(this.originalPayButtonText);

            // Handle Stripe gateway
            if (this.selectedGateway === 'stripe') {
                this.initializeStripePayment($selectedEntry);
            } else {
                // For other gateways, enable payment button immediately
                this.$paymentButton.prop('disabled', false);
            }
        },

        /**
         * Initialize Stripe payment elements
         */
        initializeStripePayment: function($selectedEntry) {
            const $stripeCardArea = $selectedEntry.find('#stripe-card-area');
            if (!$stripeCardArea.length) {
                // Stripe card area not found
                this.showPaymentMessage('Stripe UI setup error. Please try refreshing.', 'error');
                return;
            }

            $stripeCardArea.removeClass('hidden').html('<p class="text-sm text-gray-600 p-2">Loading card details...</p>');

            const invoiceId = this.$paymentButton.data('invoice-id');
            if (!invoiceId) {
                $stripeCardArea.html('<p class="text-red-600 p-2">Error: Invoice ID missing.</p>');
                return;
            }

            // Request client secret from server
            $.ajax({
                url: easy_invoice_vars.ajax_url,
                type: 'POST',
                data: {
                    action: 'easy_invoice_process_payment',
                    invoice_id: invoiceId,
                    payment_method: 'stripe',
                    payment_nonce: easy_invoice_vars.nonce
                },
                success: (response) => {
                    if (response.success && response.data.client_secret) {
                        this.stripeClientSecret = response.data.client_secret;
                        
                        // Setup card element HTML
                        $stripeCardArea.html(`
                            <label for="card-element" class="block text-sm font-medium text-gray-700 mb-1">Credit or debit card</label>
                            <div id="card-element" class="mt-1 p-3 border border-gray-300 rounded-md shadow-sm bg-white"></div>
                            <div id="card-errors" role="alert" class="mt-2 text-sm text-red-600"></div>
                        `);
                        
                        if (this.initializeStripeElements(this.stripeClientSecret, $selectedEntry)) {
                            this.$paymentButton.text('Pay with Card');
                            
                            // Set button state based on card completeness
                            const currentCardState = this.cardElement._empty ? false : this.cardElement._complete;
                            this.$paymentButton.prop('disabled', !currentCardState);
                        } else {
                            this.$paymentButton.prop('disabled', true);
                        }
                    } else {
                        const errorMsg = response.data && response.data.message 
                            ? response.data.message 
                            : 'Could not initialize Stripe payment.';
                        $stripeCardArea.html(`<p class="text-red-600 p-2">${errorMsg}</p>`);
                        this.$paymentButton.prop('disabled', true);
                    }
                },
                error: (xhr) => {
                    // Stripe client_secret AJAX error
                    $stripeCardArea.html('<p class="text-red-600 p-2">Server error during Stripe setup. Please try again.</p>');
                    this.$paymentButton.prop('disabled', true);
                }
            });
        },

        /**
         * Initialize Stripe Elements
         */
        initializeStripeElements: function(clientSecret, $stripeMethodEntry) {
            if (!this.stripeInstance) {
                this.stripeInstance = Stripe(easy_invoice_vars.stripe_public_key);
            }
            
            const elements = this.stripeInstance.elements({
                clientSecret: clientSecret
            });
            
            // Clean up previous card element
            if (this.cardElement) {
                this.cardElement.destroy();
            }

            this.cardElement = elements.create('card');

            const $stripeCardArea = $stripeMethodEntry.find('#stripe-card-area');
            const $cardElementDiv = $stripeCardArea.find('#card-element');
            const $cardErrorsDiv = $stripeCardArea.find('#card-errors');

            if (!$cardElementDiv.length) {
                // Card element div not found within stripe card area
                $stripeCardArea.removeClass('hidden')
                              .html('<p class="text-red-600">Stripe card input cannot be displayed. Element missing.</p>');
                this.$paymentButton.prop('disabled', true);
                return false;
            }
            
            $cardElementDiv.empty();
            this.cardElement.mount($cardElementDiv.get(0));
            $stripeCardArea.removeClass('hidden');

            // Listen for card element changes
            this.cardElement.on('change', (event) => {
                if (event.error) {
                    $cardErrorsDiv.text(event.error.message);
                    this.$paymentButton.prop('disabled', true);
                } else {
                    $cardErrorsDiv.text('');
                    this.$paymentButton.prop('disabled', !event.complete);
                }
            });
            
            return true;
        },

        /**
         * Handle payment button click
         */
        handlePaymentButtonClick: function(e) {
            e.preventDefault();
            this.$paymentButton.prop('disabled', true);

            if (!this.selectedGateway) {
                this.showPaymentMessage('Please select a payment method first.', 'error');
                this.$paymentButton.prop('disabled', false);
                return;
            }

            // Show loading state
            this.$paymentButton.html('<i class="fas fa-circle-notch fa-spin mr-2"></i>Processing...');
            
            // Handle Stripe payment
            if (this.selectedGateway === 'stripe') {
                this.processStripePayment();
                return;
            }
            
            // Handle other payment methods
            this.processStandardPayment();
        },

        /**
         * Process Stripe payment
         */
        processStripePayment: function() {
            if (!this.stripeInstance || !this.cardElement || !this.stripeClientSecret) {
                this.showPaymentMessage('Stripe payment setup is incomplete. Please refresh and try again.', 'error');
                this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                return;
            }

            this.stripeInstance.confirmCardPayment(this.stripeClientSecret, {
                        payment_method: {
                    card: this.cardElement
                }
            })
            .then((result) => {
                if (result.error) {
                    this.showPaymentMessage(result.error.message, 'error');
                    this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                } else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') {
                    // Create payment record
                    this.createPaymentRecord(result.paymentIntent.id);
                } else {
                    this.showPaymentMessage('Payment processing failed. Please try again.', 'error');
                    this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                }
            })
            .catch((error) => {
                // Stripe.confirmCardPayment error
                this.showPaymentMessage('Payment processing error. Please try again.', 'error');
                this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
            });
        },



        /**
         * Process standard (non-Stripe) payment
         */
        processStandardPayment: function() {
            const invoiceId = this.$paymentButton.data('invoice-id');
            if (!invoiceId) {
                this.showPaymentMessage('Invoice ID is missing. Please refresh the page.', 'error');
                this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                return;
            }

            $.ajax({
                url: easy_invoice_vars.ajax_url,
                type: 'POST',
                data: {
                    action: 'easy_invoice_process_payment',
                    invoice_id: invoiceId,
                    payment_method: this.selectedGateway,
                    payment_nonce: easy_invoice_vars.nonce
                },
                success: (response) => {
                    if (response.success) {
                        this.handlePaymentSuccess(response.data);
                    } else {
                        const errorMsg = response.data && response.data.message 
                            ? response.data.message 
                            : 'Payment processing failed.';
                        this.showPaymentMessage(errorMsg, 'error');
                        this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                    }
                },
                error: (xhr) => {
                    // Payment AJAX error
                    this.showPaymentMessage('Server error during payment. Please try again.', 'error');
                    this.$paymentButton.html(this.originalPayButtonText).prop('disabled', false);
                }
            });
        },

        /**
         * Create payment record after successful Stripe payment
         */
        createPaymentRecord: function(transactionId) {
            const invoiceId = this.$paymentButton.data('invoice-id');
            
            $.ajax({
                url: easy_invoice_vars.ajax_url,
                type: 'POST',
                data: {
                    action: 'easy_invoice_create_payment_record',
                    invoice_id: invoiceId,
                    payment_method: 'stripe',
                    transaction_id: transactionId,
                    payment_nonce: easy_invoice_vars.nonce
                },
                success: (response) => {
                    if (response.success) {
                        this.handlePaymentSuccess(response.data);
                    } else {
                        const errorMsg = response.data && response.data.message 
                            ? response.data.message 
                            : 'Payment recorded but failed to update records.';
                        this.showPaymentMessage(errorMsg, 'warning');
                        this.$paymentButton.html('Payment Recorded').prop('disabled', true);
                        
                        // Reload page after delay even if there was an error updating records
                        setTimeout(() => {
                            window.location.reload();
                        }, 3000);
                    }
                },
                error: (xhr) => {
                    // Payment record error
                    this.showPaymentMessage('Payment successful but failed to update records. The administrator has been notified.', 'warning');
                    this.$paymentButton.html('Payment Recorded').prop('disabled', true);
                    
                    // Reload page after delay
                    setTimeout(() => {
                        window.location.reload();
                    }, 3000);
                }
            });
        },

        /**
         * Handle successful payment
         */
        handlePaymentSuccess: function(data) {
            // Update button state
            this.$paymentButton.html('<i class="fas fa-check-circle mr-2"></i>Payment Successful!').prop('disabled', true);
            
            // Show success message
            this.showPaymentMessage(data.message || 'Payment successful!', 'success');
            
            // Handle redirect if provided
            if (data.redirect_url) {
                setTimeout(() => {
                window.location.href = data.redirect_url;
                }, 2000);
                return;
            }
            
            // Otherwise reload the page after delay
            setTimeout(() => {
                window.location.reload();
            }, 2000);
        },

        /**
         * Show payment status message
         */
        showPaymentMessage: function(message, type) {
            const $messageArea = $('#payment-message-area');
            if (!$messageArea.length) {
                // Payment message area not found
                return;
            }
            
            // Clear any existing timeout
            if (this.messageClearTimeout) {
                clearTimeout(this.messageClearTimeout);
            }
            
            // Determine styling based on message type
            let bgClass, textClass, icon;
            
            switch (type) {
                case 'success':
                    bgClass = 'bg-green-100 border-green-400';
                    textClass = 'text-green-700';
                    icon = 'fa-check-circle';
                    break;
                case 'warning':
                    bgClass = 'bg-yellow-100 border-yellow-400';
                    textClass = 'text-yellow-700';
                    icon = 'fa-exclamation-triangle';
                    break;
                case 'error':
                default:
                    bgClass = 'bg-red-100 border-red-400';
                    textClass = 'text-red-700';
                    icon = 'fa-times-circle';
                    break;
            }
            
            // Build and show message
            const $messageHtml = $(`
                <div class="rounded-md border px-4 py-3 ${bgClass} mb-4 animate-fade-in">
                    <div class="flex items-center">
                        <div class="flex-shrink-0">
                            <i class="fas ${icon} ${textClass}"></i>
                        </div>
                        <div class="ml-3">
                            <p class="text-sm ${textClass}">${message}</p>
                        </div>
                    </div>
                </div>
            `);
            
            $messageArea.html($messageHtml);
            
            // Auto-hide messages except for success ones
            if (type !== 'success') {
                this.messageClearTimeout = setTimeout(() => {
                    $messageHtml.fadeOut(300, function() {
                        $(this).remove();
                    });
                }, 6000);
            }
        },

        /**
         * Handle panel open button click
         */
        handleOpenPanel: function(e) {
            e.preventDefault();
            this.$paymentPanel.addClass('is-open');
            $('body').addClass('body-panel-active');
            this.$closePaymentPanelButton.focus();
        },

        /**
         * Handle panel close button click
         */
        handleClosePanel: function(e) {
            e.preventDefault();
            this.$paymentPanel.removeClass('is-open');
            $('body').removeClass('body-panel-active');
            this.$openPaymentPanelButton.focus();
        },

        /**
         * Handle keyboard events
         */
        handleKeyDown: function(event) {
            if (event.key === "Escape" && this.$paymentPanel.hasClass('is-open')) {
                this.handleClosePanel(event);
            }
        }
    };

    // Initialize when document is ready
    $(document).ready(function() {
        EasyInvoicePayment.init();
    });

})(jQuery); 
```
