loadSettings(); $this->loadTemplates(); $this->initHooks(); } /** * Initialize hooks */ private function initHooks(): void { // Invoice/quote send-email AJAX is handled exclusively by EasyInvoice\Admin\EasyInvoiceAjax // (published-document checks, single handler) to avoid duplicate nopriv callbacks. // Add email settings to admin add_action('admin_init', [$this, 'registerEmailSettings']); add_filter('easy_invoice_settings_sections', [$this, 'addEmailSettingsSection']); // Refresh settings when they're updated add_action('update_option_easy_invoice_email_from_name', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_from_address', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_reply_to', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_reply_to_name', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_enable_email_styling', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_logo', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_footer_text', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_bcc_admin', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_admin_email', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_subject', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_body', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_quote_subject', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_quote_body', [$this, 'refreshSettings']); // Add email logs add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3); add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3); // Listen for payment completion to send admin notifications add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3); } /** * Load email settings */ private function loadSettings(): void { $this->settings = [ 'from_name' => get_option('easy_invoice_email_from_name', get_bloginfo('name')), 'from_email' => get_option('easy_invoice_email_from_address', get_bloginfo('admin_email')), 'reply_to_email' => get_option('easy_invoice_email_reply_to', ''), 'reply_to_name' => get_option('easy_invoice_email_reply_to_name', ''), 'enable_html' => get_option('easy_invoice_enable_email_styling', 'yes'), 'email_logo' => get_option('easy_invoice_email_logo', ''), 'footer_text' => get_option('easy_invoice_email_footer_text', ''), 'bcc_admin' => get_option('easy_invoice_bcc_admin', 'no'), 'admin_email' => get_option('easy_invoice_admin_email', get_option('admin_email')), ]; } /** * Load email templates */ private function loadTemplates(): void { $this->templates = [ 'invoice_new' => [ 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')), 'body' => get_option('easy_invoice_invoice_email_body', $this->getDefaultInvoiceTemplate()), 'type' => 'invoice' ], 'invoice_reminder' => [ 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_reminder_subject', 'Payment Reminder - Invoice #{{invoice_number}}'), 'body' => get_option('easy_invoice_reminder_body', $this->getDefaultReminderTemplate()), 'type' => 'invoice' ], 'invoice_paid' => [ 'enabled' => get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')), 'body' => get_option('easy_invoice_payment_email_body', $this->getDefaultPaymentTemplate()), 'type' => 'invoice' ], 'quote_new' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')), 'body' => get_option('easy_invoice_quote_email_body', $this->getDefaultQuoteTemplate()), 'type' => 'quote' ], 'quote_accepted' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_accepted_subject', 'Quote Accepted - #{{quote_number}}'), 'body' => get_option('easy_invoice_quote_accepted_body', $this->getDefaultQuoteAcceptedTemplate()), 'type' => 'quote' ], 'quote_declined' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_declined_subject', 'Quote Declined - #{{quote_number}}'), 'body' => get_option('easy_invoice_quote_declined_body', $this->getDefaultQuoteDeclinedTemplate()), 'type' => 'quote' ] ]; } /** * Send invoice email * * @param Invoice $invoice The invoice * @param string $template_type Template type (new, reminder, paid) * @param array $additional_data Additional data for template * @return array Result array with success status and message */ public function sendInvoiceEmail(Invoice $invoice, string $template_type = 'new', array $additional_data = []): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get client email $client_email = $invoice->getCustomerEmail(); if (empty($client_email)) { return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')]; } // Get template $template_key = 'invoice_' . $template_type; if (!isset($this->templates[$template_key])) { return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')]; } $template = $this->templates[$template_key]; // Check if email is enabled if (!$template['enabled']) { return ['success' => false, 'message' => __('Invoice available email is disabled', 'easy-invoice')]; } // Prepare email data $email_data = $this->prepareInvoiceEmailData($invoice, $template, $additional_data); // Send email $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); if ($sent) { // Log success do_action('easy_invoice_email_sent', $invoice, $client_email, $template_type); return [ 'success' => true, 'message' => __('Email sent successfully', 'easy-invoice'), 'email_data' => $email_data ]; } else { // Log failure do_action('easy_invoice_email_failed', $invoice, $client_email, $template_type); return ['success' => false, 'message' => __('Failed to send email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Email sending error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending email: ', 'easy-invoice') . $e->getMessage()]; } } /** * Send quote email * * @param Quote $quote The quote * @param string $template_type Template type (new, accepted, declined) * @param array $additional_data Additional data for template * @return array Result array with success status and message */ public function sendQuoteEmail(Quote $quote, string $template_type = 'new', array $additional_data = []): array { try { // Validate quote if (!$quote || !$quote->getId()) { return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')]; } // Get client email $client_email = $quote->getCustomerEmail(); if (empty($client_email)) { return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')]; } // Get template $template_key = 'quote_' . $template_type; if (!isset($this->templates[$template_key])) { return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')]; } $template = $this->templates[$template_key]; // Check if email is enabled if (!$template['enabled']) { return ['success' => false, 'message' => __('Quote available email is disabled', 'easy-invoice')]; } // Prepare email data $email_data = $this->prepareQuoteEmailData($quote, $template, $additional_data); // Send email $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); if ($sent) { // Log success do_action('easy_invoice_quote_email_sent', $quote, $client_email, $template_type); return [ 'success' => true, 'message' => __('Quote email sent successfully', 'easy-invoice'), 'email_data' => $email_data ]; } else { // Log failure do_action('easy_invoice_quote_email_failed', $quote, $client_email, $template_type); return ['success' => false, 'message' => __('Failed to send quote email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Quote email sending error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending quote email: ', 'easy-invoice') . $e->getMessage()]; } } /** * Prepare invoice email data * * @param Invoice $invoice The invoice * @param array $template The email template * @param array $additional_data Additional data * @return array Email data */ private function prepareInvoiceEmailData(Invoice $invoice, array $template, array $additional_data = []): array { // Get replacements $replacements = $this->getInvoiceReplacements($invoice, $additional_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); $message = do_shortcode($message); // Render shortcodes like [easy_invoice_url ...] // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $invoice->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Prepare quote email data * * @param Quote $quote The quote * @param array $template The email template * @param array $additional_data Additional data * @return array Email data */ private function prepareQuoteEmailData(Quote $quote, array $template, array $additional_data = []): array { // Get replacements $replacements = $this->getQuoteReplacements($quote, $additional_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $quote->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Prepare payment email data * * @param Invoice $invoice The invoice * @param array $template The email template * @param array $payment_data Payment data * @return array Email data */ private function preparePaymentEmailData(Invoice $invoice, array $template, array $payment_data = []): array { // Get replacements $replacements = $this->getPaymentReplacements($invoice, $payment_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $invoice->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Get invoice replacements * * @param Invoice $invoice The invoice * @param array $additional_data Additional data * @return array Replacements */ private function getInvoiceReplacements(Invoice $invoice, array $additional_data = []): array { $currency_symbol = get_option('easy_invoice_currency_symbol', '$'); // Secure link support $invoice_url = get_permalink($invoice->getId()); $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes'; if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) { $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId()); if ($secure_url) { $invoice_url = $secure_url; } } // SECURITY: attach a per-invoice access token to the outbound URL // so the legitimate email recipient can submit manual payments // without needing to log in. The token is verified server-side in // PaymentController::submitManualPayment via // InvoiceController::canSubmitPaymentForInvoice. Empty-token // guard so a CSPRNG failure doesn't produce malformed `?ik=` URLs. $invoice_access_token = \EasyInvoice\Controllers\InvoiceController::invoiceAccessToken((int) $invoice->getId()); if ($invoice_access_token !== '' && $invoice_url) { $invoice_url = add_query_arg('ik', $invoice_access_token, $invoice_url); } // Get client data for additional fields $client = null; if ($invoice->getClientId()) { $client_repository = new \EasyInvoice\Repositories\ClientRepository(); $client = $client_repository->find($invoice->getClientId()); } return array_merge([ '{{invoice_number}}' => $invoice->getNumber(), '{{invoice_title}}' => $invoice->getTitle(), '{{client_name}}' => $invoice->getCustomerName(), '{{client_email}}' => $invoice->getCustomerEmail(), '{{client_address}}' => $invoice->getCustomerAddress(), '{{client_first_name}}' => $client ? $client->getFirstName() : '', '{{client_last_name}}' => $client ? $client->getLastName() : '', '{{company_name}}' => get_bloginfo('name'), '{{company_email}}' => $this->settings['from_email'], '{{company_phone}}' => get_option('easy_invoice_company_phone', ''), '{{company_address}}' => get_option('easy_invoice_company_address', ''), '{{company_website}}' => get_option('easy_invoice_company_website', ''), '{{total_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTotal()), '{{subtotal}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getSubtotal()), '{{tax_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTaxAmount()), '{{discount_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getDiscountAmount()), '{{due_date}}' => date('F j, Y', strtotime($invoice->getDueDate())), '{{issue_date}}' => date('F j, Y', strtotime($invoice->getIssueDate())), '{{invoice_url}}' => $invoice_url, // Use correct (possibly secure) link '{{payment_url}}' => add_query_arg('payment', '1', get_permalink($invoice->getId())), '{{site_url}}' => get_site_url(), '{{admin_url}}' => admin_url(), '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')), ], $additional_data); } /** * Get quote replacements * * @param Quote $quote The quote * @param array $additional_data Additional data * @return array Replacements */ private function getQuoteReplacements(Quote $quote, array $additional_data = []): array { $currency_symbol = get_option('easy_invoice_currency_symbol', '$'); $quote_url = get_permalink($quote->getId()); $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes'; if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) { $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId()); if ($secure_url) { $quote_url = $secure_url; } } // SECURITY (CVE-2026-9021): attach the per-quote access token so // the emailed recipient lands on a page that renders the // Accept/Decline UI and can submit either action without // authenticating. Without the token the public single-quote page // is read-only (no buttons, no nonce in DOM). Lazily generates // the token on first send. The query parameter name is // intentionally short ('qk') and opaque โ€” leaking it via referer // headers is no worse than leaking the secure-link signature. $quote_access_token = \EasyInvoice\Controllers\QuoteController::quoteAccessToken((int) $quote->getId()); if ($quote_access_token !== '' && $quote_url) { $quote_url = add_query_arg('qk', $quote_access_token, $quote_url); } // Get client data for additional fields $client = null; if ($quote->getClientId()) { $client_repository = new \EasyInvoice\Repositories\ClientRepository(); $client = $client_repository->find($quote->getClientId()); } return array_merge([ '{{quote_number}}' => $quote->getNumber(), '{{quote_title}}' => $quote->getTitle(), '{{client_name}}' => $quote->getCustomerName(), '{{client_email}}' => $quote->getCustomerEmail(), '{{client_address}}' => $quote->getCustomerAddress(), '{{client_first_name}}' => $client ? $client->getFirstName() : '', '{{client_last_name}}' => $client ? $client->getLastName() : '', '{{company_name}}' => get_bloginfo('name'), '{{company_email}}' => $this->settings['from_email'], '{{company_phone}}' => get_option('easy_invoice_company_phone', ''), '{{company_address}}' => get_option('easy_invoice_company_address', ''), '{{company_website}}' => get_option('easy_invoice_company_website', ''), '{{total_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTotal()), '{{subtotal}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getSubtotal()), '{{tax_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTaxAmount()), '{{discount_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getDiscountAmount()), '{{expiry_date}}' => date('F j, Y', strtotime($quote->getExpiryDate())), '{{issue_date}}' => date('F j, Y', strtotime($quote->getIssueDate())), '{{quote_url}}' => $quote_url, '{{site_url}}' => get_site_url(), '{{admin_url}}' => admin_url(), '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')), ], $additional_data); } /** * Get payment replacements * * @param Invoice $invoice The invoice * @param array $payment_data Payment data * @return array Replacements */ private function getPaymentReplacements(Invoice $invoice, array $payment_data = []): array { $replacements = $this->getInvoiceReplacements($invoice, $payment_data); // Add payment-specific replacements $replacements['{{payment_amount}}'] = isset($payment_data['amount']) ? $this->formatCurrency($payment_data['amount']) : $this->formatCurrency($invoice->getTotal()); $replacements['{{payment_date}}'] = isset($payment_data['date']) ? $payment_data['date'] : current_time('Y-m-d'); $replacements['{{payment_method}}'] = isset($payment_data['method']) ? $payment_data['method'] : __('Online Payment', 'easy-invoice'); $replacements['{{transaction_id}}'] = isset($payment_data['transaction_id']) ? $payment_data['transaction_id'] : __('N/A', 'easy-invoice'); $replacements['{{acceptance_date}}'] = isset($payment_data['acceptance_date']) ? $payment_data['acceptance_date'] : current_time('Y-m-d'); $replacements['{{response_date}}'] = isset($payment_data['response_date']) ? $payment_data['response_date'] : current_time('Y-m-d'); $replacements['{{decline_reason}}'] = isset($payment_data['decline_reason']) ? $payment_data['decline_reason'] : __('No specific reason provided', 'easy-invoice'); return $replacements; } /** * Process template with replacements * * @param string $template The template * @param array $replacements The replacements * @return string Processed template */ private function processTemplate(string $template, array $replacements): string { return easy_invoice_str_replace(array_keys($replacements), array_values($replacements), $template); } /** * Prepare email headers * * @return array Headers */ private function prepareEmailHeaders(): array { $headers = [ 'Content-Type: text/html; charset=UTF-8', 'From: ' . $this->settings['from_name'] . ' <' . $this->settings['from_email'] . '>', ]; // Add Reply-To if set if (!empty($this->settings['reply_to_email'])) { $reply_to_name = !empty($this->settings['reply_to_name']) ? $this->settings['reply_to_name'] : $this->settings['from_name']; $headers[] = 'Reply-To: ' . $reply_to_name . ' <' . $this->settings['reply_to_email'] . '>'; } return $headers; } /** * Wrap message in HTML template * * @param string $message The message * @return string HTML wrapped message */ private function wrapInHtmlTemplate(string $message): string { $logo_html = ''; if (!empty($this->settings['email_logo'])) { $logo_html = '
' . esc_attr($this->settings['from_name']) . '
'; } $footer_html = ''; if (!empty($this->settings['footer_text'])) { $footer_html = '
' . wpautop($this->settings['footer_text']) . '
'; } return ' ' . esc_html($this->settings['from_name']) . '
' . $logo_html . '
' . wpautop($message) . '
' . $footer_html . '
'; } /** * Register email settings */ public function registerEmailSettings(): void { // Email settings section add_settings_section( 'easy_invoice_email_settings', __('Email Configuration', 'easy-invoice'), [$this, 'emailSettingsSectionCallback'], 'easy_invoice_settings' ); // Register settings register_setting('easy_invoice_settings', 'easy_invoice_email_from_name'); register_setting('easy_invoice_settings', 'easy_invoice_email_from_address'); register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to'); register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to_name'); register_setting('easy_invoice_settings', 'easy_invoice_enable_email_styling'); register_setting('easy_invoice_settings', 'easy_invoice_email_logo'); register_setting('easy_invoice_settings', 'easy_invoice_email_footer_text'); register_setting('easy_invoice_settings', 'easy_invoice_bcc_admin'); register_setting('easy_invoice_settings', 'easy_invoice_admin_email'); // Add settings fields add_settings_field( 'easy_invoice_email_from_name', __('From Name', 'easy-invoice'), [$this, 'textFieldCallback'], 'easy_invoice_settings', 'easy_invoice_email_settings', ['label_for' => 'easy_invoice_email_from_name'] ); add_settings_field( 'easy_invoice_email_from_address', __('From Email Address', 'easy-invoice'), [$this, 'emailFieldCallback'], 'easy_invoice_settings', 'easy_invoice_email_settings', ['label_for' => 'easy_invoice_email_from_address'] ); add_settings_field( 'easy_invoice_email_reply_to', __('Reply-To Email', 'easy-invoice'), [$this, 'emailFieldCallback'], 'easy_invoice_settings', 'easy_invoice_email_settings', ['label_for' => 'easy_invoice_email_reply_to'] ); add_settings_field( 'easy_invoice_enable_email_styling', __('Enable HTML Emails', 'easy-invoice'), [$this, 'checkboxFieldCallback'], 'easy_invoice_settings', 'easy_invoice_email_settings', ['label_for' => 'easy_invoice_enable_email_styling'] ); add_settings_field( 'easy_invoice_bcc_admin', __('BCC Admin on All Emails', 'easy-invoice'), [$this, 'checkboxFieldCallback'], 'easy_invoice_settings', 'easy_invoice_email_settings', ['label_for' => 'easy_invoice_bcc_admin'] ); } /** * Add email settings section * * @param array $sections Settings sections * @return array Modified sections */ public function addEmailSettingsSection(array $sections): array { $sections['email'] = [ 'title' => __('Email Settings', 'easy-invoice'), 'description' => __('Configure email sending options and templates', 'easy-invoice'), 'icon' => 'fas fa-envelope', 'fields' => [ 'easy_invoice_email_from_name' => [ 'label' => __('From Name', 'easy-invoice'), 'type' => 'text', 'default' => get_bloginfo('name'), 'col_span' => 'sm:col-span-3' ], 'easy_invoice_email_from_address' => [ 'label' => __('From Email Address', 'easy-invoice'), 'type' => 'email', 'default' => get_bloginfo('admin_email'), 'col_span' => 'sm:col-span-3' ], 'easy_invoice_email_reply_to' => [ 'label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_enable_email_styling' => [ 'label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_bcc_admin' => [ 'label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_email_logo' => [ 'label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_email_footer_text' => [ 'label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ], ] ]; return $sections; } /** * Email settings section callback */ public function emailSettingsSectionCallback(): void { echo '

' . __('Configure how emails are sent from Easy Invoice.', 'easy-invoice') . '

'; } /** * Text field callback * * @param array $args Field arguments */ public function textFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; } /** * Email field callback * * @param array $args Field arguments */ public function emailFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; } /** * Checkbox field callback * * @param array $args Field arguments */ public function checkboxFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; echo '' . __('Enable this option', 'easy-invoice') . ''; } /** * Log email sent * * @param Invoice $invoice The invoice * @param string $email The email address * @param string $type The email type */ public function logEmailSent($invoice, string $email, string $type): void { $this->log(sprintf('Email sent to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'info'); } /** * Log email failed * * @param Invoice $invoice The invoice * @param string $email The email address * @param string $type The email type */ public function logEmailFailed($invoice, string $email, string $type): void { $this->log(sprintf('Email failed to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'error'); } /** * Get default invoice template * * @return string Template */ private function getDefaultInvoiceTemplate(): string { return '

๐Ÿ“„ Your Invoice is Ready

Dear {{client_name}},

Invoice #{{invoice_number}}
{{total_amount}}
Due Date: {{due_date}}

Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment or visit the link below.

๐Ÿ“‹ Payment Details:
โ€ข Invoice Number: {{invoice_number}}
โ€ข Total Amount: {{total_amount}}
โ€ข Due Date: {{due_date}}
โ€ข Payment Terms: {{payment_terms}}

๐Ÿ”— View Invoice Online:
{{invoice_url}}

Shortcode: [easy_invoice_url number="{{invoice_number}}" text="View Invoice"]

โš ๏ธ Important: Please ensure payment is received by the due date to avoid any late fees or service interruptions.

If you have any questions about this invoice, please do not hesitate to contact us.

Thank you for your business!

Best regards,
{{company_name}}
{{company_email}}

'; } private function getDefaultReminderTemplate(): string { return '

โฐ Payment Reminder

Dear {{client_name}},

Invoice #{{invoice_number}}
{{total_amount}}
Due Date: {{due_date}}

This is a friendly reminder that payment for the above invoice is now due. If you have already made the payment, please disregard this message.

๐Ÿ’ณ Payment Options:
โ€ข Online payment through our secure portal
โ€ข Bank transfer to the details provided
โ€ข Check or money order

๐Ÿ”— View Invoice Online:
{{invoice_url}}

Shortcode: [easy_invoice_url number="{{invoice_number}}" text="View Invoice"]

๐Ÿ“ž Need Help? If you have any questions or need to discuss payment arrangements, please contact us immediately.

Thank you for your prompt attention to this matter.

Best regards,
{{company_name}}
{{company_email}}

'; } private function getDefaultPaymentTemplate(): string { return '

โœ… Payment Received - Thank You!

Dear {{client_name}},

Payment Confirmation
Invoice #{{invoice_number}}
{{payment_amount}}
Payment Date: {{payment_date}}
Payment Method: {{payment_method}}

We have successfully received your payment. Thank you for your prompt payment!

๐Ÿ“Š Payment Details:
โ€ข Invoice Number: {{invoice_number}}
โ€ข Amount Paid: {{payment_amount}}
โ€ข Payment Date: {{payment_date}}
โ€ข Payment Method: {{payment_method}}
โ€ข Transaction ID: {{transaction_id}}

๐ŸŽ‰ Status: PAID
Your payment has been processed and your account is now up to date. We appreciate your business!

If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.

Thank you for choosing our services!

Best regards,
{{company_name}}
{{company_email}}

'; } private function getDefaultQuoteTemplate(): string { return '

๐Ÿ“‹ Your Quote is Ready

Dear {{client_name}},

Quote #{{quote_number}}
{{total_amount}}
Valid Until: {{expiry_date}}

We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.

๐Ÿ“‹ Quote Summary:
โ€ข Quote Number: {{quote_number}}
โ€ข Total Amount: {{total_amount}}
โ€ข Valid Until: {{expiry_date}}
โ€ข Terms: {{payment_terms}}

๐Ÿ”— View Quote Online:
{{quote_url}}

Shortcode: [easy_quote_url number="{{quote_number}}" text="View Quote"]

โฐ Time Sensitive: This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.

If you have any questions or would like to discuss any aspects of this quote, please contact us.

We look forward to working with you!

Best regards,
{{company_name}}
{{company_email}}

'; } private function getDefaultQuoteAcceptedTemplate(): string { return '

๐ŸŽ‰ Quote Accepted - Project Confirmed!

Dear {{client_name}},

Quote #{{quote_number}} - ACCEPTED
{{total_amount}}
Acceptance Date: {{acceptance_date}}

Thank you for accepting our quote! We\'re excited to begin working on your project.

๐Ÿš€ Next Steps:
โ€ข We will create an invoice for the accepted quote
โ€ข You will receive payment instructions
โ€ข Project work will begin as scheduled

๐Ÿ“ž What\'s Next? Our team will be in touch shortly with the next steps and any additional information you may need.

Thank you for choosing our services!

Best regards,
{{company_name}}
{{company_email}}

'; } private function getDefaultQuoteDeclinedTemplate(): string { return '

๐Ÿ“ Quote Response Received

Dear {{client_name}},

Quote #{{quote_number}} - DECLINED
Response Date: {{response_date}}

We have received your response regarding our quote. We understand that this quote may not have met your current needs.

๐Ÿ“‹ Feedback:
โ€ข Reason: {{decline_reason}}
โ€ข Response Date: {{response_date}}

๐Ÿค Future Opportunities: We appreciate you taking the time to review our proposal. If your requirements change in the future, we would be happy to discuss new opportunities.

Thank you for considering our services!

Best regards,
{{company_name}}
{{company_email}}

'; } /** * Refresh settings and templates * Call this method when settings are updated */ public function refreshSettings(): void { $this->loadSettings(); $this->loadTemplates(); } /** * Get email templates * * @return array Templates */ public function getTemplates(): array { return $this->templates; } /** * Get email settings * * @return array Settings */ public function getSettings(): array { return $this->settings; } /** * Test email functionality * * @param string $to_email Email to send test to * @return array Result */ public function testEmail(string $to_email): array { $subject = 'Easy Invoice - Email Configuration Test'; $message = '

๐Ÿงช Email Configuration Test

Hello!

โœ… Test Email Successfully Sent
Date: ' . current_time('Y-m-d H:i:s') . '
To: ' . esc_html($to_email) . '

This is a test email to verify that your Easy Invoice email configuration is working correctly.

โš™๏ธ Email Settings Verified:
โ€ข From Name: ' . esc_html($this->settings['from_name']) . '
โ€ข From Email: ' . esc_html($this->settings['from_email']) . '
โ€ข Reply-To: ' . esc_html($this->settings['reply_to_email'] ?: 'Not set') . '
โ€ข HTML Emails: ' . ($this->settings['enable_html'] === 'yes' ? 'Enabled' : 'Disabled') . '

๐ŸŽ‰ Congratulations! If you received this email, your email configuration is working properly and you can now send invoices, quotes, and payment confirmations to your clients.

Thank you for using Easy Invoice!

Best regards,
' . esc_html($this->settings['from_name']) . '

'; if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } $headers = $this->prepareEmailHeaders(); $sent = $this->sendEmail($to_email, $subject, $message, $headers); if ($sent) { return ['success' => true, 'message' => __('Test email sent successfully', 'easy-invoice')]; } else { return ['success' => false, 'message' => __('Failed to send test email', 'easy-invoice')]; } } /** * Send test template email with custom subject and body * * @param string $to_email Email to send test to * @param string $subject Email subject * @param string $body Email body * @return array Result */ public function sendTestTemplateEmail(string $to_email, string $subject, string $body): array { if ($this->settings['enable_html'] === 'yes') { $body = $this->wrapInHtmlTemplate($body); } $headers = $this->prepareEmailHeaders(); $sent = $this->sendEmail($to_email, $subject, $body, $headers); if ($sent) { return ['success' => true, 'message' => __('Template test email sent successfully', 'easy-invoice')]; } else { return ['success' => false, 'message' => __('Failed to send template test email', 'easy-invoice')]; } } /** * Send payment received email * * @param Invoice $invoice The invoice * @param array $payment_data Payment data * @return array Result array with success status and message */ public function sendPaymentEmail(Invoice $invoice, array $payment_data = []): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get client email $client_email = $invoice->getCustomerEmail(); if (empty($client_email)) { return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')]; } // Get template $template_key = 'invoice_paid'; if (!isset($this->templates[$template_key])) { return ['success' => false, 'message' => __('Payment email template not found', 'easy-invoice')]; } $template = $this->templates[$template_key]; // Check if email is enabled if (!$template['enabled']) { return ['success' => false, 'message' => __('Payment received email is disabled', 'easy-invoice')]; } // Prepare email data $email_data = $this->preparePaymentEmailData($invoice, $template, $payment_data); // Send email $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); if ($sent) { // Log success do_action('easy_invoice_payment_email_sent', $invoice, $client_email, $payment_data); return [ 'success' => true, 'message' => __('Payment email sent successfully', 'easy-invoice'), 'email_data' => $email_data ]; } else { // Log failure do_action('easy_invoice_payment_email_failed', $invoice, $client_email, $payment_data); return ['success' => false, 'message' => __('Failed to send payment email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Payment email sending error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending payment email: ', 'easy-invoice') . $e->getMessage()]; } } /** * Send admin notification when payment is received * * @param Invoice $invoice The invoice * @param array $payment_data Payment data (method, amount, etc.) * @return array Result array with success status and message */ public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get admin email $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); if (empty($admin_email)) { return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; } // Get payment method $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online'; $payment_method_label = $this->getPaymentMethodLabel($payment_method); // Format amount $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice); $amount = $formatter->format($invoice->getTotal()); // Prepare email subject $subject = sprintf( __('New Payment Received - Invoice #%s', 'easy-invoice'), $invoice->getNumber() ); // Prepare email message $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Send email $sent = $this->sendEmail($admin_email, $subject, $message, $headers); if ($sent) { do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data); return [ 'success' => true, 'message' => __('Admin notification sent successfully', 'easy-invoice') ]; } else { do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data); return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Admin payment notification error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; } } /** * Send payment confirmation email to customer * * @param Invoice $invoice The invoice * @param array $payment_data Payment data * @return array Result array with success status and message */ public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get customer email $customer_email = $invoice->getCustomerEmail(); if (empty($customer_email)) { return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; } // Get currency settings $settings_controller = new \EasyInvoice\Controllers\SettingsController(); $settings = $settings_controller->getSettings(); $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); // Format amount $amount = $invoice->getTotal(); $formatted_amount = $currency_symbol . number_format($amount, 2); // Prepare email subject $site_name = get_bloginfo('name'); $subject = sprintf( __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice->getNumber() ); // Prepare email message $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate) // The payment completion hook already sends a dedicated admin notification $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true; if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } // Send email $sent = $this->sendEmail($customer_email, $subject, $message, $headers); if ($sent) { do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data); return [ 'success' => true, 'message' => __('Payment confirmation email sent successfully', 'easy-invoice') ]; } else { do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data); return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()]; } } /** * Send payment rejection email to customer * * @param Invoice $invoice The invoice * @param string $reason Rejection reason * @return array Result array with success status and message */ public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get customer email $customer_email = $invoice->getCustomerEmail(); if (empty($customer_email)) { return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; } // Prepare email subject $site_name = get_bloginfo('name'); $subject = sprintf( __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'), $site_name, $invoice->getNumber() ); // Prepare email message $message = $this->preparePaymentRejectionMessage($invoice, $reason); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } // Send email $sent = $this->sendEmail($customer_email, $subject, $message, $headers); if ($sent) { do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason); return [ 'success' => true, 'message' => __('Payment rejection email sent successfully', 'easy-invoice') ]; } else { do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason); return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Payment rejection email error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()]; } } /** * Prepare admin payment notification message * * @param Invoice $invoice The invoice * @param string $payment_method_label Payment method label * @param string $amount Formatted amount * @param array $payment_data Payment data * @return string Email message */ private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string { $invoice_number = $invoice->getNumber(); $customer_name = $invoice->getCustomerName(); $customer_email = $invoice->getCustomerEmail(); $invoice_id = $invoice->getId(); $message = sprintf( __('A new %s payment has been received for invoice #%s.', 'easy-invoice'), $payment_method_label, $invoice_number ); $message .= "\n\n"; $message .= __('Invoice Details:', 'easy-invoice'); $message .= "\n"; $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount); $message .= "\n"; $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name); $message .= "\n"; $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email); // Add transaction ID if available if (!empty($payment_data['transaction_id'])) { $message .= "\n"; $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']); } $message .= "\n\n"; $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice'); $message .= "\n"; $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id); $message .= "\n\n"; $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice'); return $message; } /** * Prepare payment confirmation message * * @param Invoice $invoice The invoice * @param string $formatted_amount Formatted amount * @return string Email message */ private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string { $customer_name = $invoice->getCustomerName(); $invoice_number = $invoice->getNumber(); $site_name = get_bloginfo('name'); $company_name = get_option('easy_invoice_company_name', $site_name); $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); $message .= "\n\n"; $message .= sprintf( __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'), $formatted_amount, $invoice_number ); $message .= "\n\n"; $message .= __('Thank you for your business.', 'easy-invoice'); $message .= "\n\n"; $message .= __('Regards,', 'easy-invoice'); $message .= "\n"; $message .= $company_name; return $message; } /** * Prepare payment rejection message * * @param Invoice $invoice The invoice * @param string $reason Rejection reason * @return string Email message */ private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string { $customer_name = $invoice->getCustomerName(); $invoice_number = $invoice->getNumber(); $site_name = get_bloginfo('name'); $company_name = get_option('easy_invoice_company_name', $site_name); $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); $message .= "\n\n"; $message .= sprintf( __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'), $invoice_number ); if (!empty($reason)) { $message .= "\n\n"; $message .= __('Reason:', 'easy-invoice'); $message .= "\n"; $message .= $reason; } $message .= "\n\n"; $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice'); $message .= "\n\n"; $message .= __('Regards,', 'easy-invoice'); $message .= "\n"; $message .= $company_name; return $message; } /** * Send admin notification for quote acceptance/decline * * @param Quote $quote The quote * @param string $action Action type ('accepted' or 'declined') * @return array Result array with success status and message */ public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array { try { // Validate quote if (!$quote || !$quote->getId()) { return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')]; } // Get admin email $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); if (empty($admin_email)) { return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; } // Prepare email subject $subject = sprintf( __('Quote %s has been %s', 'easy-invoice'), $quote->getNumber(), $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice') ); // Prepare email message $message = $this->prepareAdminQuoteNotificationMessage($quote, $action); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Send email $sent = $this->sendEmail($admin_email, $subject, $message, $headers); if ($sent) { do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action); return [ 'success' => true, 'message' => __('Admin notification sent successfully', 'easy-invoice') ]; } else { do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action); return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Admin quote notification error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; } } /** * Prepare admin quote notification message * * @param Quote $quote The quote * @param string $action Action type ('accepted' or 'declined') * @return string Email message */ private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string { $site_name = get_bloginfo('name'); $quote_number = $quote->getNumber(); $customer_name = $quote->getCustomerName(); // Format amount $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote); $formatted_amount = $formatter->format($quote->getTotal()); $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice'); $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice'); $message = __('Hello,', 'easy-invoice'); $message .= "\n\n"; $message .= sprintf( __('The quote %s for %s has been %s by the client.', 'easy-invoice'), $quote_number, $customer_name, $action_label ); $message .= "\n\n"; $message .= __('Quote Details:', 'easy-invoice'); $message .= "\n"; $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number); $message .= "\n"; $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name); $message .= "\n"; $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount); $message .= "\n"; $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); $message .= "\n\n"; $message .= __('You can view the quote at:', 'easy-invoice'); $message .= "\n"; $message .= get_permalink($quote->getId()); $message .= "\n\n"; $message .= __('Best regards,', 'easy-invoice'); $message .= "\n"; $message .= $site_name; return $message; } /** * Handle payment completed hook * Sends admin notification and customer confirmation when payment is completed * * @param int $invoice_id Invoice ID * @param \EasyInvoice\Models\Invoice $invoice Invoice object * @param array $payment_data Payment data (method, gateway, transaction_id, amount) * @return void */ public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void { if (!$invoice || !$invoice->getId()) { return; } // Send admin notification $this->sendAdminPaymentNotification($invoice, $payment_data); // Send customer confirmation email using proper template system // Check if payment email is enabled first if (isset($this->templates['invoice_paid']) && $this->templates['invoice_paid']['enabled']) { $this->sendInvoiceEmail($invoice, 'paid', array_merge($payment_data, ['skip_bcc' => true])); } } /** * Get payment method label * * @param string $method Payment method * @return string Payment method label */ private function getPaymentMethodLabel(string $method): string { $labels = [ 'bank' => __('Bank Transfer', 'easy-invoice'), 'cheque' => __('Cheque', 'easy-invoice'), 'paypal' => __('PayPal', 'easy-invoice'), 'stripe' => __('Stripe', 'easy-invoice'), 'square' => __('Square', 'easy-invoice'), 'mollie' => __('Mollie', 'easy-invoice'), 'authorizenet' => __('Authorize.Net', 'easy-invoice'), 'manual' => __('Manual Payment', 'easy-invoice'), 'online' => __('Online Payment', 'easy-invoice'), ]; return $labels[$method] ?? ucfirst($method); } }