Plugin Directory

Changeset 3393362


Ignore:
Timestamp:
11/11/2025 04:55:44 AM (4 months ago)
Author:
matrixaddons
Message:

Update to version 2.1.4 from GitHub

Location:
easy-invoice
Files:
26 edited
1 copied

Legend:

Unmodified
Added
Removed
  • easy-invoice/tags/2.1.4/easy-invoice.php

    r3388559 r3393362  
    44 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice
    55 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease.
    6  * Version: 2.1.3
     6 * Version: 2.1.4
    77 * Author: MatrixAddons
    88 * Author URI: https://matrixaddons.com
     
    2525
    2626// Define plugin constants.
    27 define( 'EASY_INVOICE_VERSION', '2.1.3' );
     27define( 'EASY_INVOICE_VERSION', '2.1.4' );
    2828define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ );
    2929define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • easy-invoice/tags/2.1.4/includes/Admin/AdminController.php

    r3345595 r3393362  
    3838     */
    3939    public function sendManualPaymentNotification($invoice_id, $payment_method) {
    40         // Get admin email
    41         $admin_email = get_option('admin_email');
    42        
    4340        // Get invoice details
    4441        $invoice = new \EasyInvoice\Models\Invoice(get_post($invoice_id));
    4542       
    46         // Get customer details
    47         $customer_name = $invoice->getCustomerName();
    48         $customer_email = $invoice->getCustomerEmail();
     43        if (!$invoice || !$invoice->getId()) {
     44            return;
     45        }
    4946       
    50         // Format amount
    51         $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
    52         $amount = $formatter->format($invoice->getTotal());
    53        
    54         // Format payment method
    55         $payment_method_label = $payment_method === 'bank' ? 'Bank Transfer' : 'Cheque';
    56        
    57         // Email subject
    58         $subject = sprintf(
    59             __('New %s Payment Received - Invoice #%s', 'easy-invoice'),
    60             $payment_method_label,
    61             $invoice->getNumber()
    62         );
    63        
    64         // Email message
    65         $message = sprintf(
    66             __('A new %s payment has been submitted for invoice #%s.
    67 
    68 Invoice Details:
    69 - Amount: %s
    70 - Customer: %s
    71 - Email: %s
    72 
    73 Please review and verify this payment in the admin dashboard:
    74 %s
    75 
    76 This is an automated message from Easy Invoice.', 'easy-invoice'),
    77             $payment_method_label,
    78             $invoice->getNumber(),
    79             $amount,
    80             $customer_name,
    81             $customer_email,
    82             admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id)
    83         );
    84        
    85         // Send email
    86         wp_mail($admin_email, $subject, $message);
     47        // Use EmailManager to send admin notification
     48        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     49        $email_manager->sendAdminPaymentNotification($invoice, [
     50            'payment_method' => $payment_method
     51        ]);
    8752    }
    8853
  • easy-invoice/tags/2.1.4/includes/Controllers/PaymentController.php

    r3346980 r3393362  
    694694            $payment = Payment::create($payment_data);
    695695           
     696            // Store payment details before updating status (for the hook)
     697            $invoice->setMeta('_payment_method', $payment_method);
     698            if ($transaction_id) {
     699                $invoice->setMeta('_transaction_id', $transaction_id);
     700            }
     701           
    696702            // Update invoice status to paid only if total payments are sufficient
     703            // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
    697704            $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'manual');
    698705           
     
    765772    private function sendPaymentConfirmationEmail($invoice_id, $payment_id): void {
    766773        $invoice = new Invoice(get_post($invoice_id));
    767         $customer_email = $invoice->getCustomerEmail();
    768        
    769         if (!$customer_email) {
    770             return;
    771         }
    772        
    773         // Get currency settings
    774         $settings_controller = new \EasyInvoice\Controllers\SettingsController();
    775         $settings = $settings_controller->getSettings();
    776         $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
    777         $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
    778        
    779         $site_name = get_bloginfo('name');
    780         $invoice_number = $invoice->getNumber();
    781         $amount = $invoice->getTotal();
    782         $formatted_amount = $currency_symbol . number_format($amount, 2);
    783        
    784         $subject = sprintf(__('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    785        
    786         $message = sprintf(
    787             __('Dear %s,', 'easy-invoice'),
    788             $invoice->getCustomerName()
    789         );
    790         $message .= "\n\n";
    791         $message .= sprintf(
    792             __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
    793             $formatted_amount,
    794             $invoice_number
    795         );
    796         $message .= "\n\n";
    797         $message .= __('Thank you for your business.', 'easy-invoice');
    798         $message .= "\n\n";
    799         $message .= sprintf(__('Regards,', 'easy-invoice'));
    800         $message .= "\n";
    801         $message .= get_option('easy_invoice_company_name', $site_name);
    802        
    803         wp_mail($customer_email, $subject, $message);
     774       
     775        if (!$invoice || !$invoice->getId()) {
     776            return;
     777        }
     778       
     779        // Use EmailManager to send payment confirmation
     780        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     781        $email_manager->sendPaymentConfirmationEmail($invoice, [
     782            'payment_id' => $payment_id
     783        ]);
    804784    }
    805785   
     
    812792    private function sendPaymentRejectionEmail($invoice_id, $reason): void {
    813793        $invoice = new Invoice(get_post($invoice_id));
    814         $customer_email = $invoice->getCustomerEmail();
    815        
    816         if (!$customer_email) {
    817             return;
    818         }
    819        
    820         // Get currency settings
    821         $settings_controller = new \EasyInvoice\Controllers\SettingsController();
    822         $settings = $settings_controller->getSettings();
    823         $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
    824         $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
    825        
    826         $site_name = get_bloginfo('name');
    827         $invoice_number = $invoice->getNumber();
    828         $amount = $invoice->getTotal();
    829         $formatted_amount = $currency_symbol . number_format($amount, 2);
    830        
    831         $subject = sprintf(__('[%s] Payment Issue - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    832        
    833         $message = sprintf(
    834             __('Dear %s,', 'easy-invoice'),
    835             $invoice->getCustomerName()
    836         );
    837         $message .= "\n\n";
    838         $message .= sprintf(
    839             __('We regret to inform you that we could not process your payment of %s for Invoice #%s.', 'easy-invoice'),
    840             $formatted_amount,
    841             $invoice_number
    842         );
    843         $message .= "\n\n";
    844        
    845         if ($reason) {
    846             $message .= __('Reason:', 'easy-invoice') . "\n";
    847             $message .= $reason;
    848             $message .= "\n\n";
    849         }
    850        
    851         $message .= __('Please contact us to arrange an alternative payment method.', 'easy-invoice');
    852         $message .= "\n\n";
    853         $message .= sprintf(__('Regards,', 'easy-invoice'));
    854         $message .= "\n";
    855         $message .= get_option('easy_invoice_company_name', $site_name);
    856        
    857         wp_mail($customer_email, $subject, $message);
     794       
     795        if (!$invoice || !$invoice->getId()) {
     796            return;
     797        }
     798       
     799        // Use EmailManager to send payment rejection
     800        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     801        $email_manager->sendPaymentRejectionEmail($invoice, $reason);
    858802    }
    859803   
     
    938882            foreach ($pending_invoices as $post) {
    939883                $invoice = new Invoice($post);
    940                 $customer_email = $invoice->getCustomerEmail();
    941                
    942                 if (!$customer_email) {
     884               
     885                if (!$invoice || !$invoice->getId()) {
    943886                    continue;
    944887                }
    945888               
    946                 $site_name = get_bloginfo('name');
    947                 $invoice_number = $invoice->getNumber();
    948                 $amount = $invoice->getTotal();
    949                 $formatted_amount = $currency_symbol . number_format($amount, 2);
    950                 $payment_method = get_post_meta($invoice->getId(), '_payment_method', true);
    951                 $payment_method_label = $payment_method === 'bank' ? __('Bank Transfer', 'easy-invoice') : __('Cheque', 'easy-invoice');
    952                
    953                 $subject = sprintf(__('[%s] Payment Reminder - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    954                
    955                 $message = sprintf(
    956                     __('Dear %s,', 'easy-invoice'),
    957                     $invoice->getCustomerName()
    958                 );
    959                 $message .= "\n\n";
    960                 $message .= sprintf(
    961                     __('This is a friendly reminder that we are still awaiting your %s payment of %s for Invoice #%s.', 'easy-invoice'),
    962                     $payment_method_label,
    963                     $formatted_amount,
    964                     $invoice_number
    965                 );
    966                 $message .= "\n\n";
    967                 $message .= __('If you have already sent the payment, please disregard this reminder. If not, please arrange for payment at your earliest convenience.', 'easy-invoice');
    968                 $message .= "\n\n";
    969                 $message .= sprintf(__('Regards,', 'easy-invoice'));
    970                 $message .= "\n";
    971                 $message .= get_option('easy_invoice_company_name', $site_name);
    972                
    973                 wp_mail($customer_email, $subject, $message);
    974                
    975                 // Mark reminder as sent
    976                 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql'));
     889                // Use EmailManager to send payment reminder
     890                $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     891                $result = $email_manager->sendInvoiceEmail($invoice, 'reminder', [
     892                    'payment_method' => get_post_meta($invoice->getId(), '_payment_method', true)
     893                ]);
     894               
     895                // Mark reminder as sent if email was sent successfully
     896                if ($result['success']) {
     897                    update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql'));
     898                }
    977899            }
    978900           
     
    11881110        }
    11891111
     1112        // Store payment details before updating status (for the hook)
     1113        $transaction_id = get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id;
     1114        $invoice->setMeta('_payment_method', $payment_method);
     1115        $invoice->setMeta('_transaction_id', $transaction_id);
     1116       
     1117        // Update invoice status to paid
     1118        // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
     1119        $invoice->setStatus('paid');
     1120        $invoice->save();
     1121       
     1122        // Trigger the payment completed hook manually since we're updating status directly
     1123        do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [
     1124            'payment_method' => $payment_method,
     1125            'gateway_name' => 'manual',
     1126            'transaction_id' => $transaction_id,
     1127            'amount' => $invoice->getTotal()
     1128        ]);
     1129       
    11901130        // Trigger email confirmation and actions only if we have a payment_id
    11911131        if ($payment_id) {
     1132            // Send confirmation email to customer
    11921133            $this->sendPaymentConfirmationEmail($invoice_id, $payment_id);
    11931134            do_action('easy_invoice_manual_payment_confirmed', $invoice_id, $payment_id, $payment_method);
  • easy-invoice/tags/2.1.4/includes/Controllers/QuoteController.php

    r3387040 r3393362  
    13491349     */
    13501350    private function sendQuoteAcceptanceNotification($quote): void {
    1351         $admin_email = get_option('admin_email');
    1352         $site_name = get_bloginfo('name');
    1353        
    1354         $subject = sprintf(__('Quote %s has been accepted', 'easy-invoice'), $quote->getNumber());
    1355        
    1356         $message = sprintf(
    1357             __('Hello,
    1358 
    1359 The quote %s for %s has been accepted by the client.
    1360 
    1361 Quote Details:
    1362 - Quote Number: %s
    1363 - Client: %s
    1364 - Total Amount: %s
    1365 - Accepted Date: %s
    1366 
    1367 You can view the quote at: %s
    1368 
    1369 Best regards,
    1370 %s', 'easy-invoice'),
    1371             $quote->getNumber(),
    1372             $quote->getCustomerName(),
    1373             $quote->getNumber(),
    1374             $quote->getCustomerName(),
    1375             $this->formatCurrency($quote->getTotal(), $quote),
    1376             date_i18n(get_option('date_format') . ' ' . get_option('time_format')),
    1377             get_permalink($quote->getId()),
    1378             $site_name
    1379         );
    1380        
    1381         wp_mail($admin_email, $subject, $message);
     1351        // Use EmailManager to send admin notification
     1352        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     1353        $email_manager->sendAdminQuoteNotification($quote, 'accepted');
    13821354    }
    13831355   
     
    13881360     */
    13891361    private function sendQuoteDeclineNotification($quote): void {
    1390         $admin_email = get_option('admin_email');
    1391         $site_name = get_bloginfo('name');
    1392        
    1393         $subject = sprintf(__('Quote %s has been declined', 'easy-invoice'), $quote->getNumber());
    1394        
    1395         $message = sprintf(
    1396             __('Hello,
    1397 
    1398 The quote %s for %s has been declined by the client.
    1399 
    1400 Quote Details:
    1401 - Quote Number: %s
    1402 - Client: %s
    1403 - Total Amount: %s
    1404 - Declined Date: %s
    1405 
    1406 You can view the quote at: %s
    1407 
    1408 Best regards,
    1409 %s', 'easy-invoice'),
    1410             $quote->getNumber(),
    1411             $quote->getCustomerName(),
    1412             $quote->getNumber(),
    1413             $quote->getCustomerName(),
    1414             $this->formatCurrency($quote->getTotal(), $quote),
    1415             date_i18n(get_option('date_format') . ' ' . get_option('time_format')),
    1416             get_permalink($quote->getId()),
    1417             $site_name
    1418         );
    1419        
    1420         wp_mail($admin_email, $subject, $message);
     1362        // Use EmailManager to send admin notification
     1363        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     1364        $email_manager->sendAdminQuoteNotification($quote, 'declined');
    14211365    }
    14221366   
  • easy-invoice/tags/2.1.4/includes/Forms/FormProcessor.php

    r3348168 r3393362  
    2323   
    2424    /**
     25     * Allowed HTML tags for textarea fields
     26     *
     27     * @since 1.0.0
     28     * @var array
     29     */
     30    private static $allowed_textarea_tags = [
     31        'p' => [],
     32        'br' => [],
     33        'strong' => [],
     34        'em' => [],
     35        'i' => [],
     36        'b' => [],
     37        'u' => [],
     38        'ul' => [],
     39        'ol' => [],
     40        'li' => [],
     41        'h1' => [],
     42        'h2' => [],
     43        'h3' => [],
     44        'h4' => [],
     45        'h5' => [],
     46        'h6' => [],
     47        'a' => [
     48            'href' => [],
     49            'title' => [],
     50            'target' => [],
     51        ],
     52        'span' => [],
     53        'div' => [],
     54    ];
     55   
     56    /**
     57     * Sanitize textarea field allowing basic HTML tags
     58     *
     59     * @since 1.0.0
     60     * @param string $value The value to sanitize
     61     * @return string Sanitized value with allowed HTML tags
     62     */
     63    public static function sanitizeTextareaWithHtml(string $value): string {
     64        // Use wp_kses to allow only safe HTML tags
     65        return wp_kses($value, self::$allowed_textarea_tags);
     66    }
     67   
     68    /**
    2569     * Process form data
    2670     *
     
    5296            }
    5397            $required = $field['required'] ?? false;
     98            $field_type = $field['type'] ?? 'text';
    5499            $raw_value = $raw_data[$field_name] ?? '';
    55100           
     
    63108            }
    64109           
    65             // Skip empty non-required fields
    66             if (empty($raw_value) && !$required && $raw_value !== '0') {
     110            // Always include textarea fields (like description) even when empty
     111            // This allows users to clear these fields by submitting empty values
     112            $always_include_fields = ['description', 'notes', 'terms', 'internal_notes'];
     113            $should_always_include = in_array($field_name, $always_include_fields) || $field_type === 'textarea';
     114           
     115            // Skip empty non-required fields (unless they should always be included)
     116            if (empty($raw_value) && !$required && $raw_value !== '0' && !$should_always_include) {
    67117                continue;
    68118            }
     
    208258            $value = $form_data[$field_name] ?? null;
    209259           
    210 
    211            
    212             // Only save if value exists and is not empty (or is 0)
    213             if ($value !== null && $value !== '') {
     260            // Fields that should always be saved, even if empty (to allow clearing)
     261            $always_save_fields = ['description', 'notes', 'terms', 'internal_notes'];
     262            $should_always_save = in_array($field_name, $always_save_fields);
     263           
     264            // Save if value exists and is not empty (or is 0), OR if it's a field that should always be saved
     265            if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) {
    214266                // Use custom save callback if provided
    215267                if (isset($field['save_callback']) && is_callable($field['save_callback'])) {
    216                     $field['save_callback']($value, $model);
     268                    $field['save_callback']($value ?? '', $model);
    217269                } else {
    218270                    // Default save to meta data
    219271                    if (method_exists($model, 'setMetaData')) {
    220                         $model->setMetaData($db_key, $value);
    221                        
    222 
     272                        $model->setMetaData($db_key, $value ?? '');
    223273                    }
    224274                }
     
    227277       
    228278        // Also process any extra fields that might not be in the configuration
     279        $always_save_fields = ['description', 'notes', 'terms', 'internal_notes'];
    229280        foreach ($form_data as $field_name => $value) {
    230             if ($value !== null && $value !== '') {
     281            $should_always_save = in_array($field_name, $always_save_fields);
     282           
     283            if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) {
    231284                $db_key = '_easy_invoice_' . $field_name;
    232285               
     
    241294               
    242295                if (!$already_processed && method_exists($model, 'setMetaData')) {
    243                     $model->setMetaData($db_key, $value);
     296                    $model->setMetaData($db_key, $value ?? '');
    244297                }
    245298            }
     
    293346               
    294347            case 'textarea':
    295                 return sanitize_textarea_field($value);
     348                // Allow basic HTML tags in textarea fields
     349                return self::sanitizeTextareaWithHtml($value);
    296350               
    297351            case 'email':
     
    324378       
    325379        if (is_callable($sanitize_callback)) {
     380            // If callback is 'sanitize_textarea_field', use our HTML-allowing version for textarea fields
     381            if ($sanitize_callback === 'sanitize_textarea_field' && ($field['type'] ?? '') === 'textarea') {
     382                return self::sanitizeTextareaWithHtml($value);
     383            }
    326384            return $sanitize_callback($value);
    327385        }
  • easy-invoice/tags/2.1.4/includes/Gateways/PayPalGateway.php

    r3346980 r3393362  
    539539       
    540540        // Update invoice status to paid only if total payments are sufficient
     541        // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
    541542        $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'paypal');
    542543       
     
    546547             wp_update_post(['ID' => $invoice_id, 'post_status' => 'publish']);
    547548        }
     549       
     550        // Store payment details for the hook
     551        $invoice->setMeta('_payment_method', 'paypal');
     552        $invoice->setMeta('_transaction_id', $paypal_txn_id);
    548553
    549554        return [
  • easy-invoice/tags/2.1.4/includes/Models/Invoice.php

    r3348168 r3393362  
    446446           
    447447            // Get the value from dynamic data array
    448             $value = $this->data[$field_name] ?? null;
    449             if ($value !== null) {
     448            // Save all fields that exist in the data array (including empty strings to allow clearing fields)
     449            // If a field exists in $this->data, it means it was explicitly set, so we should save it
     450            if (array_key_exists($field_name, $this->data)) {
     451                $value = $this->data[$field_name];
    450452                update_post_meta($this->id, $meta_key, $value);
    451453            }
  • easy-invoice/tags/2.1.4/includes/Models/Quote.php

    r3363158 r3393362  
    486486           
    487487            // Get the value from dynamic data array
    488             $value = $this->data[$field_name] ?? null;
    489             if ($value !== null) {
    490                
    491                
     488            // Save all fields that exist in the data array (including empty strings to allow clearing fields)
     489            // If a field exists in $this->data, it means it was explicitly set, so we should save it
     490            if (array_key_exists($field_name, $this->data)) {
     491                $value = $this->data[$field_name];
    492492                update_post_meta($this->id, $meta_key, $value);
    493                
    494                 // Debug: Log template field saving to database
    495 
    496493            }
    497494        }
  • easy-invoice/tags/2.1.4/includes/Repositories/InvoiceRepository.php

    r3387051 r3393362  
    397397        }
    398398       
    399         if (isset($data['description'])) {
    400             $invoice->setDescription($data['description']);
    401         }
    402        
    403         if (isset($data['invoice_description'])) {
    404             $invoice->setDescription($data['invoice_description']);
     399        // Always update description if it exists in data (even if empty, to allow clearing)
     400        if (array_key_exists('description', $data)) {
     401            $invoice->setDescription($data['description'] ?? '');
     402        }
     403       
     404        if (array_key_exists('invoice_description', $data)) {
     405            $invoice->setDescription($data['invoice_description'] ?? '');
    405406        }
    406407       
  • easy-invoice/tags/2.1.4/includes/Repositories/QuoteRepository.php

    r3387051 r3393362  
    495495        }
    496496       
    497         if (isset($data['description'])) {
    498             $quote->setDescription($data['description']);
     497        // Always update description if it exists in data (even if empty, to allow clearing)
     498        if (array_key_exists('description', $data)) {
     499            $quote->setDescription($data['description'] ?? '');
    499500        }
    500501       
  • easy-invoice/tags/2.1.4/includes/Services/EmailManager.php

    r3387040 r3393362  
    109109        add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3);
    110110        add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3);
     111       
     112        // Listen for payment completion to send admin notifications
     113        add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3);
    111114    }
    112115   
     
    14671470    }
    14681471   
     1472    /**
     1473     * Send admin notification when payment is received
     1474     *
     1475     * @param Invoice $invoice The invoice
     1476     * @param array $payment_data Payment data (method, amount, etc.)
     1477     * @return array Result array with success status and message
     1478     */
     1479    public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array {
     1480        try {
     1481            // Validate invoice
     1482            if (!$invoice || !$invoice->getId()) {
     1483                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1484            }
     1485           
     1486            // Get admin email
     1487            $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
     1488            if (empty($admin_email)) {
     1489                return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
     1490            }
     1491           
     1492            // Get payment method
     1493            $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online';
     1494            $payment_method_label = $this->getPaymentMethodLabel($payment_method);
     1495           
     1496            // Format amount
     1497            $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
     1498            $amount = $formatter->format($invoice->getTotal());
     1499           
     1500            // Prepare email subject
     1501            $subject = sprintf(
     1502                __('New Payment Received - Invoice #%s', 'easy-invoice'),
     1503                $invoice->getNumber()
     1504            );
     1505           
     1506            // Prepare email message
     1507            $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data);
     1508           
     1509            // Add HTML wrapper if enabled
     1510            if ($this->settings['enable_html'] === 'yes') {
     1511                $message = $this->wrapInHtmlTemplate($message);
     1512            }
     1513           
     1514            // Prepare headers
     1515            $headers = $this->prepareEmailHeaders();
     1516           
     1517            // Send email
     1518            $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
     1519           
     1520            if ($sent) {
     1521                do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data);
     1522                return [
     1523                    'success' => true,
     1524                    'message' => __('Admin notification sent successfully', 'easy-invoice')
     1525                ];
     1526            } else {
     1527                do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data);
     1528                return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
     1529            }
     1530           
     1531        } catch (\Exception $e) {
     1532            $this->log('Admin payment notification error: ' . $e->getMessage(), 'error');
     1533            return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
     1534        }
     1535    }
     1536   
     1537    /**
     1538     * Send payment confirmation email to customer
     1539     *
     1540     * @param Invoice $invoice The invoice
     1541     * @param array $payment_data Payment data
     1542     * @return array Result array with success status and message
     1543     */
     1544    public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array {
     1545        try {
     1546            // Validate invoice
     1547            if (!$invoice || !$invoice->getId()) {
     1548                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1549            }
     1550           
     1551            // Get customer email
     1552            $customer_email = $invoice->getCustomerEmail();
     1553            if (empty($customer_email)) {
     1554                return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
     1555            }
     1556           
     1557            // Get currency settings
     1558            $settings_controller = new \EasyInvoice\Controllers\SettingsController();
     1559            $settings = $settings_controller->getSettings();
     1560            $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
     1561            $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
     1562           
     1563            // Format amount
     1564            $amount = $invoice->getTotal();
     1565            $formatted_amount = $currency_symbol . number_format($amount, 2);
     1566           
     1567            // Prepare email subject
     1568            $site_name = get_bloginfo('name');
     1569            $subject = sprintf(
     1570                __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'),
     1571                $site_name,
     1572                $invoice->getNumber()
     1573            );
     1574           
     1575            // Prepare email message
     1576            $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount);
     1577           
     1578            // Add HTML wrapper if enabled
     1579            if ($this->settings['enable_html'] === 'yes') {
     1580                $message = $this->wrapInHtmlTemplate($message);
     1581            }
     1582           
     1583            // Prepare headers
     1584            $headers = $this->prepareEmailHeaders();
     1585           
     1586            // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate)
     1587            // The payment completion hook already sends a dedicated admin notification
     1588            $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true;
     1589            if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
     1590                $headers[] = 'Bcc: ' . $this->settings['admin_email'];
     1591            }
     1592           
     1593            // Send email
     1594            $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
     1595           
     1596            if ($sent) {
     1597                do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data);
     1598                return [
     1599                    'success' => true,
     1600                    'message' => __('Payment confirmation email sent successfully', 'easy-invoice')
     1601                ];
     1602            } else {
     1603                do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data);
     1604                return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')];
     1605            }
     1606           
     1607        } catch (\Exception $e) {
     1608            $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error');
     1609            return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()];
     1610        }
     1611    }
     1612   
     1613    /**
     1614     * Send payment rejection email to customer
     1615     *
     1616     * @param Invoice $invoice The invoice
     1617     * @param string $reason Rejection reason
     1618     * @return array Result array with success status and message
     1619     */
     1620    public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array {
     1621        try {
     1622            // Validate invoice
     1623            if (!$invoice || !$invoice->getId()) {
     1624                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1625            }
     1626           
     1627            // Get customer email
     1628            $customer_email = $invoice->getCustomerEmail();
     1629            if (empty($customer_email)) {
     1630                return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
     1631            }
     1632           
     1633            // Prepare email subject
     1634            $site_name = get_bloginfo('name');
     1635            $subject = sprintf(
     1636                __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'),
     1637                $site_name,
     1638                $invoice->getNumber()
     1639            );
     1640           
     1641            // Prepare email message
     1642            $message = $this->preparePaymentRejectionMessage($invoice, $reason);
     1643           
     1644            // Add HTML wrapper if enabled
     1645            if ($this->settings['enable_html'] === 'yes') {
     1646                $message = $this->wrapInHtmlTemplate($message);
     1647            }
     1648           
     1649            // Prepare headers
     1650            $headers = $this->prepareEmailHeaders();
     1651           
     1652            // Add BCC to admin if enabled
     1653            if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
     1654                $headers[] = 'Bcc: ' . $this->settings['admin_email'];
     1655            }
     1656           
     1657            // Send email
     1658            $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
     1659           
     1660            if ($sent) {
     1661                do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason);
     1662                return [
     1663                    'success' => true,
     1664                    'message' => __('Payment rejection email sent successfully', 'easy-invoice')
     1665                ];
     1666            } else {
     1667                do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason);
     1668                return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')];
     1669            }
     1670           
     1671        } catch (\Exception $e) {
     1672            $this->log('Payment rejection email error: ' . $e->getMessage(), 'error');
     1673            return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()];
     1674        }
     1675    }
     1676   
     1677    /**
     1678     * Prepare admin payment notification message
     1679     *
     1680     * @param Invoice $invoice The invoice
     1681     * @param string $payment_method_label Payment method label
     1682     * @param string $amount Formatted amount
     1683     * @param array $payment_data Payment data
     1684     * @return string Email message
     1685     */
     1686    private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string {
     1687        $invoice_number = $invoice->getNumber();
     1688        $customer_name = $invoice->getCustomerName();
     1689        $customer_email = $invoice->getCustomerEmail();
     1690        $invoice_id = $invoice->getId();
     1691       
     1692        $message = sprintf(
     1693            __('A new %s payment has been received for invoice #%s.', 'easy-invoice'),
     1694            $payment_method_label,
     1695            $invoice_number
     1696        );
     1697        $message .= "\n\n";
     1698        $message .= __('Invoice Details:', 'easy-invoice');
     1699        $message .= "\n";
     1700        $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount);
     1701        $message .= "\n";
     1702        $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name);
     1703        $message .= "\n";
     1704        $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email);
     1705       
     1706        // Add transaction ID if available
     1707        if (!empty($payment_data['transaction_id'])) {
     1708            $message .= "\n";
     1709            $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']);
     1710        }
     1711       
     1712        $message .= "\n\n";
     1713        $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice');
     1714        $message .= "\n";
     1715        $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id);
     1716        $message .= "\n\n";
     1717        $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice');
     1718       
     1719        return $message;
     1720    }
     1721   
     1722    /**
     1723     * Prepare payment confirmation message
     1724     *
     1725     * @param Invoice $invoice The invoice
     1726     * @param string $formatted_amount Formatted amount
     1727     * @return string Email message
     1728     */
     1729    private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string {
     1730        $customer_name = $invoice->getCustomerName();
     1731        $invoice_number = $invoice->getNumber();
     1732        $site_name = get_bloginfo('name');
     1733        $company_name = get_option('easy_invoice_company_name', $site_name);
     1734       
     1735        $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
     1736        $message .= "\n\n";
     1737        $message .= sprintf(
     1738            __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
     1739            $formatted_amount,
     1740            $invoice_number
     1741        );
     1742        $message .= "\n\n";
     1743        $message .= __('Thank you for your business.', 'easy-invoice');
     1744        $message .= "\n\n";
     1745        $message .= __('Regards,', 'easy-invoice');
     1746        $message .= "\n";
     1747        $message .= $company_name;
     1748       
     1749        return $message;
     1750    }
     1751   
     1752    /**
     1753     * Prepare payment rejection message
     1754     *
     1755     * @param Invoice $invoice The invoice
     1756     * @param string $reason Rejection reason
     1757     * @return string Email message
     1758     */
     1759    private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string {
     1760        $customer_name = $invoice->getCustomerName();
     1761        $invoice_number = $invoice->getNumber();
     1762        $site_name = get_bloginfo('name');
     1763        $company_name = get_option('easy_invoice_company_name', $site_name);
     1764       
     1765        $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
     1766        $message .= "\n\n";
     1767        $message .= sprintf(
     1768            __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'),
     1769            $invoice_number
     1770        );
     1771       
     1772        if (!empty($reason)) {
     1773            $message .= "\n\n";
     1774            $message .= __('Reason:', 'easy-invoice');
     1775            $message .= "\n";
     1776            $message .= $reason;
     1777        }
     1778       
     1779        $message .= "\n\n";
     1780        $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice');
     1781        $message .= "\n\n";
     1782        $message .= __('Regards,', 'easy-invoice');
     1783        $message .= "\n";
     1784        $message .= $company_name;
     1785       
     1786        return $message;
     1787    }
     1788   
     1789    /**
     1790     * Send admin notification for quote acceptance/decline
     1791     *
     1792     * @param Quote $quote The quote
     1793     * @param string $action Action type ('accepted' or 'declined')
     1794     * @return array Result array with success status and message
     1795     */
     1796    public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array {
     1797        try {
     1798            // Validate quote
     1799            if (!$quote || !$quote->getId()) {
     1800                return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
     1801            }
     1802           
     1803            // Get admin email
     1804            $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
     1805            if (empty($admin_email)) {
     1806                return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
     1807            }
     1808           
     1809            // Prepare email subject
     1810            $subject = sprintf(
     1811                __('Quote %s has been %s', 'easy-invoice'),
     1812                $quote->getNumber(),
     1813                $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice')
     1814            );
     1815           
     1816            // Prepare email message
     1817            $message = $this->prepareAdminQuoteNotificationMessage($quote, $action);
     1818           
     1819            // Add HTML wrapper if enabled
     1820            if ($this->settings['enable_html'] === 'yes') {
     1821                $message = $this->wrapInHtmlTemplate($message);
     1822            }
     1823           
     1824            // Prepare headers
     1825            $headers = $this->prepareEmailHeaders();
     1826           
     1827            // Send email
     1828            $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
     1829           
     1830            if ($sent) {
     1831                do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action);
     1832                return [
     1833                    'success' => true,
     1834                    'message' => __('Admin notification sent successfully', 'easy-invoice')
     1835                ];
     1836            } else {
     1837                do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action);
     1838                return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
     1839            }
     1840           
     1841        } catch (\Exception $e) {
     1842            $this->log('Admin quote notification error: ' . $e->getMessage(), 'error');
     1843            return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
     1844        }
     1845    }
     1846   
     1847    /**
     1848     * Prepare admin quote notification message
     1849     *
     1850     * @param Quote $quote The quote
     1851     * @param string $action Action type ('accepted' or 'declined')
     1852     * @return string Email message
     1853     */
     1854    private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string {
     1855        $site_name = get_bloginfo('name');
     1856        $quote_number = $quote->getNumber();
     1857        $customer_name = $quote->getCustomerName();
     1858       
     1859        // Format amount
     1860        $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote);
     1861        $formatted_amount = $formatter->format($quote->getTotal());
     1862       
     1863        $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice');
     1864        $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice');
     1865       
     1866        $message = __('Hello,', 'easy-invoice');
     1867        $message .= "\n\n";
     1868        $message .= sprintf(
     1869            __('The quote %s for %s has been %s by the client.', 'easy-invoice'),
     1870            $quote_number,
     1871            $customer_name,
     1872            $action_label
     1873        );
     1874        $message .= "\n\n";
     1875        $message .= __('Quote Details:', 'easy-invoice');
     1876        $message .= "\n";
     1877        $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number);
     1878        $message .= "\n";
     1879        $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name);
     1880        $message .= "\n";
     1881        $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount);
     1882        $message .= "\n";
     1883        $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format')));
     1884        $message .= "\n\n";
     1885        $message .= __('You can view the quote at:', 'easy-invoice');
     1886        $message .= "\n";
     1887        $message .= get_permalink($quote->getId());
     1888        $message .= "\n\n";
     1889        $message .= __('Best regards,', 'easy-invoice');
     1890        $message .= "\n";
     1891        $message .= $site_name;
     1892       
     1893        return $message;
     1894    }
     1895   
     1896    /**
     1897     * Handle payment completed hook
     1898     * Sends admin notification and customer confirmation when payment is completed
     1899     *
     1900     * @param int $invoice_id Invoice ID
     1901     * @param \EasyInvoice\Models\Invoice $invoice Invoice object
     1902     * @param array $payment_data Payment data (method, gateway, transaction_id, amount)
     1903     * @return void
     1904     */
     1905    public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void {
     1906        if (!$invoice || !$invoice->getId()) {
     1907            return;
     1908        }
     1909       
     1910        // Send admin notification
     1911        $this->sendAdminPaymentNotification($invoice, $payment_data);
     1912       
     1913        // Send customer confirmation email (without BCC to admin since we already sent admin notification)
     1914        // We pass a flag to skip BCC for payment confirmations
     1915        $this->sendPaymentConfirmationEmail($invoice, array_merge($payment_data, ['skip_bcc' => true]));
     1916    }
     1917   
     1918    /**
     1919     * Get payment method label
     1920     *
     1921     * @param string $method Payment method
     1922     * @return string Payment method label
     1923     */
     1924    private function getPaymentMethodLabel(string $method): string {
     1925        $labels = [
     1926            'bank' => __('Bank Transfer', 'easy-invoice'),
     1927            'cheque' => __('Cheque', 'easy-invoice'),
     1928            'paypal' => __('PayPal', 'easy-invoice'),
     1929            'stripe' => __('Stripe', 'easy-invoice'),
     1930            'square' => __('Square', 'easy-invoice'),
     1931            'mollie' => __('Mollie', 'easy-invoice'),
     1932            'authorizenet' => __('Authorize.Net', 'easy-invoice'),
     1933            'manual' => __('Manual Payment', 'easy-invoice'),
     1934            'online' => __('Online Payment', 'easy-invoice'),
     1935        ];
     1936       
     1937        return $labels[$method] ?? ucfirst($method);
     1938    }
     1939   
    14691940}
  • easy-invoice/tags/2.1.4/includes/Traits/PaymentCalculationTrait.php

    r3346980 r3393362  
    8686            }
    8787           
     88            // Trigger hook when invoice is marked as paid (for email notifications, etc.)
     89            // Get payment method and transaction ID from the most recent payment
     90            $payment_method = get_post_meta($invoice_id, '_payment_method', true) ?: $gateway_name ?: 'online';
     91            $transaction_id = get_post_meta($invoice_id, '_transaction_id', true) ?: '';
     92            $payment_amount = $invoice->getTotal();
     93           
     94            do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [
     95                'payment_method' => $payment_method,
     96                'gateway_name' => $gateway_name,
     97                'transaction_id' => $transaction_id,
     98                'amount' => $payment_amount
     99            ]);
     100           
    88101            return true;
    89102        } else {
  • easy-invoice/tags/2.1.4/readme.txt

    r3388559 r3393362  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 2.1.3
     7Stable tag: 2.1.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Create professional invoices and quotes with PDF export and online payments. Complete WordPress invoice plugin for billing and client management.
     11Create professional invoices, accept payments, and manage clients directly from WordPress.
    1212
    1313== Description ==
     
    138138== Changelog ==
    139139
     140= 2.1.4 - 2025-11-11 =
     141* Fixed - Email issue fixed
     142* Fixed - allowed html tag for textarea friend
     143* Fixed - Empty data Saving issue fixed for the description
    140144
    141145= 2.1.3 - 2025-11-03 =
  • easy-invoice/trunk/easy-invoice.php

    r3388559 r3393362  
    44 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice
    55 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease.
    6  * Version: 2.1.3
     6 * Version: 2.1.4
    77 * Author: MatrixAddons
    88 * Author URI: https://matrixaddons.com
     
    2525
    2626// Define plugin constants.
    27 define( 'EASY_INVOICE_VERSION', '2.1.3' );
     27define( 'EASY_INVOICE_VERSION', '2.1.4' );
    2828define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ );
    2929define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • easy-invoice/trunk/includes/Admin/AdminController.php

    r3345595 r3393362  
    3838     */
    3939    public function sendManualPaymentNotification($invoice_id, $payment_method) {
    40         // Get admin email
    41         $admin_email = get_option('admin_email');
    42        
    4340        // Get invoice details
    4441        $invoice = new \EasyInvoice\Models\Invoice(get_post($invoice_id));
    4542       
    46         // Get customer details
    47         $customer_name = $invoice->getCustomerName();
    48         $customer_email = $invoice->getCustomerEmail();
     43        if (!$invoice || !$invoice->getId()) {
     44            return;
     45        }
    4946       
    50         // Format amount
    51         $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
    52         $amount = $formatter->format($invoice->getTotal());
    53        
    54         // Format payment method
    55         $payment_method_label = $payment_method === 'bank' ? 'Bank Transfer' : 'Cheque';
    56        
    57         // Email subject
    58         $subject = sprintf(
    59             __('New %s Payment Received - Invoice #%s', 'easy-invoice'),
    60             $payment_method_label,
    61             $invoice->getNumber()
    62         );
    63        
    64         // Email message
    65         $message = sprintf(
    66             __('A new %s payment has been submitted for invoice #%s.
    67 
    68 Invoice Details:
    69 - Amount: %s
    70 - Customer: %s
    71 - Email: %s
    72 
    73 Please review and verify this payment in the admin dashboard:
    74 %s
    75 
    76 This is an automated message from Easy Invoice.', 'easy-invoice'),
    77             $payment_method_label,
    78             $invoice->getNumber(),
    79             $amount,
    80             $customer_name,
    81             $customer_email,
    82             admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id)
    83         );
    84        
    85         // Send email
    86         wp_mail($admin_email, $subject, $message);
     47        // Use EmailManager to send admin notification
     48        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     49        $email_manager->sendAdminPaymentNotification($invoice, [
     50            'payment_method' => $payment_method
     51        ]);
    8752    }
    8853
  • easy-invoice/trunk/includes/Controllers/PaymentController.php

    r3346980 r3393362  
    694694            $payment = Payment::create($payment_data);
    695695           
     696            // Store payment details before updating status (for the hook)
     697            $invoice->setMeta('_payment_method', $payment_method);
     698            if ($transaction_id) {
     699                $invoice->setMeta('_transaction_id', $transaction_id);
     700            }
     701           
    696702            // Update invoice status to paid only if total payments are sufficient
     703            // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
    697704            $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'manual');
    698705           
     
    765772    private function sendPaymentConfirmationEmail($invoice_id, $payment_id): void {
    766773        $invoice = new Invoice(get_post($invoice_id));
    767         $customer_email = $invoice->getCustomerEmail();
    768        
    769         if (!$customer_email) {
    770             return;
    771         }
    772        
    773         // Get currency settings
    774         $settings_controller = new \EasyInvoice\Controllers\SettingsController();
    775         $settings = $settings_controller->getSettings();
    776         $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
    777         $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
    778        
    779         $site_name = get_bloginfo('name');
    780         $invoice_number = $invoice->getNumber();
    781         $amount = $invoice->getTotal();
    782         $formatted_amount = $currency_symbol . number_format($amount, 2);
    783        
    784         $subject = sprintf(__('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    785        
    786         $message = sprintf(
    787             __('Dear %s,', 'easy-invoice'),
    788             $invoice->getCustomerName()
    789         );
    790         $message .= "\n\n";
    791         $message .= sprintf(
    792             __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
    793             $formatted_amount,
    794             $invoice_number
    795         );
    796         $message .= "\n\n";
    797         $message .= __('Thank you for your business.', 'easy-invoice');
    798         $message .= "\n\n";
    799         $message .= sprintf(__('Regards,', 'easy-invoice'));
    800         $message .= "\n";
    801         $message .= get_option('easy_invoice_company_name', $site_name);
    802        
    803         wp_mail($customer_email, $subject, $message);
     774       
     775        if (!$invoice || !$invoice->getId()) {
     776            return;
     777        }
     778       
     779        // Use EmailManager to send payment confirmation
     780        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     781        $email_manager->sendPaymentConfirmationEmail($invoice, [
     782            'payment_id' => $payment_id
     783        ]);
    804784    }
    805785   
     
    812792    private function sendPaymentRejectionEmail($invoice_id, $reason): void {
    813793        $invoice = new Invoice(get_post($invoice_id));
    814         $customer_email = $invoice->getCustomerEmail();
    815        
    816         if (!$customer_email) {
    817             return;
    818         }
    819        
    820         // Get currency settings
    821         $settings_controller = new \EasyInvoice\Controllers\SettingsController();
    822         $settings = $settings_controller->getSettings();
    823         $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
    824         $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
    825        
    826         $site_name = get_bloginfo('name');
    827         $invoice_number = $invoice->getNumber();
    828         $amount = $invoice->getTotal();
    829         $formatted_amount = $currency_symbol . number_format($amount, 2);
    830        
    831         $subject = sprintf(__('[%s] Payment Issue - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    832        
    833         $message = sprintf(
    834             __('Dear %s,', 'easy-invoice'),
    835             $invoice->getCustomerName()
    836         );
    837         $message .= "\n\n";
    838         $message .= sprintf(
    839             __('We regret to inform you that we could not process your payment of %s for Invoice #%s.', 'easy-invoice'),
    840             $formatted_amount,
    841             $invoice_number
    842         );
    843         $message .= "\n\n";
    844        
    845         if ($reason) {
    846             $message .= __('Reason:', 'easy-invoice') . "\n";
    847             $message .= $reason;
    848             $message .= "\n\n";
    849         }
    850        
    851         $message .= __('Please contact us to arrange an alternative payment method.', 'easy-invoice');
    852         $message .= "\n\n";
    853         $message .= sprintf(__('Regards,', 'easy-invoice'));
    854         $message .= "\n";
    855         $message .= get_option('easy_invoice_company_name', $site_name);
    856        
    857         wp_mail($customer_email, $subject, $message);
     794       
     795        if (!$invoice || !$invoice->getId()) {
     796            return;
     797        }
     798       
     799        // Use EmailManager to send payment rejection
     800        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     801        $email_manager->sendPaymentRejectionEmail($invoice, $reason);
    858802    }
    859803   
     
    938882            foreach ($pending_invoices as $post) {
    939883                $invoice = new Invoice($post);
    940                 $customer_email = $invoice->getCustomerEmail();
    941                
    942                 if (!$customer_email) {
     884               
     885                if (!$invoice || !$invoice->getId()) {
    943886                    continue;
    944887                }
    945888               
    946                 $site_name = get_bloginfo('name');
    947                 $invoice_number = $invoice->getNumber();
    948                 $amount = $invoice->getTotal();
    949                 $formatted_amount = $currency_symbol . number_format($amount, 2);
    950                 $payment_method = get_post_meta($invoice->getId(), '_payment_method', true);
    951                 $payment_method_label = $payment_method === 'bank' ? __('Bank Transfer', 'easy-invoice') : __('Cheque', 'easy-invoice');
    952                
    953                 $subject = sprintf(__('[%s] Payment Reminder - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
    954                
    955                 $message = sprintf(
    956                     __('Dear %s,', 'easy-invoice'),
    957                     $invoice->getCustomerName()
    958                 );
    959                 $message .= "\n\n";
    960                 $message .= sprintf(
    961                     __('This is a friendly reminder that we are still awaiting your %s payment of %s for Invoice #%s.', 'easy-invoice'),
    962                     $payment_method_label,
    963                     $formatted_amount,
    964                     $invoice_number
    965                 );
    966                 $message .= "\n\n";
    967                 $message .= __('If you have already sent the payment, please disregard this reminder. If not, please arrange for payment at your earliest convenience.', 'easy-invoice');
    968                 $message .= "\n\n";
    969                 $message .= sprintf(__('Regards,', 'easy-invoice'));
    970                 $message .= "\n";
    971                 $message .= get_option('easy_invoice_company_name', $site_name);
    972                
    973                 wp_mail($customer_email, $subject, $message);
    974                
    975                 // Mark reminder as sent
    976                 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql'));
     889                // Use EmailManager to send payment reminder
     890                $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     891                $result = $email_manager->sendInvoiceEmail($invoice, 'reminder', [
     892                    'payment_method' => get_post_meta($invoice->getId(), '_payment_method', true)
     893                ]);
     894               
     895                // Mark reminder as sent if email was sent successfully
     896                if ($result['success']) {
     897                    update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql'));
     898                }
    977899            }
    978900           
     
    11881110        }
    11891111
     1112        // Store payment details before updating status (for the hook)
     1113        $transaction_id = get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id;
     1114        $invoice->setMeta('_payment_method', $payment_method);
     1115        $invoice->setMeta('_transaction_id', $transaction_id);
     1116       
     1117        // Update invoice status to paid
     1118        // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
     1119        $invoice->setStatus('paid');
     1120        $invoice->save();
     1121       
     1122        // Trigger the payment completed hook manually since we're updating status directly
     1123        do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [
     1124            'payment_method' => $payment_method,
     1125            'gateway_name' => 'manual',
     1126            'transaction_id' => $transaction_id,
     1127            'amount' => $invoice->getTotal()
     1128        ]);
     1129       
    11901130        // Trigger email confirmation and actions only if we have a payment_id
    11911131        if ($payment_id) {
     1132            // Send confirmation email to customer
    11921133            $this->sendPaymentConfirmationEmail($invoice_id, $payment_id);
    11931134            do_action('easy_invoice_manual_payment_confirmed', $invoice_id, $payment_id, $payment_method);
  • easy-invoice/trunk/includes/Controllers/QuoteController.php

    r3387040 r3393362  
    13491349     */
    13501350    private function sendQuoteAcceptanceNotification($quote): void {
    1351         $admin_email = get_option('admin_email');
    1352         $site_name = get_bloginfo('name');
    1353        
    1354         $subject = sprintf(__('Quote %s has been accepted', 'easy-invoice'), $quote->getNumber());
    1355        
    1356         $message = sprintf(
    1357             __('Hello,
    1358 
    1359 The quote %s for %s has been accepted by the client.
    1360 
    1361 Quote Details:
    1362 - Quote Number: %s
    1363 - Client: %s
    1364 - Total Amount: %s
    1365 - Accepted Date: %s
    1366 
    1367 You can view the quote at: %s
    1368 
    1369 Best regards,
    1370 %s', 'easy-invoice'),
    1371             $quote->getNumber(),
    1372             $quote->getCustomerName(),
    1373             $quote->getNumber(),
    1374             $quote->getCustomerName(),
    1375             $this->formatCurrency($quote->getTotal(), $quote),
    1376             date_i18n(get_option('date_format') . ' ' . get_option('time_format')),
    1377             get_permalink($quote->getId()),
    1378             $site_name
    1379         );
    1380        
    1381         wp_mail($admin_email, $subject, $message);
     1351        // Use EmailManager to send admin notification
     1352        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     1353        $email_manager->sendAdminQuoteNotification($quote, 'accepted');
    13821354    }
    13831355   
     
    13881360     */
    13891361    private function sendQuoteDeclineNotification($quote): void {
    1390         $admin_email = get_option('admin_email');
    1391         $site_name = get_bloginfo('name');
    1392        
    1393         $subject = sprintf(__('Quote %s has been declined', 'easy-invoice'), $quote->getNumber());
    1394        
    1395         $message = sprintf(
    1396             __('Hello,
    1397 
    1398 The quote %s for %s has been declined by the client.
    1399 
    1400 Quote Details:
    1401 - Quote Number: %s
    1402 - Client: %s
    1403 - Total Amount: %s
    1404 - Declined Date: %s
    1405 
    1406 You can view the quote at: %s
    1407 
    1408 Best regards,
    1409 %s', 'easy-invoice'),
    1410             $quote->getNumber(),
    1411             $quote->getCustomerName(),
    1412             $quote->getNumber(),
    1413             $quote->getCustomerName(),
    1414             $this->formatCurrency($quote->getTotal(), $quote),
    1415             date_i18n(get_option('date_format') . ' ' . get_option('time_format')),
    1416             get_permalink($quote->getId()),
    1417             $site_name
    1418         );
    1419        
    1420         wp_mail($admin_email, $subject, $message);
     1362        // Use EmailManager to send admin notification
     1363        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
     1364        $email_manager->sendAdminQuoteNotification($quote, 'declined');
    14211365    }
    14221366   
  • easy-invoice/trunk/includes/Forms/FormProcessor.php

    r3348168 r3393362  
    2323   
    2424    /**
     25     * Allowed HTML tags for textarea fields
     26     *
     27     * @since 1.0.0
     28     * @var array
     29     */
     30    private static $allowed_textarea_tags = [
     31        'p' => [],
     32        'br' => [],
     33        'strong' => [],
     34        'em' => [],
     35        'i' => [],
     36        'b' => [],
     37        'u' => [],
     38        'ul' => [],
     39        'ol' => [],
     40        'li' => [],
     41        'h1' => [],
     42        'h2' => [],
     43        'h3' => [],
     44        'h4' => [],
     45        'h5' => [],
     46        'h6' => [],
     47        'a' => [
     48            'href' => [],
     49            'title' => [],
     50            'target' => [],
     51        ],
     52        'span' => [],
     53        'div' => [],
     54    ];
     55   
     56    /**
     57     * Sanitize textarea field allowing basic HTML tags
     58     *
     59     * @since 1.0.0
     60     * @param string $value The value to sanitize
     61     * @return string Sanitized value with allowed HTML tags
     62     */
     63    public static function sanitizeTextareaWithHtml(string $value): string {
     64        // Use wp_kses to allow only safe HTML tags
     65        return wp_kses($value, self::$allowed_textarea_tags);
     66    }
     67   
     68    /**
    2569     * Process form data
    2670     *
     
    5296            }
    5397            $required = $field['required'] ?? false;
     98            $field_type = $field['type'] ?? 'text';
    5499            $raw_value = $raw_data[$field_name] ?? '';
    55100           
     
    63108            }
    64109           
    65             // Skip empty non-required fields
    66             if (empty($raw_value) && !$required && $raw_value !== '0') {
     110            // Always include textarea fields (like description) even when empty
     111            // This allows users to clear these fields by submitting empty values
     112            $always_include_fields = ['description', 'notes', 'terms', 'internal_notes'];
     113            $should_always_include = in_array($field_name, $always_include_fields) || $field_type === 'textarea';
     114           
     115            // Skip empty non-required fields (unless they should always be included)
     116            if (empty($raw_value) && !$required && $raw_value !== '0' && !$should_always_include) {
    67117                continue;
    68118            }
     
    208258            $value = $form_data[$field_name] ?? null;
    209259           
    210 
    211            
    212             // Only save if value exists and is not empty (or is 0)
    213             if ($value !== null && $value !== '') {
     260            // Fields that should always be saved, even if empty (to allow clearing)
     261            $always_save_fields = ['description', 'notes', 'terms', 'internal_notes'];
     262            $should_always_save = in_array($field_name, $always_save_fields);
     263           
     264            // Save if value exists and is not empty (or is 0), OR if it's a field that should always be saved
     265            if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) {
    214266                // Use custom save callback if provided
    215267                if (isset($field['save_callback']) && is_callable($field['save_callback'])) {
    216                     $field['save_callback']($value, $model);
     268                    $field['save_callback']($value ?? '', $model);
    217269                } else {
    218270                    // Default save to meta data
    219271                    if (method_exists($model, 'setMetaData')) {
    220                         $model->setMetaData($db_key, $value);
    221                        
    222 
     272                        $model->setMetaData($db_key, $value ?? '');
    223273                    }
    224274                }
     
    227277       
    228278        // Also process any extra fields that might not be in the configuration
     279        $always_save_fields = ['description', 'notes', 'terms', 'internal_notes'];
    229280        foreach ($form_data as $field_name => $value) {
    230             if ($value !== null && $value !== '') {
     281            $should_always_save = in_array($field_name, $always_save_fields);
     282           
     283            if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) {
    231284                $db_key = '_easy_invoice_' . $field_name;
    232285               
     
    241294               
    242295                if (!$already_processed && method_exists($model, 'setMetaData')) {
    243                     $model->setMetaData($db_key, $value);
     296                    $model->setMetaData($db_key, $value ?? '');
    244297                }
    245298            }
     
    293346               
    294347            case 'textarea':
    295                 return sanitize_textarea_field($value);
     348                // Allow basic HTML tags in textarea fields
     349                return self::sanitizeTextareaWithHtml($value);
    296350               
    297351            case 'email':
     
    324378       
    325379        if (is_callable($sanitize_callback)) {
     380            // If callback is 'sanitize_textarea_field', use our HTML-allowing version for textarea fields
     381            if ($sanitize_callback === 'sanitize_textarea_field' && ($field['type'] ?? '') === 'textarea') {
     382                return self::sanitizeTextareaWithHtml($value);
     383            }
    326384            return $sanitize_callback($value);
    327385        }
  • easy-invoice/trunk/includes/Gateways/PayPalGateway.php

    r3346980 r3393362  
    539539       
    540540        // Update invoice status to paid only if total payments are sufficient
     541        // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification
    541542        $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'paypal');
    542543       
     
    546547             wp_update_post(['ID' => $invoice_id, 'post_status' => 'publish']);
    547548        }
     549       
     550        // Store payment details for the hook
     551        $invoice->setMeta('_payment_method', 'paypal');
     552        $invoice->setMeta('_transaction_id', $paypal_txn_id);
    548553
    549554        return [
  • easy-invoice/trunk/includes/Models/Invoice.php

    r3348168 r3393362  
    446446           
    447447            // Get the value from dynamic data array
    448             $value = $this->data[$field_name] ?? null;
    449             if ($value !== null) {
     448            // Save all fields that exist in the data array (including empty strings to allow clearing fields)
     449            // If a field exists in $this->data, it means it was explicitly set, so we should save it
     450            if (array_key_exists($field_name, $this->data)) {
     451                $value = $this->data[$field_name];
    450452                update_post_meta($this->id, $meta_key, $value);
    451453            }
  • easy-invoice/trunk/includes/Models/Quote.php

    r3363158 r3393362  
    486486           
    487487            // Get the value from dynamic data array
    488             $value = $this->data[$field_name] ?? null;
    489             if ($value !== null) {
    490                
    491                
     488            // Save all fields that exist in the data array (including empty strings to allow clearing fields)
     489            // If a field exists in $this->data, it means it was explicitly set, so we should save it
     490            if (array_key_exists($field_name, $this->data)) {
     491                $value = $this->data[$field_name];
    492492                update_post_meta($this->id, $meta_key, $value);
    493                
    494                 // Debug: Log template field saving to database
    495 
    496493            }
    497494        }
  • easy-invoice/trunk/includes/Repositories/InvoiceRepository.php

    r3387051 r3393362  
    397397        }
    398398       
    399         if (isset($data['description'])) {
    400             $invoice->setDescription($data['description']);
    401         }
    402        
    403         if (isset($data['invoice_description'])) {
    404             $invoice->setDescription($data['invoice_description']);
     399        // Always update description if it exists in data (even if empty, to allow clearing)
     400        if (array_key_exists('description', $data)) {
     401            $invoice->setDescription($data['description'] ?? '');
     402        }
     403       
     404        if (array_key_exists('invoice_description', $data)) {
     405            $invoice->setDescription($data['invoice_description'] ?? '');
    405406        }
    406407       
  • easy-invoice/trunk/includes/Repositories/QuoteRepository.php

    r3387051 r3393362  
    495495        }
    496496       
    497         if (isset($data['description'])) {
    498             $quote->setDescription($data['description']);
     497        // Always update description if it exists in data (even if empty, to allow clearing)
     498        if (array_key_exists('description', $data)) {
     499            $quote->setDescription($data['description'] ?? '');
    499500        }
    500501       
  • easy-invoice/trunk/includes/Services/EmailManager.php

    r3387040 r3393362  
    109109        add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3);
    110110        add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3);
     111       
     112        // Listen for payment completion to send admin notifications
     113        add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3);
    111114    }
    112115   
     
    14671470    }
    14681471   
     1472    /**
     1473     * Send admin notification when payment is received
     1474     *
     1475     * @param Invoice $invoice The invoice
     1476     * @param array $payment_data Payment data (method, amount, etc.)
     1477     * @return array Result array with success status and message
     1478     */
     1479    public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array {
     1480        try {
     1481            // Validate invoice
     1482            if (!$invoice || !$invoice->getId()) {
     1483                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1484            }
     1485           
     1486            // Get admin email
     1487            $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
     1488            if (empty($admin_email)) {
     1489                return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
     1490            }
     1491           
     1492            // Get payment method
     1493            $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online';
     1494            $payment_method_label = $this->getPaymentMethodLabel($payment_method);
     1495           
     1496            // Format amount
     1497            $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
     1498            $amount = $formatter->format($invoice->getTotal());
     1499           
     1500            // Prepare email subject
     1501            $subject = sprintf(
     1502                __('New Payment Received - Invoice #%s', 'easy-invoice'),
     1503                $invoice->getNumber()
     1504            );
     1505           
     1506            // Prepare email message
     1507            $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data);
     1508           
     1509            // Add HTML wrapper if enabled
     1510            if ($this->settings['enable_html'] === 'yes') {
     1511                $message = $this->wrapInHtmlTemplate($message);
     1512            }
     1513           
     1514            // Prepare headers
     1515            $headers = $this->prepareEmailHeaders();
     1516           
     1517            // Send email
     1518            $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
     1519           
     1520            if ($sent) {
     1521                do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data);
     1522                return [
     1523                    'success' => true,
     1524                    'message' => __('Admin notification sent successfully', 'easy-invoice')
     1525                ];
     1526            } else {
     1527                do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data);
     1528                return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
     1529            }
     1530           
     1531        } catch (\Exception $e) {
     1532            $this->log('Admin payment notification error: ' . $e->getMessage(), 'error');
     1533            return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
     1534        }
     1535    }
     1536   
     1537    /**
     1538     * Send payment confirmation email to customer
     1539     *
     1540     * @param Invoice $invoice The invoice
     1541     * @param array $payment_data Payment data
     1542     * @return array Result array with success status and message
     1543     */
     1544    public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array {
     1545        try {
     1546            // Validate invoice
     1547            if (!$invoice || !$invoice->getId()) {
     1548                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1549            }
     1550           
     1551            // Get customer email
     1552            $customer_email = $invoice->getCustomerEmail();
     1553            if (empty($customer_email)) {
     1554                return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
     1555            }
     1556           
     1557            // Get currency settings
     1558            $settings_controller = new \EasyInvoice\Controllers\SettingsController();
     1559            $settings = $settings_controller->getSettings();
     1560            $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
     1561            $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
     1562           
     1563            // Format amount
     1564            $amount = $invoice->getTotal();
     1565            $formatted_amount = $currency_symbol . number_format($amount, 2);
     1566           
     1567            // Prepare email subject
     1568            $site_name = get_bloginfo('name');
     1569            $subject = sprintf(
     1570                __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'),
     1571                $site_name,
     1572                $invoice->getNumber()
     1573            );
     1574           
     1575            // Prepare email message
     1576            $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount);
     1577           
     1578            // Add HTML wrapper if enabled
     1579            if ($this->settings['enable_html'] === 'yes') {
     1580                $message = $this->wrapInHtmlTemplate($message);
     1581            }
     1582           
     1583            // Prepare headers
     1584            $headers = $this->prepareEmailHeaders();
     1585           
     1586            // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate)
     1587            // The payment completion hook already sends a dedicated admin notification
     1588            $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true;
     1589            if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
     1590                $headers[] = 'Bcc: ' . $this->settings['admin_email'];
     1591            }
     1592           
     1593            // Send email
     1594            $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
     1595           
     1596            if ($sent) {
     1597                do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data);
     1598                return [
     1599                    'success' => true,
     1600                    'message' => __('Payment confirmation email sent successfully', 'easy-invoice')
     1601                ];
     1602            } else {
     1603                do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data);
     1604                return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')];
     1605            }
     1606           
     1607        } catch (\Exception $e) {
     1608            $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error');
     1609            return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()];
     1610        }
     1611    }
     1612   
     1613    /**
     1614     * Send payment rejection email to customer
     1615     *
     1616     * @param Invoice $invoice The invoice
     1617     * @param string $reason Rejection reason
     1618     * @return array Result array with success status and message
     1619     */
     1620    public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array {
     1621        try {
     1622            // Validate invoice
     1623            if (!$invoice || !$invoice->getId()) {
     1624                return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
     1625            }
     1626           
     1627            // Get customer email
     1628            $customer_email = $invoice->getCustomerEmail();
     1629            if (empty($customer_email)) {
     1630                return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
     1631            }
     1632           
     1633            // Prepare email subject
     1634            $site_name = get_bloginfo('name');
     1635            $subject = sprintf(
     1636                __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'),
     1637                $site_name,
     1638                $invoice->getNumber()
     1639            );
     1640           
     1641            // Prepare email message
     1642            $message = $this->preparePaymentRejectionMessage($invoice, $reason);
     1643           
     1644            // Add HTML wrapper if enabled
     1645            if ($this->settings['enable_html'] === 'yes') {
     1646                $message = $this->wrapInHtmlTemplate($message);
     1647            }
     1648           
     1649            // Prepare headers
     1650            $headers = $this->prepareEmailHeaders();
     1651           
     1652            // Add BCC to admin if enabled
     1653            if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
     1654                $headers[] = 'Bcc: ' . $this->settings['admin_email'];
     1655            }
     1656           
     1657            // Send email
     1658            $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
     1659           
     1660            if ($sent) {
     1661                do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason);
     1662                return [
     1663                    'success' => true,
     1664                    'message' => __('Payment rejection email sent successfully', 'easy-invoice')
     1665                ];
     1666            } else {
     1667                do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason);
     1668                return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')];
     1669            }
     1670           
     1671        } catch (\Exception $e) {
     1672            $this->log('Payment rejection email error: ' . $e->getMessage(), 'error');
     1673            return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()];
     1674        }
     1675    }
     1676   
     1677    /**
     1678     * Prepare admin payment notification message
     1679     *
     1680     * @param Invoice $invoice The invoice
     1681     * @param string $payment_method_label Payment method label
     1682     * @param string $amount Formatted amount
     1683     * @param array $payment_data Payment data
     1684     * @return string Email message
     1685     */
     1686    private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string {
     1687        $invoice_number = $invoice->getNumber();
     1688        $customer_name = $invoice->getCustomerName();
     1689        $customer_email = $invoice->getCustomerEmail();
     1690        $invoice_id = $invoice->getId();
     1691       
     1692        $message = sprintf(
     1693            __('A new %s payment has been received for invoice #%s.', 'easy-invoice'),
     1694            $payment_method_label,
     1695            $invoice_number
     1696        );
     1697        $message .= "\n\n";
     1698        $message .= __('Invoice Details:', 'easy-invoice');
     1699        $message .= "\n";
     1700        $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount);
     1701        $message .= "\n";
     1702        $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name);
     1703        $message .= "\n";
     1704        $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email);
     1705       
     1706        // Add transaction ID if available
     1707        if (!empty($payment_data['transaction_id'])) {
     1708            $message .= "\n";
     1709            $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']);
     1710        }
     1711       
     1712        $message .= "\n\n";
     1713        $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice');
     1714        $message .= "\n";
     1715        $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id);
     1716        $message .= "\n\n";
     1717        $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice');
     1718       
     1719        return $message;
     1720    }
     1721   
     1722    /**
     1723     * Prepare payment confirmation message
     1724     *
     1725     * @param Invoice $invoice The invoice
     1726     * @param string $formatted_amount Formatted amount
     1727     * @return string Email message
     1728     */
     1729    private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string {
     1730        $customer_name = $invoice->getCustomerName();
     1731        $invoice_number = $invoice->getNumber();
     1732        $site_name = get_bloginfo('name');
     1733        $company_name = get_option('easy_invoice_company_name', $site_name);
     1734       
     1735        $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
     1736        $message .= "\n\n";
     1737        $message .= sprintf(
     1738            __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
     1739            $formatted_amount,
     1740            $invoice_number
     1741        );
     1742        $message .= "\n\n";
     1743        $message .= __('Thank you for your business.', 'easy-invoice');
     1744        $message .= "\n\n";
     1745        $message .= __('Regards,', 'easy-invoice');
     1746        $message .= "\n";
     1747        $message .= $company_name;
     1748       
     1749        return $message;
     1750    }
     1751   
     1752    /**
     1753     * Prepare payment rejection message
     1754     *
     1755     * @param Invoice $invoice The invoice
     1756     * @param string $reason Rejection reason
     1757     * @return string Email message
     1758     */
     1759    private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string {
     1760        $customer_name = $invoice->getCustomerName();
     1761        $invoice_number = $invoice->getNumber();
     1762        $site_name = get_bloginfo('name');
     1763        $company_name = get_option('easy_invoice_company_name', $site_name);
     1764       
     1765        $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
     1766        $message .= "\n\n";
     1767        $message .= sprintf(
     1768            __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'),
     1769            $invoice_number
     1770        );
     1771       
     1772        if (!empty($reason)) {
     1773            $message .= "\n\n";
     1774            $message .= __('Reason:', 'easy-invoice');
     1775            $message .= "\n";
     1776            $message .= $reason;
     1777        }
     1778       
     1779        $message .= "\n\n";
     1780        $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice');
     1781        $message .= "\n\n";
     1782        $message .= __('Regards,', 'easy-invoice');
     1783        $message .= "\n";
     1784        $message .= $company_name;
     1785       
     1786        return $message;
     1787    }
     1788   
     1789    /**
     1790     * Send admin notification for quote acceptance/decline
     1791     *
     1792     * @param Quote $quote The quote
     1793     * @param string $action Action type ('accepted' or 'declined')
     1794     * @return array Result array with success status and message
     1795     */
     1796    public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array {
     1797        try {
     1798            // Validate quote
     1799            if (!$quote || !$quote->getId()) {
     1800                return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
     1801            }
     1802           
     1803            // Get admin email
     1804            $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
     1805            if (empty($admin_email)) {
     1806                return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
     1807            }
     1808           
     1809            // Prepare email subject
     1810            $subject = sprintf(
     1811                __('Quote %s has been %s', 'easy-invoice'),
     1812                $quote->getNumber(),
     1813                $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice')
     1814            );
     1815           
     1816            // Prepare email message
     1817            $message = $this->prepareAdminQuoteNotificationMessage($quote, $action);
     1818           
     1819            // Add HTML wrapper if enabled
     1820            if ($this->settings['enable_html'] === 'yes') {
     1821                $message = $this->wrapInHtmlTemplate($message);
     1822            }
     1823           
     1824            // Prepare headers
     1825            $headers = $this->prepareEmailHeaders();
     1826           
     1827            // Send email
     1828            $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
     1829           
     1830            if ($sent) {
     1831                do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action);
     1832                return [
     1833                    'success' => true,
     1834                    'message' => __('Admin notification sent successfully', 'easy-invoice')
     1835                ];
     1836            } else {
     1837                do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action);
     1838                return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
     1839            }
     1840           
     1841        } catch (\Exception $e) {
     1842            $this->log('Admin quote notification error: ' . $e->getMessage(), 'error');
     1843            return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
     1844        }
     1845    }
     1846   
     1847    /**
     1848     * Prepare admin quote notification message
     1849     *
     1850     * @param Quote $quote The quote
     1851     * @param string $action Action type ('accepted' or 'declined')
     1852     * @return string Email message
     1853     */
     1854    private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string {
     1855        $site_name = get_bloginfo('name');
     1856        $quote_number = $quote->getNumber();
     1857        $customer_name = $quote->getCustomerName();
     1858       
     1859        // Format amount
     1860        $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote);
     1861        $formatted_amount = $formatter->format($quote->getTotal());
     1862       
     1863        $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice');
     1864        $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice');
     1865       
     1866        $message = __('Hello,', 'easy-invoice');
     1867        $message .= "\n\n";
     1868        $message .= sprintf(
     1869            __('The quote %s for %s has been %s by the client.', 'easy-invoice'),
     1870            $quote_number,
     1871            $customer_name,
     1872            $action_label
     1873        );
     1874        $message .= "\n\n";
     1875        $message .= __('Quote Details:', 'easy-invoice');
     1876        $message .= "\n";
     1877        $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number);
     1878        $message .= "\n";
     1879        $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name);
     1880        $message .= "\n";
     1881        $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount);
     1882        $message .= "\n";
     1883        $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format')));
     1884        $message .= "\n\n";
     1885        $message .= __('You can view the quote at:', 'easy-invoice');
     1886        $message .= "\n";
     1887        $message .= get_permalink($quote->getId());
     1888        $message .= "\n\n";
     1889        $message .= __('Best regards,', 'easy-invoice');
     1890        $message .= "\n";
     1891        $message .= $site_name;
     1892       
     1893        return $message;
     1894    }
     1895   
     1896    /**
     1897     * Handle payment completed hook
     1898     * Sends admin notification and customer confirmation when payment is completed
     1899     *
     1900     * @param int $invoice_id Invoice ID
     1901     * @param \EasyInvoice\Models\Invoice $invoice Invoice object
     1902     * @param array $payment_data Payment data (method, gateway, transaction_id, amount)
     1903     * @return void
     1904     */
     1905    public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void {
     1906        if (!$invoice || !$invoice->getId()) {
     1907            return;
     1908        }
     1909       
     1910        // Send admin notification
     1911        $this->sendAdminPaymentNotification($invoice, $payment_data);
     1912       
     1913        // Send customer confirmation email (without BCC to admin since we already sent admin notification)
     1914        // We pass a flag to skip BCC for payment confirmations
     1915        $this->sendPaymentConfirmationEmail($invoice, array_merge($payment_data, ['skip_bcc' => true]));
     1916    }
     1917   
     1918    /**
     1919     * Get payment method label
     1920     *
     1921     * @param string $method Payment method
     1922     * @return string Payment method label
     1923     */
     1924    private function getPaymentMethodLabel(string $method): string {
     1925        $labels = [
     1926            'bank' => __('Bank Transfer', 'easy-invoice'),
     1927            'cheque' => __('Cheque', 'easy-invoice'),
     1928            'paypal' => __('PayPal', 'easy-invoice'),
     1929            'stripe' => __('Stripe', 'easy-invoice'),
     1930            'square' => __('Square', 'easy-invoice'),
     1931            'mollie' => __('Mollie', 'easy-invoice'),
     1932            'authorizenet' => __('Authorize.Net', 'easy-invoice'),
     1933            'manual' => __('Manual Payment', 'easy-invoice'),
     1934            'online' => __('Online Payment', 'easy-invoice'),
     1935        ];
     1936       
     1937        return $labels[$method] ?? ucfirst($method);
     1938    }
     1939   
    14691940}
  • easy-invoice/trunk/includes/Traits/PaymentCalculationTrait.php

    r3346980 r3393362  
    8686            }
    8787           
     88            // Trigger hook when invoice is marked as paid (for email notifications, etc.)
     89            // Get payment method and transaction ID from the most recent payment
     90            $payment_method = get_post_meta($invoice_id, '_payment_method', true) ?: $gateway_name ?: 'online';
     91            $transaction_id = get_post_meta($invoice_id, '_transaction_id', true) ?: '';
     92            $payment_amount = $invoice->getTotal();
     93           
     94            do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [
     95                'payment_method' => $payment_method,
     96                'gateway_name' => $gateway_name,
     97                'transaction_id' => $transaction_id,
     98                'amount' => $payment_amount
     99            ]);
     100           
    88101            return true;
    89102        } else {
  • easy-invoice/trunk/readme.txt

    r3388559 r3393362  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 2.1.3
     7Stable tag: 2.1.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Create professional invoices and quotes with PDF export and online payments. Complete WordPress invoice plugin for billing and client management.
     11Create professional invoices, accept payments, and manage clients directly from WordPress.
    1212
    1313== Description ==
     
    138138== Changelog ==
    139139
     140= 2.1.4 - 2025-11-11 =
     141* Fixed - Email issue fixed
     142* Fixed - allowed html tag for textarea friend
     143* Fixed - Empty data Saving issue fixed for the description
    140144
    141145= 2.1.3 - 2025-11-03 =
Note: See TracChangeset for help on using the changeset viewer.