PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.4
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.4
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Services / EmailManager.php

EmailManager.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.4, at includes/Services/EmailManager.php

1,902 lines 76.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email Manager Service
4 *
5 * Handles all email functionality for Easy Invoice
6 *
7 * @package EasyInvoice
8 * @subpackage Services
9 * @since 1.0.0
10 */
11
12 namespace EasyInvoice\Services;
13
14 use EasyInvoice\Models\Invoice;
15 use EasyInvoice\Models\Quote;
16 use EasyInvoice\Models\Client;
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * EmailManager Class
24 *
25 * Centralized email management for Easy Invoice
26 */
27 class EmailManager extends BaseService {
28
29 /**
30 * Singleton instance
31 *
32 * @var EmailManager|null
33 */
34 private static $instance = null;
35
36 /**
37 * Service name
38 *
39 * @var string
40 */
41 protected $service_name = 'email_manager';
42
43 /**
44 * Email templates
45 *
46 * @var array
47 */
48 private $templates = [];
49
50 /**
51 * Email settings
52 *
53 * @var array
54 */
55 private $settings = [];
56
57 /**
58 * Get singleton instance
59 *
60 * @return EmailManager
61 */
62 public static function getInstance(): EmailManager {
63 if (self::$instance === null) {
64 self::$instance = new self();
65 }
66 return self::$instance;
67 }
68
69 /**
70 * Constructor
71 */
72 public function __construct() {
73 parent::__construct();
74 $this->loadSettings();
75 $this->loadTemplates();
76 $this->initHooks();
77 }
78
79 /**
80 * Initialize hooks
81 */
82 private function initHooks(): void {
83 // Invoice/quote send-email AJAX is handled exclusively by EasyInvoice\Admin\EasyInvoiceAjax
84 // (published-document checks, single handler) to avoid duplicate nopriv callbacks.
85
86 // Add email settings to admin
87 add_action('admin_init', [$this, 'registerEmailSettings']);
88 add_filter('easy_invoice_settings_sections', [$this, 'addEmailSettingsSection']);
89
90 // Refresh settings when they're updated
91 add_action('update_option_easy_invoice_email_from_name', [$this, 'refreshSettings']);
92 add_action('update_option_easy_invoice_email_from_address', [$this, 'refreshSettings']);
93 add_action('update_option_easy_invoice_email_reply_to', [$this, 'refreshSettings']);
94 add_action('update_option_easy_invoice_email_reply_to_name', [$this, 'refreshSettings']);
95 add_action('update_option_easy_invoice_enable_email_styling', [$this, 'refreshSettings']);
96 add_action('update_option_easy_invoice_email_logo', [$this, 'refreshSettings']);
97 add_action('update_option_easy_invoice_email_footer_text', [$this, 'refreshSettings']);
98 add_action('update_option_easy_invoice_bcc_admin', [$this, 'refreshSettings']);
99 add_action('update_option_easy_invoice_admin_email', [$this, 'refreshSettings']);
100 add_action('update_option_easy_invoice_email_subject', [$this, 'refreshSettings']);
101 add_action('update_option_easy_invoice_email_body', [$this, 'refreshSettings']);
102 add_action('update_option_easy_invoice_quote_subject', [$this, 'refreshSettings']);
103 add_action('update_option_easy_invoice_quote_body', [$this, 'refreshSettings']);
104
105 // Add email logs
106 add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3);
107 add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3);
108
109 // Listen for payment completion to send admin notifications
110 add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3);
111 }
112
113 /**
114 * Load email settings
115 */
116 private function loadSettings(): void {
117 $this->settings = [
118 'from_name' => get_option('easy_invoice_email_from_name', get_bloginfo('name')),
119 'from_email' => get_option('easy_invoice_email_from_address', get_bloginfo('admin_email')),
120 'reply_to_email' => get_option('easy_invoice_email_reply_to', ''),
121 'reply_to_name' => get_option('easy_invoice_email_reply_to_name', ''),
122 'enable_html' => get_option('easy_invoice_enable_email_styling', 'yes'),
123 'email_logo' => get_option('easy_invoice_email_logo', ''),
124 'footer_text' => get_option('easy_invoice_email_footer_text', ''),
125 'bcc_admin' => get_option('easy_invoice_bcc_admin', 'no'),
126 'admin_email' => get_option('easy_invoice_admin_email', get_option('admin_email')),
127 ];
128 }
129
130 /**
131 * Load email templates
132 */
133 private function loadTemplates(): void {
134 $this->templates = [
135 'invoice_new' => [
136 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes',
137 'subject' => get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')),
138 'body' => get_option('easy_invoice_invoice_email_body', $this->getDefaultInvoiceTemplate()),
139 'type' => 'invoice'
140 ],
141 'invoice_reminder' => [
142 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes',
143 'subject' => get_option('easy_invoice_reminder_subject', 'Payment Reminder - Invoice #{{invoice_number}}'),
144 'body' => get_option('easy_invoice_reminder_body', $this->getDefaultReminderTemplate()),
145 'type' => 'invoice'
146 ],
147 'invoice_paid' => [
148 'enabled' => get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes',
149 'subject' => get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')),
150 'body' => get_option('easy_invoice_payment_email_body', $this->getDefaultPaymentTemplate()),
151 'type' => 'invoice'
152 ],
153 'quote_new' => [
154 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
155 'subject' => get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')),
156 'body' => get_option('easy_invoice_quote_email_body', $this->getDefaultQuoteTemplate()),
157 'type' => 'quote'
158 ],
159 'quote_accepted' => [
160 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
161 'subject' => get_option('easy_invoice_quote_accepted_subject', 'Quote Accepted - #{{quote_number}}'),
162 'body' => get_option('easy_invoice_quote_accepted_body', $this->getDefaultQuoteAcceptedTemplate()),
163 'type' => 'quote'
164 ],
165 'quote_declined' => [
166 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
167 'subject' => get_option('easy_invoice_quote_declined_subject', 'Quote Declined - #{{quote_number}}'),
168 'body' => get_option('easy_invoice_quote_declined_body', $this->getDefaultQuoteDeclinedTemplate()),
169 'type' => 'quote'
170 ]
171 ];
172 }
173
174 /**
175 * Send invoice email
176 *
177 * @param Invoice $invoice The invoice
178 * @param string $template_type Template type (new, reminder, paid)
179 * @param array $additional_data Additional data for template
180 * @return array Result array with success status and message
181 */
182 public function sendInvoiceEmail(Invoice $invoice, string $template_type = 'new', array $additional_data = []): array {
183 try {
184 // Validate invoice
185 if (!$invoice || !$invoice->getId()) {
186 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
187 }
188
189 // Get client email
190 $client_email = $invoice->getCustomerEmail();
191 if (empty($client_email)) {
192 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
193 }
194
195 // Get template
196 $template_key = 'invoice_' . $template_type;
197 if (!isset($this->templates[$template_key])) {
198 return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')];
199 }
200
201 $template = $this->templates[$template_key];
202
203 // Check if email is enabled
204 if (!$template['enabled']) {
205 return ['success' => false, 'message' => __('Invoice available email is disabled', 'easy-invoice')];
206 }
207
208 // Prepare email data
209 $email_data = $this->prepareInvoiceEmailData($invoice, $template, $additional_data);
210
211 // Send email
212 $sent = $this->sendEmail(
213 $email_data['to'],
214 $email_data['subject'],
215 $email_data['message'],
216 $email_data['headers']
217 );
218
219 if ($sent) {
220 // Log success
221 do_action('easy_invoice_email_sent', $invoice, $client_email, $template_type);
222
223 return [
224 'success' => true,
225 'message' => __('Email sent successfully', 'easy-invoice'),
226 'email_data' => $email_data
227 ];
228 } else {
229 // Log failure
230 do_action('easy_invoice_email_failed', $invoice, $client_email, $template_type);
231
232 return ['success' => false, 'message' => __('Failed to send email', 'easy-invoice')];
233 }
234
235 } catch (\Exception $e) {
236 $this->log('Email sending error: ' . $e->getMessage(), 'error');
237 return ['success' => false, 'message' => __('Error sending email: ', 'easy-invoice') . $e->getMessage()];
238 }
239 }
240
241 /**
242 * Send quote email
243 *
244 * @param Quote $quote The quote
245 * @param string $template_type Template type (new, accepted, declined)
246 * @param array $additional_data Additional data for template
247 * @return array Result array with success status and message
248 */
249 public function sendQuoteEmail(Quote $quote, string $template_type = 'new', array $additional_data = []): array {
250 try {
251 // Validate quote
252 if (!$quote || !$quote->getId()) {
253 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
254 }
255
256 // Get client email
257 $client_email = $quote->getCustomerEmail();
258 if (empty($client_email)) {
259 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
260 }
261
262 // Get template
263 $template_key = 'quote_' . $template_type;
264 if (!isset($this->templates[$template_key])) {
265 return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')];
266 }
267
268 $template = $this->templates[$template_key];
269
270 // Check if email is enabled
271 if (!$template['enabled']) {
272 return ['success' => false, 'message' => __('Quote available email is disabled', 'easy-invoice')];
273 }
274
275 // Prepare email data
276 $email_data = $this->prepareQuoteEmailData($quote, $template, $additional_data);
277
278 // Send email
279 $sent = $this->sendEmail(
280 $email_data['to'],
281 $email_data['subject'],
282 $email_data['message'],
283 $email_data['headers']
284 );
285
286 if ($sent) {
287 // Log success
288 do_action('easy_invoice_quote_email_sent', $quote, $client_email, $template_type);
289
290 return [
291 'success' => true,
292 'message' => __('Quote email sent successfully', 'easy-invoice'),
293 'email_data' => $email_data
294 ];
295 } else {
296 // Log failure
297 do_action('easy_invoice_quote_email_failed', $quote, $client_email, $template_type);
298
299 return ['success' => false, 'message' => __('Failed to send quote email', 'easy-invoice')];
300 }
301
302 } catch (\Exception $e) {
303 $this->log('Quote email sending error: ' . $e->getMessage(), 'error');
304 return ['success' => false, 'message' => __('Error sending quote email: ', 'easy-invoice') . $e->getMessage()];
305 }
306 }
307
308 /**
309 * Prepare invoice email data
310 *
311 * @param Invoice $invoice The invoice
312 * @param array $template The email template
313 * @param array $additional_data Additional data
314 * @return array Email data
315 */
316 private function prepareInvoiceEmailData(Invoice $invoice, array $template, array $additional_data = []): array {
317 // Get replacements
318 $replacements = $this->getInvoiceReplacements($invoice, $additional_data);
319
320 // Process template
321 $subject = $this->processTemplate($template['subject'], $replacements);
322 $message = $this->processTemplate($template['body'], $replacements);
323 $message = do_shortcode($message); // Render shortcodes like [easy_invoice_url ...]
324
325 // Add HTML wrapper if enabled
326 if ($this->settings['enable_html'] === 'yes') {
327 $message = $this->wrapInHtmlTemplate($message);
328 }
329
330 // Prepare headers
331 $headers = $this->prepareEmailHeaders();
332
333 // Add BCC to admin if enabled
334 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
335 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
336 }
337
338 return [
339 'to' => $invoice->getCustomerEmail(),
340 'subject' => $subject,
341 'message' => $message,
342 'headers' => $headers
343 ];
344 }
345
346 /**
347 * Prepare quote email data
348 *
349 * @param Quote $quote The quote
350 * @param array $template The email template
351 * @param array $additional_data Additional data
352 * @return array Email data
353 */
354 private function prepareQuoteEmailData(Quote $quote, array $template, array $additional_data = []): array {
355 // Get replacements
356 $replacements = $this->getQuoteReplacements($quote, $additional_data);
357
358 // Process template
359 $subject = $this->processTemplate($template['subject'], $replacements);
360 $message = $this->processTemplate($template['body'], $replacements);
361
362 // Add HTML wrapper if enabled
363 if ($this->settings['enable_html'] === 'yes') {
364 $message = $this->wrapInHtmlTemplate($message);
365 }
366
367 // Prepare headers
368 $headers = $this->prepareEmailHeaders();
369
370 // Add BCC to admin if enabled
371 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
372 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
373 }
374
375 return [
376 'to' => $quote->getCustomerEmail(),
377 'subject' => $subject,
378 'message' => $message,
379 'headers' => $headers
380 ];
381 }
382
383 /**
384 * Prepare payment email data
385 *
386 * @param Invoice $invoice The invoice
387 * @param array $template The email template
388 * @param array $payment_data Payment data
389 * @return array Email data
390 */
391 private function preparePaymentEmailData(Invoice $invoice, array $template, array $payment_data = []): array {
392 // Get replacements
393 $replacements = $this->getPaymentReplacements($invoice, $payment_data);
394
395 // Process template
396 $subject = $this->processTemplate($template['subject'], $replacements);
397 $message = $this->processTemplate($template['body'], $replacements);
398
399 // Add HTML wrapper if enabled
400 if ($this->settings['enable_html'] === 'yes') {
401 $message = $this->wrapInHtmlTemplate($message);
402 }
403
404 // Prepare headers
405 $headers = $this->prepareEmailHeaders();
406
407 // Add BCC to admin if enabled
408 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
409 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
410 }
411
412 return [
413 'to' => $invoice->getCustomerEmail(),
414 'subject' => $subject,
415 'message' => $message,
416 'headers' => $headers
417 ];
418 }
419
420 /**
421 * Get invoice replacements
422 *
423 * @param Invoice $invoice The invoice
424 * @param array $additional_data Additional data
425 * @return array Replacements
426 */
427 private function getInvoiceReplacements(Invoice $invoice, array $additional_data = []): array {
428 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
429
430 // Secure link support
431 $invoice_url = get_permalink($invoice->getId());
432 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
433 if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
434 $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId());
435 if ($secure_url) {
436 $invoice_url = $secure_url;
437 }
438 }
439
440 // Get client data for additional fields
441 $client = null;
442 if ($invoice->getClientId()) {
443 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
444 $client = $client_repository->find($invoice->getClientId());
445 }
446
447 return array_merge([
448 '{{invoice_number}}' => $invoice->getNumber(),
449 '{{invoice_title}}' => $invoice->getTitle(),
450 '{{client_name}}' => $invoice->getCustomerName(),
451 '{{client_email}}' => $invoice->getCustomerEmail(),
452 '{{client_address}}' => $invoice->getCustomerAddress(),
453 '{{client_first_name}}' => $client ? $client->getFirstName() : '',
454 '{{client_last_name}}' => $client ? $client->getLastName() : '',
455 '{{company_name}}' => get_bloginfo('name'),
456 '{{company_email}}' => $this->settings['from_email'],
457 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
458 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
459 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
460 '{{total_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTotal()),
461 '{{subtotal}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getSubtotal()),
462 '{{tax_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTaxAmount()),
463 '{{discount_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getDiscountAmount()),
464 '{{due_date}}' => date('F j, Y', strtotime($invoice->getDueDate())),
465 '{{issue_date}}' => date('F j, Y', strtotime($invoice->getIssueDate())),
466 '{{invoice_url}}' => $invoice_url, // Use correct (possibly secure) link
467 '{{payment_url}}' => add_query_arg('payment', '1', get_permalink($invoice->getId())),
468 '{{site_url}}' => get_site_url(),
469 '{{admin_url}}' => admin_url(),
470 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
471 ], $additional_data);
472 }
473
474 /**
475 * Get quote replacements
476 *
477 * @param Quote $quote The quote
478 * @param array $additional_data Additional data
479 * @return array Replacements
480 */
481 private function getQuoteReplacements(Quote $quote, array $additional_data = []): array {
482 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
483
484 $quote_url = get_permalink($quote->getId());
485 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
486 if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
487 $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId());
488 if ($secure_url) {
489 $quote_url = $secure_url;
490 }
491 }
492
493 // SECURITY (CVE-2026-9021): attach the per-quote access token so
494 // the emailed recipient lands on a page that renders the
495 // Accept/Decline UI and can submit either action without
496 // authenticating. Without the token the public single-quote page
497 // is read-only (no buttons, no nonce in DOM). Lazily generates
498 // the token on first send. The query parameter name is
499 // intentionally short ('qk') and opaque — leaking it via referer
500 // headers is no worse than leaking the secure-link signature.
501 $quote_access_token = \EasyInvoice\Controllers\QuoteController::quoteAccessToken((int) $quote->getId());
502 if ($quote_access_token !== '' && $quote_url) {
503 $quote_url = add_query_arg('qk', $quote_access_token, $quote_url);
504 }
505
506 // Get client data for additional fields
507 $client = null;
508 if ($quote->getClientId()) {
509 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
510 $client = $client_repository->find($quote->getClientId());
511 }
512
513 return array_merge([
514 '{{quote_number}}' => $quote->getNumber(),
515 '{{quote_title}}' => $quote->getTitle(),
516 '{{client_name}}' => $quote->getCustomerName(),
517 '{{client_email}}' => $quote->getCustomerEmail(),
518 '{{client_address}}' => $quote->getCustomerAddress(),
519 '{{client_first_name}}' => $client ? $client->getFirstName() : '',
520 '{{client_last_name}}' => $client ? $client->getLastName() : '',
521 '{{company_name}}' => get_bloginfo('name'),
522 '{{company_email}}' => $this->settings['from_email'],
523 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
524 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
525 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
526 '{{total_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTotal()),
527 '{{subtotal}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getSubtotal()),
528 '{{tax_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTaxAmount()),
529 '{{discount_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getDiscountAmount()),
530 '{{expiry_date}}' => date('F j, Y', strtotime($quote->getExpiryDate())),
531 '{{issue_date}}' => date('F j, Y', strtotime($quote->getIssueDate())),
532 '{{quote_url}}' => $quote_url,
533 '{{site_url}}' => get_site_url(),
534 '{{admin_url}}' => admin_url(),
535 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
536 ], $additional_data);
537 }
538
539 /**
540 * Get payment replacements
541 *
542 * @param Invoice $invoice The invoice
543 * @param array $payment_data Payment data
544 * @return array Replacements
545 */
546 private function getPaymentReplacements(Invoice $invoice, array $payment_data = []): array {
547 $replacements = $this->getInvoiceReplacements($invoice, $payment_data);
548
549 // Add payment-specific replacements
550 $replacements['{{payment_amount}}'] = isset($payment_data['amount']) ? $this->formatCurrency($payment_data['amount']) : $this->formatCurrency($invoice->getTotal());
551 $replacements['{{payment_date}}'] = isset($payment_data['date']) ? $payment_data['date'] : current_time('Y-m-d');
552 $replacements['{{payment_method}}'] = isset($payment_data['method']) ? $payment_data['method'] : __('Online Payment', 'easy-invoice');
553 $replacements['{{transaction_id}}'] = isset($payment_data['transaction_id']) ? $payment_data['transaction_id'] : __('N/A', 'easy-invoice');
554 $replacements['{{acceptance_date}}'] = isset($payment_data['acceptance_date']) ? $payment_data['acceptance_date'] : current_time('Y-m-d');
555 $replacements['{{response_date}}'] = isset($payment_data['response_date']) ? $payment_data['response_date'] : current_time('Y-m-d');
556 $replacements['{{decline_reason}}'] = isset($payment_data['decline_reason']) ? $payment_data['decline_reason'] : __('No specific reason provided', 'easy-invoice');
557
558 return $replacements;
559 }
560
561 /**
562 * Process template with replacements
563 *
564 * @param string $template The template
565 * @param array $replacements The replacements
566 * @return string Processed template
567 */
568 private function processTemplate(string $template, array $replacements): string {
569 return easy_invoice_str_replace(array_keys($replacements), array_values($replacements), $template);
570 }
571
572 /**
573 * Prepare email headers
574 *
575 * @return array Headers
576 */
577 private function prepareEmailHeaders(): array {
578 $headers = [
579 'Content-Type: text/html; charset=UTF-8',
580 'From: ' . $this->settings['from_name'] . ' <' . $this->settings['from_email'] . '>',
581 ];
582
583 // Add Reply-To if set
584 if (!empty($this->settings['reply_to_email'])) {
585 $reply_to_name = !empty($this->settings['reply_to_name']) ? $this->settings['reply_to_name'] : $this->settings['from_name'];
586 $headers[] = 'Reply-To: ' . $reply_to_name . ' <' . $this->settings['reply_to_email'] . '>';
587 }
588
589 return $headers;
590 }
591
592 /**
593 * Wrap message in HTML template
594 *
595 * @param string $message The message
596 * @return string HTML wrapped message
597 */
598 private function wrapInHtmlTemplate(string $message): string {
599 $logo_html = '';
600 if (!empty($this->settings['email_logo'])) {
601 $logo_html = '<div style="text-align: center; margin-bottom: 40px;"><img src="' . esc_url($this->settings['email_logo']) . '" alt="' . esc_attr($this->settings['from_name']) . '" style="max-width: 200px; height: auto; border-radius: 8px;"></div>';
602 }
603
604 $footer_html = '';
605 if (!empty($this->settings['footer_text'])) {
606 $footer_html = '<div style="margin-top: 50px; padding-top: 25px; border-top: 2px solid #f3f4f6; font-size: 14px; color: #6b7280; text-align: center;">' . wpautop($this->settings['footer_text']) . '</div>';
607 }
608
609 return '
610 <!DOCTYPE html>
611 <html>
612 <head>
613 <meta charset="UTF-8">
614 <meta name="viewport" content="width=device-width, initial-scale=1.0">
615 <title>' . esc_html($this->settings['from_name']) . '</title>
616 <style>
617 body {
618 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
619 line-height: 1.6;
620 color: #374151;
621 margin: 0;
622 padding: 0;
623 background-color: #f9fafb;
624 }
625 .email-container {
626 max-width: 600px;
627 margin: 0 auto;
628 background-color: #ffffff;
629 border-radius: 12px;
630 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
631 overflow: hidden;
632 }
633 .email-header {
634 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
635 padding: 50px 30px;
636 text-align: center;
637 position: relative;
638 }
639 .email-header::before {
640 content: "";
641 position: absolute;
642 top: 0;
643 left: 0;
644 right: 0;
645 bottom: 0;
646 background: url("data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"%3E%3Cg fill="none" fill-rule="evenodd"%3E%3Cg fill="%23ffffff" fill-opacity="0.1"%3E%3Ccircle cx="30" cy="30" r="2"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
647 opacity: 0.3;
648 }
649 .email-header h1 {
650 color: #ffffff;
651 margin: 0;
652 font-size: 28px;
653 font-weight: 700;
654 position: relative;
655 z-index: 1;
656 }
657 .email-content {
658 padding: 50px 40px;
659 background: #ffffff;
660 }
661 .email-content p {
662 margin: 0 0 20px 0;
663 color: #374151;
664 line-height: 1.7;
665 }
666 .email-content h2 {
667 color: #1f2937;
668 font-size: 28px;
669 font-weight: 700;
670 margin: 0 0 30px 0;
671 text-align: center;
672 }
673 .email-content h3 {
674 color: #374151;
675 font-size: 20px;
676 font-weight: 600;
677 margin: 0 0 16px 0;
678 }
679 .email-footer {
680 background-color: #f9fafb;
681 padding: 40px 30px;
682 text-align: center;
683 border-top: 1px solid #e5e7eb;
684 }
685 .email-footer p {
686 margin: 0;
687 color: #6b7280;
688 font-size: 14px;
689 }
690 .highlight-box {
691 background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
692 border-left: 4px solid #3b82f6;
693 padding: 30px;
694 margin: 30px 0;
695 border-radius: 0 12px 12px 0;
696 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
697 }
698 .highlight-box p {
699 margin: 0;
700 font-size: 16px;
701 line-height: 1.6;
702 }
703 .highlight-box strong {
704 color: #1f2937;
705 font-weight: 600;
706 }
707 .info-box {
708 background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
709 border: 1px solid #93c5fd;
710 border-radius: 12px;
711 padding: 25px;
712 margin: 30px 0;
713 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
714 }
715 .info-box p {
716 margin: 0;
717 color: #1e40af;
718 font-size: 15px;
719 line-height: 1.7;
720 }
721 .info-box strong {
722 color: #1e3a8a;
723 font-weight: 600;
724 }
725 .success-box {
726 background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
727 border: 1px solid #6ee7b7;
728 border-radius: 12px;
729 padding: 25px;
730 margin: 30px 0;
731 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
732 }
733 .success-box p {
734 margin: 0;
735 color: #065f46;
736 font-size: 15px;
737 line-height: 1.7;
738 }
739 .success-box strong {
740 color: #047857;
741 font-weight: 600;
742 }
743 .warning-box {
744 background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
745 border: 1px solid #f59e0b;
746 border-radius: 12px;
747 padding: 25px;
748 margin: 30px 0;
749 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
750 }
751 .warning-box p {
752 margin: 0;
753 color: #92400e;
754 font-size: 15px;
755 line-height: 1.7;
756 }
757 .warning-box strong {
758 color: #78350f;
759 font-weight: 600;
760 }
761 .button {
762 display: inline-block;
763 background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
764 color: #ffffff;
765 padding: 14px 28px;
766 text-decoration: none;
767 border-radius: 8px;
768 font-weight: 600;
769 margin: 20px 0;
770 box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
771 transition: all 0.2s ease;
772 }
773 .button:hover {
774 background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
775 transform: translateY(-1px);
776 box-shadow: 0 6px 8px -1px rgba(59, 130, 246, 0.4);
777 }
778 .divider {
779 height: 1px;
780 background: linear-gradient(90deg, transparent 0%, #e5e7eb 50%, transparent 100%);
781 margin: 40px 0;
782 }
783 .amount-highlight {
784 font-size: 28px;
785 font-weight: 700;
786 color: #059669;
787 text-align: center;
788 margin: 25px 0;
789 display: block;
790 }
791 .status-badge {
792 display: inline-block;
793 padding: 6px 12px;
794 border-radius: 20px;
795 font-size: 12px;
796 font-weight: 600;
797 text-transform: uppercase;
798 letter-spacing: 0.5px;
799 }
800 .status-paid {
801 background: #d1fae5;
802 color: #065f46;
803 }
804 .status-pending {
805 background: #fef3c7;
806 color: #92400e;
807 }
808 .status-overdue {
809 background: #fee2e2;
810 color: #991b1b;
811 }
812 @media only screen and (max-width: 600px) {
813 .email-content { padding: 25px 20px; }
814 .email-header { padding: 35px 20px; }
815 .email-header h1 { font-size: 24px; }
816 .email-content h2 { font-size: 22px; }
817 .highlight-box, .info-box, .success-box, .warning-box { padding: 20px; }
818 .amount-highlight { font-size: 24px; }
819 }
820 </style>
821 </head>
822 <body>
823 <div class="email-container">
824 ' . $logo_html . '
825 <div class="email-content">
826 ' . wpautop($message) . '
827 </div>
828 ' . $footer_html . '
829 </div>
830 </body>
831 </html>';
832 }
833
834 /**
835 * Register email settings
836 */
837 public function registerEmailSettings(): void {
838 // Email settings section
839 add_settings_section(
840 'easy_invoice_email_settings',
841 __('Email Configuration', 'easy-invoice'),
842 [$this, 'emailSettingsSectionCallback'],
843 'easy_invoice_settings'
844 );
845
846 // Register settings
847 register_setting('easy_invoice_settings', 'easy_invoice_email_from_name');
848 register_setting('easy_invoice_settings', 'easy_invoice_email_from_address');
849 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to');
850 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to_name');
851 register_setting('easy_invoice_settings', 'easy_invoice_enable_email_styling');
852 register_setting('easy_invoice_settings', 'easy_invoice_email_logo');
853 register_setting('easy_invoice_settings', 'easy_invoice_email_footer_text');
854 register_setting('easy_invoice_settings', 'easy_invoice_bcc_admin');
855 register_setting('easy_invoice_settings', 'easy_invoice_admin_email');
856
857 // Add settings fields
858 add_settings_field(
859 'easy_invoice_email_from_name',
860 __('From Name', 'easy-invoice'),
861 [$this, 'textFieldCallback'],
862 'easy_invoice_settings',
863 'easy_invoice_email_settings',
864 ['label_for' => 'easy_invoice_email_from_name']
865 );
866
867 add_settings_field(
868 'easy_invoice_email_from_address',
869 __('From Email Address', 'easy-invoice'),
870 [$this, 'emailFieldCallback'],
871 'easy_invoice_settings',
872 'easy_invoice_email_settings',
873 ['label_for' => 'easy_invoice_email_from_address']
874 );
875
876 add_settings_field(
877 'easy_invoice_email_reply_to',
878 __('Reply-To Email', 'easy-invoice'),
879 [$this, 'emailFieldCallback'],
880 'easy_invoice_settings',
881 'easy_invoice_email_settings',
882 ['label_for' => 'easy_invoice_email_reply_to']
883 );
884
885 add_settings_field(
886 'easy_invoice_enable_email_styling',
887 __('Enable HTML Emails', 'easy-invoice'),
888 [$this, 'checkboxFieldCallback'],
889 'easy_invoice_settings',
890 'easy_invoice_email_settings',
891 ['label_for' => 'easy_invoice_enable_email_styling']
892 );
893
894 add_settings_field(
895 'easy_invoice_bcc_admin',
896 __('BCC Admin on All Emails', 'easy-invoice'),
897 [$this, 'checkboxFieldCallback'],
898 'easy_invoice_settings',
899 'easy_invoice_email_settings',
900 ['label_for' => 'easy_invoice_bcc_admin']
901 );
902 }
903
904 /**
905 * Add email settings section
906 *
907 * @param array $sections Settings sections
908 * @return array Modified sections
909 */
910 public function addEmailSettingsSection(array $sections): array {
911 $sections['email'] = [
912 'title' => __('Email Settings', 'easy-invoice'),
913 'description' => __('Configure email sending options and templates', 'easy-invoice'),
914 'icon' => 'fas fa-envelope',
915 'fields' => [
916 'easy_invoice_email_from_name' => [
917 'label' => __('From Name', 'easy-invoice'),
918 'type' => 'text',
919 'default' => get_bloginfo('name'),
920 'col_span' => 'sm:col-span-3'
921 ],
922 'easy_invoice_email_from_address' => [
923 'label' => __('From Email Address', 'easy-invoice'),
924 'type' => 'email',
925 'default' => get_bloginfo('admin_email'),
926 'col_span' => 'sm:col-span-3'
927 ],
928 'easy_invoice_email_reply_to' => [
929 'label' => __('Reply-To Email', 'easy-invoice'),
930 'type' => 'email',
931 'default' => '',
932 'col_span' => 'sm:col-span-3'
933 ],
934 'easy_invoice_enable_email_styling' => [
935 'label' => __('Enable HTML Emails', 'easy-invoice'),
936 'type' => 'checkbox',
937 'default' => 'yes',
938 'col_span' => 'sm:col-span-3'
939 ],
940 'easy_invoice_bcc_admin' => [
941 'label' => __('BCC Admin on All Emails', 'easy-invoice'),
942 'type' => 'checkbox',
943 'default' => 'no',
944 'col_span' => 'sm:col-span-3'
945 ],
946 'easy_invoice_email_logo' => [
947 'label' => __('Email Logo URL', 'easy-invoice'),
948 'type' => 'url',
949 'default' => '',
950 'col_span' => 'sm:col-span-6'
951 ],
952 'easy_invoice_email_footer_text' => [
953 'label' => __('Email Footer Text', 'easy-invoice'),
954 'type' => 'textarea',
955 'default' => '',
956 'col_span' => 'sm:col-span-6'
957 ],
958 ]
959 ];
960
961 return $sections;
962 }
963
964 /**
965 * Email settings section callback
966 */
967 public function emailSettingsSectionCallback(): void {
968 echo '<p>' . __('Configure how emails are sent from Easy Invoice.', 'easy-invoice') . '</p>';
969 }
970
971 /**
972 * Text field callback
973 *
974 * @param array $args Field arguments
975 */
976 public function textFieldCallback(array $args): void {
977 $field_id = $args['label_for'];
978 $value = get_option($field_id, '');
979 echo '<input type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
980 }
981
982 /**
983 * Email field callback
984 *
985 * @param array $args Field arguments
986 */
987 public function emailFieldCallback(array $args): void {
988 $field_id = $args['label_for'];
989 $value = get_option($field_id, '');
990 echo '<input type="email" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
991 }
992
993 /**
994 * Checkbox field callback
995 *
996 * @param array $args Field arguments
997 */
998 public function checkboxFieldCallback(array $args): void {
999 $field_id = $args['label_for'];
1000 $value = get_option($field_id, '');
1001 echo '<input type="checkbox" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="yes"' . checked($value, 'yes', false) . '>';
1002 echo '<span class="description">' . __('Enable this option', 'easy-invoice') . '</span>';
1003 }
1004
1005 /**
1006 * Log email sent
1007 *
1008 * @param Invoice $invoice The invoice
1009 * @param string $email The email address
1010 * @param string $type The email type
1011 */
1012 public function logEmailSent($invoice, string $email, string $type): void {
1013 $this->log(sprintf('Email sent to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'info');
1014 }
1015
1016 /**
1017 * Log email failed
1018 *
1019 * @param Invoice $invoice The invoice
1020 * @param string $email The email address
1021 * @param string $type The email type
1022 */
1023 public function logEmailFailed($invoice, string $email, string $type): void {
1024 $this->log(sprintf('Email failed to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'error');
1025 }
1026
1027 /**
1028 * Get default invoice template
1029 *
1030 * @return string Template
1031 */
1032 private function getDefaultInvoiceTemplate(): string {
1033 return '<h2>📄 Your Invoice is Ready</h2>
1034
1035 <p>Dear {{client_name}},</p>
1036
1037 <div class="highlight-box">
1038 <p><strong>Invoice #{{invoice_number}}</strong><br>
1039 <span class="amount-highlight">{{total_amount}}</span><br>
1040 Due Date: <strong>{{due_date}}</strong></p>
1041 </div>
1042
1043 <p>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.</p>
1044
1045 <div class="info-box">
1046 <p><strong>📋 Payment Details:</strong><br>
1047 • Invoice Number: {{invoice_number}}<br>
1048 • Total Amount: {{total_amount}}<br>
1049 • Due Date: {{due_date}}<br>
1050 • Payment Terms: {{payment_terms}}</p>
1051 </div>
1052
1053 <div class="highlight-box">
1054 <p><strong>🔗 View Invoice Online:</strong><br>
1055 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1056 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1057 </div>
1058
1059 <div class="warning-box">
1060 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1061 </div>
1062
1063 <p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1064
1065 <div class="divider"></div>
1066
1067 <p>Thank you for your business!</p>
1068
1069 <p>Best regards,<br>
1070 <strong>{{company_name}}</strong><br>
1071 {{company_email}}</p>';
1072 }
1073
1074 private function getDefaultReminderTemplate(): string {
1075 return '<h2>⏰ Payment Reminder</h2>
1076
1077 <p>Dear {{client_name}},</p>
1078
1079 <div class="warning-box">
1080 <p><strong>Invoice #{{invoice_number}}</strong><br>
1081 <span class="amount-highlight">{{total_amount}}</span><br>
1082 Due Date: <strong>{{due_date}}</strong></p>
1083 </div>
1084
1085 <p>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.</p>
1086
1087 <div class="info-box">
1088 <p><strong>💳 Payment Options:</strong><br>
1089 • Online payment through our secure portal<br>
1090 • Bank transfer to the details provided<br>
1091 • Check or money order</p>
1092 </div>
1093
1094 <div class="highlight-box">
1095 <p><strong>🔗 View Invoice Online:</strong><br>
1096 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1097 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1098 </div>
1099
1100 <div class="highlight-box">
1101 <p><strong>📞 Need Help?</strong> If you have any questions or need to discuss payment arrangements, please contact us immediately.</p>
1102 </div>
1103
1104 <p>Thank you for your prompt attention to this matter.</p>
1105
1106 <div class="divider"></div>
1107
1108 <p>Best regards,<br>
1109 <strong>{{company_name}}</strong><br>
1110 {{company_email}}</p>';
1111 }
1112
1113 private function getDefaultPaymentTemplate(): string {
1114 return '<h2>�
1115 Payment Received - Thank You!</h2>
1116
1117 <p>Dear {{client_name}},</p>
1118
1119 <div class="success-box">
1120 <p><strong>Payment Confirmation</strong><br>
1121 Invoice #{{invoice_number}}<br>
1122 <span class="amount-highlight">{{payment_amount}}</span><br>
1123 Payment Date: <strong>{{payment_date}}</strong><br>
1124 Payment Method: <strong>{{payment_method}}</strong></p>
1125 </div>
1126
1127 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1128
1129 <div class="info-box">
1130 <p><strong>📊 Payment Details:</strong><br>
1131 Invoice Number: {{invoice_number}}<br>
1132 Amount Paid: {{payment_amount}}<br>
1133 Payment Date: {{payment_date}}<br>
1134 Payment Method: {{payment_method}}<br>
1135 Transaction ID: {{transaction_id}}</p>
1136 </div>
1137
1138 <div class="highlight-box">
1139 <p><strong>🎉 Status: PAID</strong><br>
1140 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1141 </div>
1142
1143 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1144
1145 <div class="divider"></div>
1146
1147 <p>Thank you for choosing our services!</p>
1148
1149 <p>Best regards,<br>
1150 <strong>{{company_name}}</strong><br>
1151 {{company_email}}</p>';
1152 }
1153
1154 private function getDefaultQuoteTemplate(): string {
1155 return '<h2>📋 Your Quote is Ready</h2>
1156
1157 <p>Dear {{client_name}},</p>
1158
1159 <div class="highlight-box">
1160 <p><strong>Quote #{{quote_number}}</strong><br>
1161 <span class="amount-highlight">{{total_amount}}</span><br>
1162 Valid Until: <strong>{{expiry_date}}</strong></p>
1163 </div>
1164
1165 <p>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.</p>
1166
1167 <div class="info-box">
1168 <p><strong>📋 Quote Summary:</strong><br>
1169 • Quote Number: {{quote_number}}<br>
1170 • Total Amount: {{total_amount}}<br>
1171 • Valid Until: {{expiry_date}}<br>
1172 • Terms: {{payment_terms}}</p>
1173 </div>
1174
1175 <div class="highlight-box">
1176 <p><strong>🔗 View Quote Online:</strong><br>
1177 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1178 <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1179 </div>
1180
1181 <div class="warning-box">
1182 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1183 </div>
1184
1185 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1186
1187 <div class="divider"></div>
1188
1189 <p>We look forward to working with you!</p>
1190
1191 <p>Best regards,<br>
1192 <strong>{{company_name}}</strong><br>
1193 {{company_email}}</p>';
1194 }
1195
1196 private function getDefaultQuoteAcceptedTemplate(): string {
1197 return '<h2>🎉 Quote Accepted - Project Confirmed!</h2>
1198
1199 <p>Dear {{client_name}},</p>
1200
1201 <div class="success-box">
1202 <p><strong>Quote #{{quote_number}} - ACCEPTED</strong><br>
1203 <span class="amount-highlight">{{total_amount}}</span><br>
1204 Acceptance Date: <strong>{{acceptance_date}}</strong></p>
1205 </div>
1206
1207 <p>Thank you for accepting our quote! We\'re excited to begin working on your project.</p>
1208
1209 <div class="info-box">
1210 <p><strong>🚀 Next Steps:</strong><br>
1211 • We will create an invoice for the accepted quote<br>
1212 • You will receive payment instructions<br>
1213 • Project work will begin as scheduled</p>
1214 </div>
1215
1216 <div class="highlight-box">
1217 <p><strong>📞 What\'s Next?</strong> Our team will be in touch shortly with the next steps and any additional information you may need.</p>
1218 </div>
1219
1220 <div class="divider"></div>
1221
1222 <p>Thank you for choosing our services!</p>
1223
1224 <p>Best regards,<br>
1225 <strong>{{company_name}}</strong><br>
1226 {{company_email}}</p>';
1227 }
1228
1229 private function getDefaultQuoteDeclinedTemplate(): string {
1230 return '<h2>📝 Quote Response Received</h2>
1231
1232 <p>Dear {{client_name}},</p>
1233
1234 <div class="warning-box">
1235 <p><strong>Quote #{{quote_number}} - DECLINED</strong><br>
1236 Response Date: <strong>{{response_date}}</strong></p>
1237 </div>
1238
1239 <p>We have received your response regarding our quote. We understand that this quote may not have met your current needs.</p>
1240
1241 <div class="info-box">
1242 <p><strong>📋 Feedback:</strong><br>
1243 • Reason: {{decline_reason}}<br>
1244 • Response Date: {{response_date}}</p>
1245 </div>
1246
1247 <div class="highlight-box">
1248 <p><strong>🤝 Future Opportunities:</strong> 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.</p>
1249 </div>
1250
1251 <div class="divider"></div>
1252
1253 <p>Thank you for considering our services!</p>
1254
1255 <p>Best regards,<br>
1256 <strong>{{company_name}}</strong><br>
1257 {{company_email}}</p>';
1258 }
1259
1260 /**
1261 * Refresh settings and templates
1262 * Call this method when settings are updated
1263 */
1264 public function refreshSettings(): void {
1265 $this->loadSettings();
1266 $this->loadTemplates();
1267 }
1268
1269 /**
1270 * Get email templates
1271 *
1272 * @return array Templates
1273 */
1274 public function getTemplates(): array {
1275 return $this->templates;
1276 }
1277
1278 /**
1279 * Get email settings
1280 *
1281 * @return array Settings
1282 */
1283 public function getSettings(): array {
1284 return $this->settings;
1285 }
1286
1287 /**
1288 * Test email functionality
1289 *
1290 * @param string $to_email Email to send test to
1291 * @return array Result
1292 */
1293 public function testEmail(string $to_email): array {
1294 $subject = 'Easy Invoice - Email Configuration Test';
1295 $message = '<h2>🧪 Email Configuration Test</h2>
1296
1297 <p>Hello!</p>
1298
1299 <div class="success-box">
1300 <p><strong>�
1301 Test Email Successfully Sent</strong><br>
1302 Date: <strong>' . current_time('Y-m-d H:i:s') . '</strong><br>
1303 To: <strong>' . esc_html($to_email) . '</strong></p>
1304 </div>
1305
1306 <p>This is a test email to verify that your Easy Invoice email configuration is working correctly.</p>
1307
1308 <div class="info-box">
1309 <p><strong>⚙️ Email Settings Verified:</strong><br>
1310 • From Name: ' . esc_html($this->settings['from_name']) . '<br>
1311 • From Email: ' . esc_html($this->settings['from_email']) . '<br>
1312 • Reply-To: ' . esc_html($this->settings['reply_to_email'] ?: 'Not set') . '<br>
1313 • HTML Emails: ' . ($this->settings['enable_html'] === 'yes' ? 'Enabled' : 'Disabled') . '</p>
1314 </div>
1315
1316 <div class="highlight-box">
1317 <p><strong>🎉 Congratulations!</strong> If you received this email, your email configuration is working properly and you can now send invoices, quotes, and payment confirmations to your clients.</p>
1318 </div>
1319
1320 <div class="divider"></div>
1321
1322 <p>Thank you for using Easy Invoice!</p>
1323
1324 <p>Best regards,<br>
1325 <strong>' . esc_html($this->settings['from_name']) . '</strong></p>';
1326
1327 if ($this->settings['enable_html'] === 'yes') {
1328 $message = $this->wrapInHtmlTemplate($message);
1329 }
1330
1331 $headers = $this->prepareEmailHeaders();
1332
1333 $sent = $this->sendEmail($to_email, $subject, $message, $headers);
1334
1335 if ($sent) {
1336 return ['success' => true, 'message' => __('Test email sent successfully', 'easy-invoice')];
1337 } else {
1338 return ['success' => false, 'message' => __('Failed to send test email', 'easy-invoice')];
1339 }
1340 }
1341
1342 /**
1343 * Send test template email with custom subject and body
1344 *
1345 * @param string $to_email Email to send test to
1346 * @param string $subject Email subject
1347 * @param string $body Email body
1348 * @return array Result
1349 */
1350 public function sendTestTemplateEmail(string $to_email, string $subject, string $body): array {
1351 if ($this->settings['enable_html'] === 'yes') {
1352 $body = $this->wrapInHtmlTemplate($body);
1353 }
1354
1355 $headers = $this->prepareEmailHeaders();
1356
1357 $sent = $this->sendEmail($to_email, $subject, $body, $headers);
1358
1359 if ($sent) {
1360 return ['success' => true, 'message' => __('Template test email sent successfully', 'easy-invoice')];
1361 } else {
1362 return ['success' => false, 'message' => __('Failed to send template test email', 'easy-invoice')];
1363 }
1364 }
1365
1366 /**
1367 * Send payment received email
1368 *
1369 * @param Invoice $invoice The invoice
1370 * @param array $payment_data Payment data
1371 * @return array Result array with success status and message
1372 */
1373 public function sendPaymentEmail(Invoice $invoice, array $payment_data = []): array {
1374 try {
1375 // Validate invoice
1376 if (!$invoice || !$invoice->getId()) {
1377 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1378 }
1379
1380 // Get client email
1381 $client_email = $invoice->getCustomerEmail();
1382 if (empty($client_email)) {
1383 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
1384 }
1385
1386 // Get template
1387 $template_key = 'invoice_paid';
1388 if (!isset($this->templates[$template_key])) {
1389 return ['success' => false, 'message' => __('Payment email template not found', 'easy-invoice')];
1390 }
1391
1392 $template = $this->templates[$template_key];
1393
1394 // Check if email is enabled
1395 if (!$template['enabled']) {
1396 return ['success' => false, 'message' => __('Payment received email is disabled', 'easy-invoice')];
1397 }
1398
1399 // Prepare email data
1400 $email_data = $this->preparePaymentEmailData($invoice, $template, $payment_data);
1401
1402 // Send email
1403 $sent = $this->sendEmail(
1404 $email_data['to'],
1405 $email_data['subject'],
1406 $email_data['message'],
1407 $email_data['headers']
1408 );
1409
1410 if ($sent) {
1411 // Log success
1412 do_action('easy_invoice_payment_email_sent', $invoice, $client_email, $payment_data);
1413
1414 return [
1415 'success' => true,
1416 'message' => __('Payment email sent successfully', 'easy-invoice'),
1417 'email_data' => $email_data
1418 ];
1419 } else {
1420 // Log failure
1421 do_action('easy_invoice_payment_email_failed', $invoice, $client_email, $payment_data);
1422
1423 return ['success' => false, 'message' => __('Failed to send payment email', 'easy-invoice')];
1424 }
1425
1426 } catch (\Exception $e) {
1427 $this->log('Payment email sending error: ' . $e->getMessage(), 'error');
1428 return ['success' => false, 'message' => __('Error sending payment email: ', 'easy-invoice') . $e->getMessage()];
1429 }
1430 }
1431
1432 /**
1433 * Send admin notification when payment is received
1434 *
1435 * @param Invoice $invoice The invoice
1436 * @param array $payment_data Payment data (method, amount, etc.)
1437 * @return array Result array with success status and message
1438 */
1439 public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array {
1440 try {
1441 // Validate invoice
1442 if (!$invoice || !$invoice->getId()) {
1443 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1444 }
1445
1446 // Get admin email
1447 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
1448 if (empty($admin_email)) {
1449 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
1450 }
1451
1452 // Get payment method
1453 $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online';
1454 $payment_method_label = $this->getPaymentMethodLabel($payment_method);
1455
1456 // Format amount
1457 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
1458 $amount = $formatter->format($invoice->getTotal());
1459
1460 // Prepare email subject
1461 $subject = sprintf(
1462 __('New Payment Received - Invoice #%s', 'easy-invoice'),
1463 $invoice->getNumber()
1464 );
1465
1466 // Prepare email message
1467 $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data);
1468
1469 // Add HTML wrapper if enabled
1470 if ($this->settings['enable_html'] === 'yes') {
1471 $message = $this->wrapInHtmlTemplate($message);
1472 }
1473
1474 // Prepare headers
1475 $headers = $this->prepareEmailHeaders();
1476
1477 // Send email
1478 $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
1479
1480 if ($sent) {
1481 do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data);
1482 return [
1483 'success' => true,
1484 'message' => __('Admin notification sent successfully', 'easy-invoice')
1485 ];
1486 } else {
1487 do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data);
1488 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
1489 }
1490
1491 } catch (\Exception $e) {
1492 $this->log('Admin payment notification error: ' . $e->getMessage(), 'error');
1493 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
1494 }
1495 }
1496
1497 /**
1498 * Send payment confirmation email to customer
1499 *
1500 * @param Invoice $invoice The invoice
1501 * @param array $payment_data Payment data
1502 * @return array Result array with success status and message
1503 */
1504 public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array {
1505 try {
1506 // Validate invoice
1507 if (!$invoice || !$invoice->getId()) {
1508 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1509 }
1510
1511 // Get customer email
1512 $customer_email = $invoice->getCustomerEmail();
1513 if (empty($customer_email)) {
1514 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
1515 }
1516
1517 // Get currency settings
1518 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
1519 $settings = $settings_controller->getSettings();
1520 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
1521 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
1522
1523 // Format amount
1524 $amount = $invoice->getTotal();
1525 $formatted_amount = $currency_symbol . number_format($amount, 2);
1526
1527 // Prepare email subject
1528 $site_name = get_bloginfo('name');
1529 $subject = sprintf(
1530 __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'),
1531 $site_name,
1532 $invoice->getNumber()
1533 );
1534
1535 // Prepare email message
1536 $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount);
1537
1538 // Add HTML wrapper if enabled
1539 if ($this->settings['enable_html'] === 'yes') {
1540 $message = $this->wrapInHtmlTemplate($message);
1541 }
1542
1543 // Prepare headers
1544 $headers = $this->prepareEmailHeaders();
1545
1546 // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate)
1547 // The payment completion hook already sends a dedicated admin notification
1548 $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true;
1549 if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
1550 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
1551 }
1552
1553 // Send email
1554 $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
1555
1556 if ($sent) {
1557 do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data);
1558 return [
1559 'success' => true,
1560 'message' => __('Payment confirmation email sent successfully', 'easy-invoice')
1561 ];
1562 } else {
1563 do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data);
1564 return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')];
1565 }
1566
1567 } catch (\Exception $e) {
1568 $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error');
1569 return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()];
1570 }
1571 }
1572
1573 /**
1574 * Send payment rejection email to customer
1575 *
1576 * @param Invoice $invoice The invoice
1577 * @param string $reason Rejection reason
1578 * @return array Result array with success status and message
1579 */
1580 public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array {
1581 try {
1582 // Validate invoice
1583 if (!$invoice || !$invoice->getId()) {
1584 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1585 }
1586
1587 // Get customer email
1588 $customer_email = $invoice->getCustomerEmail();
1589 if (empty($customer_email)) {
1590 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
1591 }
1592
1593 // Prepare email subject
1594 $site_name = get_bloginfo('name');
1595 $subject = sprintf(
1596 __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'),
1597 $site_name,
1598 $invoice->getNumber()
1599 );
1600
1601 // Prepare email message
1602 $message = $this->preparePaymentRejectionMessage($invoice, $reason);
1603
1604 // Add HTML wrapper if enabled
1605 if ($this->settings['enable_html'] === 'yes') {
1606 $message = $this->wrapInHtmlTemplate($message);
1607 }
1608
1609 // Prepare headers
1610 $headers = $this->prepareEmailHeaders();
1611
1612 // Add BCC to admin if enabled
1613 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
1614 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
1615 }
1616
1617 // Send email
1618 $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
1619
1620 if ($sent) {
1621 do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason);
1622 return [
1623 'success' => true,
1624 'message' => __('Payment rejection email sent successfully', 'easy-invoice')
1625 ];
1626 } else {
1627 do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason);
1628 return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')];
1629 }
1630
1631 } catch (\Exception $e) {
1632 $this->log('Payment rejection email error: ' . $e->getMessage(), 'error');
1633 return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()];
1634 }
1635 }
1636
1637 /**
1638 * Prepare admin payment notification message
1639 *
1640 * @param Invoice $invoice The invoice
1641 * @param string $payment_method_label Payment method label
1642 * @param string $amount Formatted amount
1643 * @param array $payment_data Payment data
1644 * @return string Email message
1645 */
1646 private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string {
1647 $invoice_number = $invoice->getNumber();
1648 $customer_name = $invoice->getCustomerName();
1649 $customer_email = $invoice->getCustomerEmail();
1650 $invoice_id = $invoice->getId();
1651
1652 $message = sprintf(
1653 __('A new %s payment has been received for invoice #%s.', 'easy-invoice'),
1654 $payment_method_label,
1655 $invoice_number
1656 );
1657 $message .= "\n\n";
1658 $message .= __('Invoice Details:', 'easy-invoice');
1659 $message .= "\n";
1660 $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount);
1661 $message .= "\n";
1662 $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name);
1663 $message .= "\n";
1664 $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email);
1665
1666 // Add transaction ID if available
1667 if (!empty($payment_data['transaction_id'])) {
1668 $message .= "\n";
1669 $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']);
1670 }
1671
1672 $message .= "\n\n";
1673 $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice');
1674 $message .= "\n";
1675 $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id);
1676 $message .= "\n\n";
1677 $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice');
1678
1679 return $message;
1680 }
1681
1682 /**
1683 * Prepare payment confirmation message
1684 *
1685 * @param Invoice $invoice The invoice
1686 * @param string $formatted_amount Formatted amount
1687 * @return string Email message
1688 */
1689 private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string {
1690 $customer_name = $invoice->getCustomerName();
1691 $invoice_number = $invoice->getNumber();
1692 $site_name = get_bloginfo('name');
1693 $company_name = get_option('easy_invoice_company_name', $site_name);
1694
1695 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
1696 $message .= "\n\n";
1697 $message .= sprintf(
1698 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
1699 $formatted_amount,
1700 $invoice_number
1701 );
1702 $message .= "\n\n";
1703 $message .= __('Thank you for your business.', 'easy-invoice');
1704 $message .= "\n\n";
1705 $message .= __('Regards,', 'easy-invoice');
1706 $message .= "\n";
1707 $message .= $company_name;
1708
1709 return $message;
1710 }
1711
1712 /**
1713 * Prepare payment rejection message
1714 *
1715 * @param Invoice $invoice The invoice
1716 * @param string $reason Rejection reason
1717 * @return string Email message
1718 */
1719 private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string {
1720 $customer_name = $invoice->getCustomerName();
1721 $invoice_number = $invoice->getNumber();
1722 $site_name = get_bloginfo('name');
1723 $company_name = get_option('easy_invoice_company_name', $site_name);
1724
1725 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
1726 $message .= "\n\n";
1727 $message .= sprintf(
1728 __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'),
1729 $invoice_number
1730 );
1731
1732 if (!empty($reason)) {
1733 $message .= "\n\n";
1734 $message .= __('Reason:', 'easy-invoice');
1735 $message .= "\n";
1736 $message .= $reason;
1737 }
1738
1739 $message .= "\n\n";
1740 $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice');
1741 $message .= "\n\n";
1742 $message .= __('Regards,', 'easy-invoice');
1743 $message .= "\n";
1744 $message .= $company_name;
1745
1746 return $message;
1747 }
1748
1749 /**
1750 * Send admin notification for quote acceptance/decline
1751 *
1752 * @param Quote $quote The quote
1753 * @param string $action Action type ('accepted' or 'declined')
1754 * @return array Result array with success status and message
1755 */
1756 public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array {
1757 try {
1758 // Validate quote
1759 if (!$quote || !$quote->getId()) {
1760 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
1761 }
1762
1763 // Get admin email
1764 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
1765 if (empty($admin_email)) {
1766 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
1767 }
1768
1769 // Prepare email subject
1770 $subject = sprintf(
1771 __('Quote %s has been %s', 'easy-invoice'),
1772 $quote->getNumber(),
1773 $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice')
1774 );
1775
1776 // Prepare email message
1777 $message = $this->prepareAdminQuoteNotificationMessage($quote, $action);
1778
1779 // Add HTML wrapper if enabled
1780 if ($this->settings['enable_html'] === 'yes') {
1781 $message = $this->wrapInHtmlTemplate($message);
1782 }
1783
1784 // Prepare headers
1785 $headers = $this->prepareEmailHeaders();
1786
1787 // Send email
1788 $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
1789
1790 if ($sent) {
1791 do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action);
1792 return [
1793 'success' => true,
1794 'message' => __('Admin notification sent successfully', 'easy-invoice')
1795 ];
1796 } else {
1797 do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action);
1798 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
1799 }
1800
1801 } catch (\Exception $e) {
1802 $this->log('Admin quote notification error: ' . $e->getMessage(), 'error');
1803 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
1804 }
1805 }
1806
1807 /**
1808 * Prepare admin quote notification message
1809 *
1810 * @param Quote $quote The quote
1811 * @param string $action Action type ('accepted' or 'declined')
1812 * @return string Email message
1813 */
1814 private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string {
1815 $site_name = get_bloginfo('name');
1816 $quote_number = $quote->getNumber();
1817 $customer_name = $quote->getCustomerName();
1818
1819 // Format amount
1820 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote);
1821 $formatted_amount = $formatter->format($quote->getTotal());
1822
1823 $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice');
1824 $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice');
1825
1826 $message = __('Hello,', 'easy-invoice');
1827 $message .= "\n\n";
1828 $message .= sprintf(
1829 __('The quote %s for %s has been %s by the client.', 'easy-invoice'),
1830 $quote_number,
1831 $customer_name,
1832 $action_label
1833 );
1834 $message .= "\n\n";
1835 $message .= __('Quote Details:', 'easy-invoice');
1836 $message .= "\n";
1837 $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number);
1838 $message .= "\n";
1839 $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name);
1840 $message .= "\n";
1841 $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount);
1842 $message .= "\n";
1843 $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format')));
1844 $message .= "\n\n";
1845 $message .= __('You can view the quote at:', 'easy-invoice');
1846 $message .= "\n";
1847 $message .= get_permalink($quote->getId());
1848 $message .= "\n\n";
1849 $message .= __('Best regards,', 'easy-invoice');
1850 $message .= "\n";
1851 $message .= $site_name;
1852
1853 return $message;
1854 }
1855
1856 /**
1857 * Handle payment completed hook
1858 * Sends admin notification and customer confirmation when payment is completed
1859 *
1860 * @param int $invoice_id Invoice ID
1861 * @param \EasyInvoice\Models\Invoice $invoice Invoice object
1862 * @param array $payment_data Payment data (method, gateway, transaction_id, amount)
1863 * @return void
1864 */
1865 public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void {
1866 if (!$invoice || !$invoice->getId()) {
1867 return;
1868 }
1869
1870 // Send admin notification
1871 $this->sendAdminPaymentNotification($invoice, $payment_data);
1872
1873 // Send customer confirmation email using proper template system
1874 // Check if payment email is enabled first
1875 if (isset($this->templates['invoice_paid']) && $this->templates['invoice_paid']['enabled']) {
1876 $this->sendInvoiceEmail($invoice, 'paid', array_merge($payment_data, ['skip_bcc' => true]));
1877 }
1878 }
1879
1880 /**
1881 * Get payment method label
1882 *
1883 * @param string $method Payment method
1884 * @return string Payment method label
1885 */
1886 private function getPaymentMethodLabel(string $method): string {
1887 $labels = [
1888 'bank' => __('Bank Transfer', 'easy-invoice'),
1889 'cheque' => __('Cheque', 'easy-invoice'),
1890 'paypal' => __('PayPal', 'easy-invoice'),
1891 'stripe' => __('Stripe', 'easy-invoice'),
1892 'square' => __('Square', 'easy-invoice'),
1893 'mollie' => __('Mollie', 'easy-invoice'),
1894 'authorizenet' => __('Authorize.Net', 'easy-invoice'),
1895 'manual' => __('Manual Payment', 'easy-invoice'),
1896 'online' => __('Online Payment', 'easy-invoice'),
1897 ];
1898
1899 return $labels[$method] ?? ucfirst($method);
1900 }
1901
1902 }