PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.14
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.14
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.1.14, at includes/Services/EmailManager.php

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