PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.2
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 / Controllers / PaymentController.php

PaymentController.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.2, at includes/Controllers/PaymentController.php

1,436 lines 56.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Payment Controller
4 *
5 * @package Easy_Invoice
6 */
7
8 namespace EasyInvoice\Controllers;
9
10 use EasyInvoice\PaymentGatewayManager;
11 use EasyInvoice\EasyInvoice;
12 use EasyInvoice\Models\Invoice;
13 use EasyInvoice\Models\Payment;
14 use EasyInvoice\Traits\TemplateTrait;
15 use EasyInvoice\Traits\PaymentCalculationTrait;
16 use EasyInvoice\Constants\PagesSlugs;
17 use EasyInvoice\Constants\InvoiceFields;
18 use EasyInvoice\Constants\InvoiceMetaKeys;
19 use EasyInvoice\Helpers\Sanitization;
20 use EasyInvoice\Providers\InvoiceServiceProvider; // Assuming this is used elsewhere or for future
21
22 /**
23 * Class PaymentController
24 *
25 * @package EasyInvoice\Controllers
26 */
27 class PaymentController extends BaseController {
28 use TemplateTrait;
29 use PaymentCalculationTrait;
30
31 /**
32 * Payment gateway manager instance
33 *
34 * @var PaymentGatewayManager
35 */
36 private $gatewayManager;
37
38 /**
39 * Constructor
40 */
41 public function __construct() {
42 $this->gatewayManager = EasyInvoice::getInstance()->getGatewayManager();
43 }
44
45 /**
46 * Initialize the controller
47 */
48 public function init() {
49 add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']);
50 add_action('wp_ajax_easy_invoice_process_payment', [$this, 'processPayment']);
51 add_action('wp_ajax_nopriv_easy_invoice_process_payment', [$this, 'processPayment']);
52 add_action('wp_ajax_easy_invoice_update_payment', [$this, 'updatePayment']);
53 add_action('wp_ajax_easy_invoice_payment_callback', [$this, 'handleCallback']);
54 add_action('wp_ajax_nopriv_easy_invoice_payment_callback', [$this, 'handleCallback']);
55 add_action('wp_ajax_easy_invoice_verify_manual_payment', [$this, 'verifyManualPayment']);
56 add_action('wp_ajax_easy_invoice_reject_manual_payment', [$this, 'rejectManualPayment']);
57
58
59
60 // Handler for submitting payment proof for manual gateways
61 add_action('wp_ajax_easy_invoice_submit_payment_proof', [$this, 'submitPaymentProof']);
62 add_action('wp_ajax_nopriv_easy_invoice_submit_payment_proof', [$this, 'submitPaymentProof']);
63
64 // Handler for getting payment instructions for manual gateways
65 add_action('wp_ajax_easy_invoice_get_payment_instructions', [$this, 'getPaymentInstructions']);
66 add_action('wp_ajax_nopriv_easy_invoice_get_payment_instructions', [$this, 'getPaymentInstructions']);
67
68 // Handler for admin to mark an invoice as paid
69 add_action('wp_ajax_easy_invoice_approve_payment', [$this, 'mark_invoice_paid_ajax']);
70
71 // Stripe payment handlers moved to Pro plugin
72
73 add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
74
75 // Add filter to show pending payments in admin
76 add_filter('easy_invoice_admin_payment_statuses', [$this, 'addPendingPaymentStatuses']);
77
78 // Add custom columns to payments list
79 add_filter('manage_easy-payment_posts_columns', [$this, 'addPaymentMethodColumn']);
80 add_action('manage_easy-payment_posts_custom_column', [$this, 'renderPaymentMethodColumn'], 10, 2);
81
82 // Add reminder CRON job for pending payments
83 add_action('easy_invoice_payment_reminder', [$this, 'sendPaymentReminders']);
84 if (!wp_next_scheduled('easy_invoice_payment_reminder')) {
85 wp_schedule_event(time(), 'daily', 'easy_invoice_payment_reminder');
86 }
87
88 // Handle bulk actions
89 add_action('admin_init', [$this, 'handleBulkActions']);
90 }
91
92 /**
93 * Get payment instructions for manual gateways
94 */
95 public function getPaymentInstructions() {
96 // Verify nonce
97 if (!wp_verify_nonce($_POST['nonce'], 'easy_invoice_payment')) {
98 wp_send_json_error(['message' => 'Security check failed']);
99 return;
100 }
101
102 $gateway = sanitize_text_field($_POST['gateway']);
103 $invoice_id = intval($_POST['invoice_id']);
104
105 if (!$gateway || !$invoice_id) {
106 wp_send_json_error(['message' => 'Missing required parameters']);
107 return;
108 }
109
110 // Get invoice
111 $invoice_post = get_post($invoice_id);
112 if (!$invoice_post || $invoice_post->post_type !== 'easy_invoice') {
113 wp_send_json_error(['message' => 'Invalid invoice']);
114 return;
115 }
116
117 $invoice = new \EasyInvoice\Models\Invoice($invoice_post);
118
119 // Get gateway instance
120 $gateway_instance = $this->gatewayManager->getGateway($gateway);
121
122 if (!$gateway_instance) {
123 wp_send_json_error(['message' => 'Gateway not found']);
124 return;
125 }
126
127 // Get instructions using the hook system
128 ob_start();
129 do_action('easy_invoice_payment_gateways_after', $invoice, $gateway);
130 $instructions = ob_get_clean();
131
132 if ($instructions) {
133 wp_send_json_success(['instructions' => $instructions]);
134 } else {
135 wp_send_json_error(['message' => 'No instructions available']);
136 }
137 }
138
139 /**
140 * Enqueue admin assets
141 */
142 public function enqueueAssets() {
143 $screen = get_current_screen();
144 if (!$screen || !property_exists($screen, 'id') || strpos($screen->id, 'easy-invoice') === false) {
145 return;
146 }
147
148
149 }
150
151 /**
152 * Display method implementation
153 *
154 * @param array $args Display arguments
155 */
156 public function display(array $args = []) {
157 $page = isset($args['page']) ? $args['page'] : '';
158
159 switch ($page) {
160 case PagesSlugs::PAYMENTS:
161 $this->displayPaymentsPage();
162 break;
163
164 case PagesSlugs::PAYMENT_NEW:
165 $this->displayTemplate(EASY_INVOICE_PLUGIN_DIR . 'templates/payments/new.php');
166 break;
167
168 case 'view':
169 $payment_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
170 if ($payment_id) {
171 try {
172 $payment = new Payment($payment_id);
173 $this->displayTemplate(EASY_INVOICE_PLUGIN_DIR . 'templates/payments/view.php', ['payment' => $payment]);
174 } catch (\Exception $e) {
175 wp_die(__('Invalid payment ID', 'easy-invoice'));
176 }
177 } else {
178 wp_die(__('Payment ID is required', 'easy-invoice'));
179 }
180 break;
181
182 case 'edit':
183 $payment_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
184 if ($payment_id) {
185 try {
186 $payment = new Payment($payment_id);
187 $this->displayTemplate(EASY_INVOICE_PLUGIN_DIR . 'templates/payments/edit.php', ['payment' => $payment]);
188 } catch (\Exception $e) {
189 wp_die(__('Invalid payment ID', 'easy-invoice'));
190 }
191 } else {
192 wp_die(__('Payment ID is required', 'easy-invoice'));
193 }
194 break;
195
196 default:
197 $this->displayPaymentsPage();
198 break;
199 }
200 }
201
202 /**
203 * Display payments page with pagination
204 */
205 protected function displayPaymentsPage() {
206 // Get current view (all, trash)
207 $current_view = isset($_GET['view']) ? sanitize_text_field($_GET['view']) : 'all';
208
209 // Get status filter
210 $status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
211
212 // Pagination settings
213 $per_page = 20;
214 $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
215
216 // Build query arguments
217 $args = array(
218 'post_type' => 'easy_invoice_payment',
219 'posts_per_page' => $per_page,
220 'paged' => $current_page,
221 'orderby' => 'ID',
222 'order' => 'DESC',
223 'no_found_rows' => false, // We need this for pagination
224 );
225
226 // Set post status based on current view
227 if ($current_view === 'trash') {
228 $args['post_status'] = 'trash';
229 } else {
230 $args['post_status'] = 'publish';
231 }
232
233 // Add status filter if set
234 if (!empty($status_filter)) {
235 $args['meta_query'] = array(
236 array(
237 'key' => '_status',
238 'value' => $status_filter,
239 ),
240 );
241 }
242
243 // Allow plugins to modify query arguments
244 $args = apply_filters('easy_invoice_payment_controller_query_args', $args, $current_view, $status_filter);
245
246
247 // Get paginated payments using WordPress query
248 $wp_query = new \WP_Query($args);
249
250
251 $payments = [];
252
253 if ($wp_query->have_posts()) {
254 while ($wp_query->have_posts()) {
255 $wp_query->the_post();
256 $post = get_post();
257 $payment = new Payment($post);
258 $payments[] = $payment;
259 }
260 }
261
262 wp_reset_postdata();
263
264 // Allow plugins to modify the payments array
265 $payments = apply_filters('easy_invoice_payment_controller_payments_list', $payments, $wp_query);
266
267 // Get pagination info from WordPress query
268 $total_payments = $wp_query->found_posts;
269 $total_pages = $wp_query->max_num_pages;
270
271 // Calculate statistics from ALL payments (not just current page)
272 $stats_args = array(
273 'post_type' => 'easy_invoice_payment',
274 'posts_per_page' => -1, // Get all payments
275 'meta_query' => array(
276 array(
277 'key' => '_status',
278 'compare' => 'EXISTS',
279 ),
280 ),
281 );
282
283 // Set post status for stats based on current view
284 if ($current_view === 'trash') {
285 $stats_args['post_status'] = 'trash';
286 } else {
287 $stats_args['post_status'] = 'publish';
288 }
289
290 $stats_query = new \WP_Query($stats_args);
291
292 $stats = [
293 'total_payments' => $stats_query->found_posts,
294 'total_amount' => 0,
295 'completed_payments' => 0,
296 'pending_payments' => 0,
297 'failed_payments' => 0
298 ];
299
300 // Calculate stats from the query results
301 if ($stats_query->have_posts()) {
302 while ($stats_query->have_posts()) {
303 $stats_query->the_post();
304 $payment = new Payment(get_post());
305
306 $amount = floatval($payment->getAmount());
307 $status = $payment->getStatus();
308
309 $stats['total_amount'] += $amount;
310
311 switch ($status) {
312 case 'completed':
313 $stats['completed_payments']++;
314 break;
315 case 'pending':
316 $stats['pending_payments']++;
317 break;
318 case 'failed':
319 $stats['failed_payments']++;
320 break;
321 }
322 }
323 }
324 wp_reset_postdata();
325
326 // Ensure all required keys exist with default values
327 $stats = array_merge([
328 'total_payments' => 0,
329 'total_amount' => 0,
330 'completed_payments' => 0,
331 'pending_payments' => 0,
332 'failed_payments' => 0
333 ], $stats);
334
335 // Get trash count for tab display
336 $trash_args = array(
337 'post_type' => 'easy_invoice_payment',
338 'post_status' => 'trash',
339 'posts_per_page' => -1
340 );
341 $trash_query = new \WP_Query($trash_args);
342 $trash_count = $trash_query->found_posts;
343
344 // Define available status filters
345 $status_filters = array(
346 'completed' => 'Completed',
347 'pending' => 'Pending',
348 'failed' => 'Failed'
349 );
350
351 // Prepare template data
352 $template_data = [
353 'payments' => $payments,
354 'current_view' => $current_view,
355 'status_filter' => $status_filter,
356 'status_filters' => $status_filters,
357 'trash_count' => $trash_count,
358 'stats' => $stats,
359 'current_page' => $current_page,
360 'per_page' => $per_page,
361 'total_payments' => $total_payments,
362 'total_pages' => $total_pages,
363 'wp_query' => $wp_query
364 ];
365
366 // Allow plugins to modify template data
367 $template_data = apply_filters('easy_invoice_payment_controller_template_data', $template_data);
368
369 // Display the template
370 $this->displayTemplate(
371 EASY_INVOICE_PLUGIN_DIR . 'templates/payments/list.php',
372 $template_data
373 );
374
375 // Allow plugins to perform actions after displaying payments page
376 do_action('easy_invoice_payment_controller_after_display_payments_page', $template_data);
377 }
378
379 /**
380 * Enqueue required scripts and styles
381 */
382 public function enqueueScripts(): void {
383 // Check if scripts are already enqueued
384 if (wp_script_is('easy-invoice-payment', 'enqueued')) {
385 return;
386 }
387
388 // Enqueue our custom scripts
389 wp_enqueue_script(
390 'easy-invoice-payment',
391 EASY_INVOICE_URL . 'assets/js/payment.js',
392 ['jquery'],
393 EASY_INVOICE_VERSION,
394 true
395 );
396
397 // Get currency settings
398 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
399 $settings = $settings_controller->getSettings();
400 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
401 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
402
403 // Localize script variables for payment form
404 wp_localize_script('easy-invoice-payment', 'easy_invoice_vars', [
405 'ajax_url' => admin_url('admin-ajax.php'),
406 'nonce' => wp_create_nonce('easy_invoice_payment'),
407 'currency_symbol' => $currency_symbol,
408 'currency_code' => $currency_code
409 ]);
410 }
411
412 // Stripe methods moved to Pro plugin
413
414 /**
415 * Process payment via AJAX
416 */
417 public function processPayment() {
418 check_ajax_referer('easy_invoice_payment', 'payment_nonce');
419
420 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
421 $payment_method_slug = isset($_POST['payment_method']) ? sanitize_text_field($_POST['payment_method']) : '';
422
423 // Add filter for extensions to handle custom payment logic (e.g., partial payments)
424 $custom_result = apply_filters('easy_invoice_before_process_payment', null, $invoice_id, $_POST);
425
426 if (is_array($custom_result) && isset($custom_result['handled']) && $custom_result['handled']) {
427 if ($custom_result['success']) {
428 wp_send_json_success($custom_result);
429 } else {
430 wp_send_json_error(['message' => $custom_result['message'] ?? __('Payment failed.', 'easy-invoice')]);
431 }
432 return;
433 }
434
435 if (!$invoice_id || !$payment_method_slug) {
436 wp_send_json_error(['message' => __('Missing required fields.', 'easy-invoice')]);
437 return;
438 }
439
440 $invoice_post = get_post($invoice_id);
441 if (!$invoice_post || $invoice_post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
442 wp_send_json_error(['message' => __('Invalid invoice.', 'easy-invoice')]);
443 return;
444 }
445
446 $invoice = new \EasyInvoice\Models\Invoice($invoice_post);
447 $amount = $invoice->total ?? 0;
448
449 // Log the payment processing details
450
451 $gateway_instance = $this->gatewayManager->getGateway($payment_method_slug);
452
453 if (!$gateway_instance || !$gateway_instance->isEnabled() || !$gateway_instance->isAvailable()) {
454 wp_send_json_error(['message' => __('Selected payment gateway is not available or configured correctly.', 'easy-invoice')]);
455 return;
456 }
457
458 try {
459 // Pass the entire $_POST array to the gateway
460 $result = $gateway_instance->processPayment($amount, $_POST);
461
462 if (isset($result['success']) && $result['success']) {
463 wp_send_json_success($result);
464 } else {
465 wp_send_json_error(['message' => $result['message'] ?? __('Payment processing failed with the gateway.', 'easy-invoice')]);
466 }
467
468 } catch (\Exception $e) {
469 error_log('Easy Invoice Payment Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine());
470 wp_send_json_error(['message' => __('An unexpected error occurred during payment processing. Please check plugin logs or contact support.', 'easy-invoice')]);
471 }
472 }
473
474 /**
475 * Handle payment callback/webhook
476 */
477 public function handleCallback(): void {
478 check_ajax_referer('easy_invoice_payment', 'payment_nonce');
479
480 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
481 $gateway = isset($_POST['gateway']) ? sanitize_text_field($_POST['gateway']) : '';
482
483 if (!$invoice_id || !$gateway) {
484 wp_send_json_error(['message' => __('Invalid request', 'easy-invoice')]);
485 }
486
487 $gateway_instance = $this->gatewayManager->getGateway($gateway);
488 if (!$gateway_instance) {
489 wp_send_json_error(['message' => __('Invalid payment gateway', 'easy-invoice')]);
490 }
491
492 $result = $gateway_instance->handleCallback($_POST);
493
494 // Send admin notification for manual payments
495 if ($result['success'] && in_array($gateway, ['bank', 'cheque'])) {
496 do_action('easy_invoice_manual_payment_submitted', $invoice_id, $gateway);
497 }
498
499 if ($result['success']) {
500 wp_send_json_success($result);
501 } else {
502 wp_send_json_error($result);
503 }
504 }
505
506 /**
507 * Get available payment gateways for an invoice
508 *
509 * @param int $invoice_id
510 * @return array
511 */
512 public function getAvailableGateways(int $invoice_id): array {
513 $post = get_post($invoice_id);
514 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
515 return [];
516 }
517
518 $invoice = new \EasyInvoice\Models\Invoice($post);
519 $invoice_status = $invoice->getStatus();
520
521 if (!in_array($invoice_status, [ 'unpaid', 'available'])) {
522 return [];
523 }
524
525 $enabled_gateways = $this->gatewayManager->getEnabledGateways();
526
527 if (empty($enabled_gateways)) {
528 return [];
529 }
530
531 // Get invoice-specific gateways (comma-separated string or empty)
532 $invoice_gateways = $invoice->getPaymentGateways();
533 $selected_gateways = [];
534
535 // Handle both string and array formats
536 if (!empty($invoice_gateways)) {
537 if (is_string($invoice_gateways)) {
538 // If it's a string, split by comma
539 $selected_gateways = array_filter(array_map('trim', explode(',', $invoice_gateways)));
540 } elseif (is_array($invoice_gateways)) {
541 // If it's already an array, use it directly
542 $selected_gateways = array_filter($invoice_gateways);
543 }
544 }
545
546 $available_gateways = [];
547 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
548
549 // $enabled_gateways is an associative array with gateway_id as key and gateway object as value
550 foreach ($enabled_gateways as $gateway_id => $gateway) {
551 // If invoice has custom gateways selected, only show those
552 // If no custom gateways are selected (empty array), show all enabled gateways
553 if (!empty($selected_gateways) && !in_array($gateway_id, $selected_gateways, true)) {
554 continue;
555 }
556
557 $is_available = $gateway->isAvailable();
558
559 if ($is_available) {
560 $available_gateways[] = [
561 'id' => $gateway_id,
562 'title' => $gateway_manager->getGatewayDisplayName($gateway_id),
563 'icon' => $gateway->getIcon(),
564 'description' => $gateway->getDescription()
565 ];
566 }
567 }
568
569 return $available_gateways;
570 }
571
572 /**
573 * Update payment via AJAX
574 */
575 public function updatePayment() {
576 check_ajax_referer('easy_invoice_payment', 'payment_nonce');
577
578 $payment_id = isset($_POST['payment_id']) ? intval($_POST['payment_id']) : 0;
579 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
580 $amount = isset($_POST['amount']) ? floatval($_POST['amount']) : 0;
581 $payment_method = isset($_POST['payment_method']) ? sanitize_text_field($_POST['payment_method']) : '';
582 $payment_date = isset($_POST['payment_date']) ? sanitize_text_field($_POST['payment_date']) : date('Y-m-d');
583 $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : 'pending';
584 $notes = isset($_POST['notes']) ? sanitize_textarea_field($_POST['notes']) : '';
585
586 if (!$payment_id || !$invoice_id || !$amount || !$payment_method) {
587 wp_send_json_error(['message' => __('Missing required fields', 'easy-invoice')]);
588 return;
589 }
590
591 try {
592 $payment = new Payment($payment_id);
593 if (!$payment->exists()) {
594 wp_send_json_error(['message' => __('Invalid payment', 'easy-invoice')]);
595 return;
596 }
597
598 $post = get_post($invoice_id);
599 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
600 wp_send_json_error(['message' => __('Invalid invoice', 'easy-invoice')]);
601 return;
602 }
603 $invoice = new Invoice($post);
604
605 $payment_data = [
606 'invoice_id' => $invoice_id,
607 'amount' => $amount,
608 'payment_method' => $payment_method,
609 'payment_date' => $payment_date,
610 'status' => $status,
611 'notes' => $notes,
612 'gateway_response' => [
613 'method' => $payment_method,
614 'date' => $payment_date,
615 'notes' => $notes
616 ]
617 ];
618
619 $result = $payment->update($payment_data);
620
621 if ($result) {
622 wp_send_json_success([
623 'message' => __('Payment updated successfully', 'easy-invoice'),
624 'redirect' => admin_url('admin.php?page=easy-invoice-payments')
625 ]);
626 } else {
627 wp_send_json_error(['message' => __('Failed to update payment', 'easy-invoice')]);
628 }
629 } catch (\Exception $e) {
630 wp_send_json_error(['message' => $e->getMessage()]);
631 }
632 }
633
634 /**
635 * Verify manual payment
636 */
637 public function verifyManualPayment(): void {
638 // Check permissions
639 if (!current_user_can('manage_options')) {
640 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
641 return;
642 }
643
644 // Verify nonce
645 check_ajax_referer('easy_invoice_admin', 'nonce');
646
647 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
648 $amount = isset($_POST['amount']) ? floatval($_POST['amount']) : 0;
649 $payment_method = isset($_POST['payment_method']) ? sanitize_text_field($_POST['payment_method']) : '';
650 $notes = isset($_POST['notes']) ? sanitize_textarea_field($_POST['notes']) : '';
651 $transaction_id = isset($_POST['transaction_id']) ? sanitize_text_field($_POST['transaction_id']) : '';
652
653 if (!$invoice_id || !$amount || !$payment_method) {
654 wp_send_json_error(['message' => __('Missing required fields', 'easy-invoice')]);
655 return;
656 }
657
658 // Get the invoice
659 $post = get_post($invoice_id);
660 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
661 wp_send_json_error(['message' => __('Invalid invoice', 'easy-invoice')]);
662 return;
663 }
664
665 $invoice = new Invoice($post);
666
667 // Get currency settings
668 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
669 $settings = $settings_controller->getSettings();
670 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
671 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
672
673 $payment_data = [
674 'invoice_id' => $invoice_id,
675 'amount' => $amount,
676 'payment_method' => $payment_method,
677 'payment_date' => current_time('mysql'),
678 'notes' => $notes,
679 'status' => 'completed',
680 'payment_type' => 'full',
681 'transaction_id' => $transaction_id,
682 'recurring_id' => '',
683 'parent_payment_id' => '',
684 'currency' => $currency_code,
685 'currency_symbol' => $currency_symbol,
686 'gateway_response' => [
687 'admin_verified' => true,
688 'verification_date' => current_time('mysql'),
689 'verification_user' => get_current_user_id()
690 ]
691 ];
692
693 try {
694 $payment = Payment::create($payment_data);
695
696 // Update invoice status to paid only if total payments are sufficient
697 $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'manual');
698
699 // Send confirmation email to customer
700 $this->sendPaymentConfirmationEmail($invoice_id, $payment->getId());
701
702 wp_send_json_success([
703 'message' => __('Payment verified successfully', 'easy-invoice'),
704 'payment_id' => $payment->getId()
705 ]);
706 } catch (\Exception $e) {
707 wp_send_json_error(['message' => $e->getMessage()]);
708 }
709 }
710
711 /**
712 * Reject manual payment
713 */
714 public function rejectManualPayment(): void {
715 // Check permissions
716 if (!current_user_can('manage_options')) {
717 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
718 return;
719 }
720
721 // Verify nonce
722 check_ajax_referer('easy_invoice_admin', 'nonce');
723
724 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
725 $reason = isset($_POST['reason']) ? sanitize_textarea_field($_POST['reason']) : '';
726
727 if (!$invoice_id) {
728 wp_send_json_error(['message' => __('Invoice ID is required', 'easy-invoice')]);
729 return;
730 }
731
732 // Get the invoice
733 $post = get_post($invoice_id);
734 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
735 wp_send_json_error(['message' => __('Invalid invoice', 'easy-invoice')]);
736 return;
737 }
738
739 $invoice = new Invoice($post);
740
741 // Update invoice status
742 update_post_meta($invoice_id, '_payment_status', 'rejected');
743
744 // Add rejection reason
745 update_post_meta($invoice_id, '_payment_rejection_reason', $reason);
746 update_post_meta($invoice_id, '_payment_rejection_date', current_time('mysql'));
747 update_post_meta($invoice_id, '_payment_rejection_user', get_current_user_id());
748
749 // Send rejection email to customer
750 $this->sendPaymentRejectionEmail($invoice_id, $reason);
751
752 wp_send_json_success([
753 'message' => __('Payment rejected successfully', 'easy-invoice')
754 ]);
755 }
756
757
758
759 /**
760 * Send payment confirmation email to customer
761 *
762 * @param int $invoice_id
763 * @param int $payment_id
764 */
765 private function sendPaymentConfirmationEmail($invoice_id, $payment_id): void {
766 $invoice = new Invoice(get_post($invoice_id));
767 $customer_email = $invoice->getCustomerEmail();
768
769 if (!$customer_email) {
770 return;
771 }
772
773 // Get currency settings
774 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
775 $settings = $settings_controller->getSettings();
776 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
777 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
778
779 $site_name = get_bloginfo('name');
780 $invoice_number = $invoice->getNumber();
781 $amount = $invoice->getTotal();
782 $formatted_amount = $currency_symbol . number_format($amount, 2);
783
784 $subject = sprintf(__('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
785
786 $message = sprintf(
787 __('Dear %s,', 'easy-invoice'),
788 $invoice->getCustomerName()
789 );
790 $message .= "\n\n";
791 $message .= sprintf(
792 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
793 $formatted_amount,
794 $invoice_number
795 );
796 $message .= "\n\n";
797 $message .= __('Thank you for your business.', 'easy-invoice');
798 $message .= "\n\n";
799 $message .= sprintf(__('Regards,', 'easy-invoice'));
800 $message .= "\n";
801 $message .= get_option('easy_invoice_company_name', $site_name);
802
803 wp_mail($customer_email, $subject, $message);
804 }
805
806 /**
807 * Send payment rejection email to customer
808 *
809 * @param int $invoice_id
810 * @param string $reason
811 */
812 private function sendPaymentRejectionEmail($invoice_id, $reason): void {
813 $invoice = new Invoice(get_post($invoice_id));
814 $customer_email = $invoice->getCustomerEmail();
815
816 if (!$customer_email) {
817 return;
818 }
819
820 // Get currency settings
821 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
822 $settings = $settings_controller->getSettings();
823 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
824 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
825
826 $site_name = get_bloginfo('name');
827 $invoice_number = $invoice->getNumber();
828 $amount = $invoice->getTotal();
829 $formatted_amount = $currency_symbol . number_format($amount, 2);
830
831 $subject = sprintf(__('[%s] Payment Issue - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
832
833 $message = sprintf(
834 __('Dear %s,', 'easy-invoice'),
835 $invoice->getCustomerName()
836 );
837 $message .= "\n\n";
838 $message .= sprintf(
839 __('We regret to inform you that we could not process your payment of %s for Invoice #%s.', 'easy-invoice'),
840 $formatted_amount,
841 $invoice_number
842 );
843 $message .= "\n\n";
844
845 if ($reason) {
846 $message .= __('Reason:', 'easy-invoice') . "\n";
847 $message .= $reason;
848 $message .= "\n\n";
849 }
850
851 $message .= __('Please contact us to arrange an alternative payment method.', 'easy-invoice');
852 $message .= "\n\n";
853 $message .= sprintf(__('Regards,', 'easy-invoice'));
854 $message .= "\n";
855 $message .= get_option('easy_invoice_company_name', $site_name);
856
857 wp_mail($customer_email, $subject, $message);
858 }
859
860 /**
861 * Add pending payment statuses to admin filters
862 *
863 * @param array $statuses
864 * @return array
865 */
866 public function addPendingPaymentStatuses($statuses): array {
867 $statuses['pending-bank'] = __('Pending Bank Transfer', 'easy-invoice');
868 $statuses['pending-cheque'] = __('Pending Cheque', 'easy-invoice');
869 return $statuses;
870 }
871
872 /**
873 * Add payment method column to payments list
874 *
875 * @param array $columns
876 * @return array
877 */
878 public function addPaymentMethodColumn($columns): array {
879 $new_columns = [];
880
881 foreach ($columns as $key => $value) {
882 $new_columns[$key] = $value;
883
884 if ($key === 'title') {
885 $new_columns['payment_method'] = __('Payment Method', 'easy-invoice');
886 }
887 }
888
889 return $new_columns;
890 }
891
892 /**
893 * Render payment method column
894 *
895 * @param string $column
896 * @param int $post_id
897 */
898 public function renderPaymentMethodColumn($column, $post_id): void {
899 if ($column === 'payment_method') {
900 $payment_method = get_post_meta($post_id, '_payment_method', true);
901 $payment_methods = [
902 'paypal' => __('PayPal', 'easy-invoice')
903 ];
904
905 echo isset($payment_methods[$payment_method]) ? esc_html($payment_methods[$payment_method]) : esc_html($payment_method);
906 }
907 }
908
909 /**
910 * Send payment reminders for pending manual payments
911 */
912 public function sendPaymentReminders(): void {
913 // Get invoices with pending manual payments
914 $pending_invoices = get_posts([
915 'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
916 'posts_per_page' => -1,
917 'meta_query' => [
918 'relation' => 'AND',
919 [
920 'key' => '_payment_status',
921 'value' => ['pending-bank', 'pending-cheque'],
922 'compare' => 'IN'
923 ],
924 [
925 'key' => '_payment_reminder_sent',
926 'compare' => 'NOT EXISTS'
927 ]
928 ]
929 ]);
930
931 if (!empty($pending_invoices)) {
932 // Get currency settings
933 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
934 $settings = $settings_controller->getSettings();
935 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
936 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
937
938 foreach ($pending_invoices as $post) {
939 $invoice = new Invoice($post);
940 $customer_email = $invoice->getCustomerEmail();
941
942 if (!$customer_email) {
943 continue;
944 }
945
946 $site_name = get_bloginfo('name');
947 $invoice_number = $invoice->getNumber();
948 $amount = $invoice->getTotal();
949 $formatted_amount = $currency_symbol . number_format($amount, 2);
950 $payment_method = get_post_meta($invoice->getId(), '_payment_method', true);
951 $payment_method_label = $payment_method === 'bank' ? __('Bank Transfer', 'easy-invoice') : __('Cheque', 'easy-invoice');
952
953 $subject = sprintf(__('[%s] Payment Reminder - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number);
954
955 $message = sprintf(
956 __('Dear %s,', 'easy-invoice'),
957 $invoice->getCustomerName()
958 );
959 $message .= "\n\n";
960 $message .= sprintf(
961 __('This is a friendly reminder that we are still awaiting your %s payment of %s for Invoice #%s.', 'easy-invoice'),
962 $payment_method_label,
963 $formatted_amount,
964 $invoice_number
965 );
966 $message .= "\n\n";
967 $message .= __('If you have already sent the payment, please disregard this reminder. If not, please arrange for payment at your earliest convenience.', 'easy-invoice');
968 $message .= "\n\n";
969 $message .= sprintf(__('Regards,', 'easy-invoice'));
970 $message .= "\n";
971 $message .= get_option('easy_invoice_company_name', $site_name);
972
973 wp_mail($customer_email, $subject, $message);
974
975 // Mark reminder as sent
976 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql'));
977 }
978
979 wp_reset_postdata();
980 }
981 }
982
983 /**
984 * Handle submission of payment proof for manual gateways (Bank Transfer, Cheque)
985 */
986 public function submitPaymentProof(): void {
987 $gateway_name = isset($_POST['gateway']) ? sanitize_text_field($_POST['gateway']) : '';
988 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
989
990 if (empty($gateway_name) || empty($invoice_id)) {
991 wp_send_json_error(['message' => __('Invalid request. Missing gateway or invoice ID.', 'easy-invoice')]);
992 return;
993 }
994
995 // Nonce verification (make nonce name consistent or check based on gateway)
996 $nonce_action = 'easy_invoice_payment_proof_' . $invoice_id; // Bank transfer nonce
997 $nonce_value = isset($_POST['payment_proof_nonce']) ? sanitize_text_field($_POST['payment_proof_nonce']) : '';
998 if ($gateway_name === 'cheque') {
999 $nonce_action = 'easy_invoice_cheque_notification_' . $invoice_id; // Cheque nonce
1000 $nonce_value = isset($_POST['cheque_notification_nonce']) ? sanitize_text_field($_POST['cheque_notification_nonce']) : '';
1001 }
1002
1003 if (!wp_verify_nonce($nonce_value, $nonce_action)) {
1004 wp_send_json_error(['message' => __('Nonce verification failed. Please try again.', 'easy-invoice')]);
1005 return;
1006 }
1007
1008 // Optional: Add capability check if this can be submitted by logged-in users only from frontend
1009 // if (is_user_logged_in() && !current_user_can('read_invoice', $invoice_id)) { // Example capability
1010 // wp_send_json_error(['message' => __('You do not have permission to submit proof for this invoice.', 'easy-invoice')]);
1011 // return;
1012 // }
1013
1014 $gateway = $this->gatewayManager->getGateway($gateway_name);
1015
1016 if (!$gateway || !method_exists($gateway, 'handleProofSubmission')) {
1017 wp_send_json_error(['message' => __('Invalid payment gateway or submission handler not found.', 'easy-invoice')]);
1018 return;
1019 }
1020
1021 // Prepare data for the gateway handler
1022 $post_data = stripslashes_deep($_POST);
1023 $files_data = $_FILES;
1024
1025 $result = $gateway->handleProofSubmission($post_data, $files_data);
1026
1027 if ($result['success']) {
1028 wp_send_json_success(['message' => $result['message']]);
1029 } else {
1030 wp_send_json_error(['message' => $result['message']]);
1031 }
1032 }
1033
1034 /**
1035 * AJAX handler for admin to mark an invoice as paid.
1036 */
1037 public function mark_invoice_paid_ajax(): void {
1038 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
1039 $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : '';
1040 $notes = isset($_POST['notes']) ? sanitize_textarea_field($_POST['notes']) : '';
1041
1042 if (empty($invoice_id) || !wp_verify_nonce($nonce, 'easy_invoice_approve_payment')) {
1043 easy_invoice_toast_error(__('Invalid request or security check failed.', 'easy-invoice'));
1044 return;
1045 }
1046
1047 // Use manage_options capability which administrators have
1048 if (!current_user_can('manage_options')) {
1049 easy_invoice_toast_error(__('You do not have permission to perform this action.', 'easy-invoice'));
1050 return;
1051 }
1052
1053 $invoice_post = get_post($invoice_id);
1054 if (!$invoice_post || $invoice_post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
1055 wp_send_json_error(['message' => __('Invalid invoice.', 'easy-invoice')]);
1056 return;
1057 }
1058
1059 $invoice = new Invoice($invoice_post);
1060 // For manual approval, always use 'manual' as payment method
1061 $payment_method = 'manual';
1062
1063 // Update invoice post status to 'publish' (or your primary paid status)
1064 wp_update_post(['ID' => $invoice_id, 'post_status' => 'publish']);
1065 update_post_meta($invoice_id, '_payment_status', 'completed'); // General completed status for payments
1066
1067 // Allow plugins to control invoice status update
1068 $should_update_invoice_status = apply_filters('easy_invoice_should_update_invoice_status', true, $invoice_id);
1069 if ($should_update_invoice_status) {
1070 update_post_meta($invoice_id, InvoiceFields::STATUS, 'paid'); // Specific invoice status field if used by model
1071 }
1072
1073 // Use submitted notes or default note
1074 $payment_notes = !empty($notes)
1075 ? $notes
1076 : __('Payment manually verified by admin.', 'easy-invoice');
1077
1078 // Find existing pending payment records for this invoice
1079 $existing_payment_args = [
1080 'post_type' => 'easy_invoice_payment',
1081 'posts_per_page' => 1,
1082 'meta_query' => [
1083 'relation' => 'AND',
1084 [
1085 'key' => '_invoice_id',
1086 'value' => $invoice_id,
1087 ],
1088 [
1089 'key' => '_status',
1090 'value' => ['pending-bank', 'pending-cheque', 'pending'], // Check against pending statuses
1091 'compare' => 'IN'
1092 ]
1093 ]
1094 ];
1095 $existing_payments = get_posts($existing_payment_args);
1096 $payment_id = null;
1097
1098 if (!empty($existing_payments)) {
1099 // Update existing pending payment instead of creating new one
1100 $payment_id = $existing_payments[0]->ID;
1101 update_post_meta($payment_id, '_status', 'completed'); // Update status to completed
1102 update_post_meta($payment_id, '_payment_method', 'manual'); // Set payment method to manual
1103 update_post_meta($payment_id, '_transaction_id', 'MANUAL-' . $invoice_id . '-' . time());
1104 update_post_meta($payment_id, '_payment_date', current_time('mysql'));
1105 update_post_meta($payment_id, '_notes', $payment_notes); // Update notes on existing payment
1106 } else {
1107 // Only create a new payment if no pending payments exist
1108 // This prevents creating duplicate payment records
1109 $existing_payments = get_posts([
1110 'post_type' => 'easy_invoice_payment',
1111 'posts_per_page' => -1,
1112 'meta_query' => [
1113 [
1114 'key' => '_invoice_id',
1115 'value' => $invoice_id,
1116 ]
1117 ]
1118 ]);
1119
1120 if (!empty($existing_payments)) {
1121 // If payments exist but none are pending, don't create a new one
1122 // Just update the invoice status
1123 easy_invoice_toast_success(__('Invoice marked as paid successfully.', 'easy-invoice'));
1124 return;
1125 }
1126
1127 // Get currency from invoice
1128 $currency_code = get_post_meta($invoice_id, '_easy_invoice_currency_code', true);
1129 if (empty($currency_code) || $currency_code === 'global') {
1130 $currency_code = get_option('easy_invoice_currency_code', 'USD');
1131 }
1132 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
1133
1134 $payment_data = [
1135 'invoice_id' => $invoice_id,
1136 'amount' => $invoice->getTotal(), // Or get amount from proof submission if it varies
1137 'payment_method' => $payment_method,
1138 'status' => 'completed',
1139 'transaction_id' => get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id,
1140 'payment_date' => current_time('mysql'),
1141 'notes' => $payment_notes, // Use provided notes
1142 'payment_type' => 'manual',
1143 'currency' => $currency_code,
1144 'currency_symbol' => $currency_symbol,
1145 'gateway_response' => json_encode([
1146 'admin_verified' => true,
1147 'user' => get_current_user_id(),
1148 'verification_date' => current_time('mysql'),
1149 'notes' => $payment_notes // Store notes in response JSON as well
1150 ])
1151 ];
1152 try {
1153 // Create payment record using WordPress post creation
1154 $payment_post_data = [
1155 'post_title' => sprintf('Manual Payment for Invoice #%s', $invoice->getNumber()),
1156 'post_type' => 'easy_invoice_payment',
1157 'post_status' => 'publish',
1158 'post_author' => get_current_user_id(),
1159 'meta_input' => [
1160 '_invoice_id' => $invoice_id,
1161 '_amount' => $invoice->getTotal(),
1162 '_payment_method' => $payment_method,
1163 '_status' => 'completed',
1164 '_transaction_id' => get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id,
1165 '_payment_date' => current_time('mysql'),
1166 '_notes' => $payment_notes,
1167 '_payment_type' => 'manual',
1168 '_currency' => $currency_code,
1169 '_currency_symbol' => $currency_symbol,
1170 '_gateway_response' => json_encode([
1171 'admin_verified' => true,
1172 'user' => get_current_user_id(),
1173 'verification_date' => current_time('mysql'),
1174 'notes' => $payment_notes
1175 ])
1176 ]
1177 ];
1178
1179 $payment_id = wp_insert_post($payment_post_data);
1180 if (is_wp_error($payment_id)) {
1181 easy_invoice_toast_error(__('Error creating payment record:', 'easy-invoice') . ' ' . $payment_id->get_error_message());
1182 return;
1183 }
1184 } catch (\Exception $e) {
1185 easy_invoice_toast_error(__('Error creating payment record:', 'easy-invoice') . ' ' . $e->getMessage());
1186 return;
1187 }
1188 }
1189
1190 // Trigger email confirmation and actions only if we have a payment_id
1191 if ($payment_id) {
1192 $this->sendPaymentConfirmationEmail($invoice_id, $payment_id);
1193 do_action('easy_invoice_manual_payment_confirmed', $invoice_id, $payment_id, $payment_method);
1194 }
1195
1196 easy_invoice_toast_success(__('Invoice marked as paid successfully.', 'easy-invoice'));
1197 }
1198
1199 /**
1200 * Handle bulk actions for payments
1201 */
1202 public function handleBulkActions() {
1203 // Check if we're processing a bulk action
1204 if (!isset($_POST['action']) || $_POST['action'] !== 'easy_invoice_payment_bulk_action') {
1205 return;
1206 }
1207
1208 // Check nonce and capability
1209 if (!wp_verify_nonce($_POST['easy_invoice_payment_bulk_nonce'], 'easy_invoice_payment_bulk_action')) {
1210 wp_die(__('Security check failed.', 'easy-invoice'));
1211 }
1212
1213 if (!current_user_can('manage_options')) {
1214 wp_die(__('You do not have permission to perform this action.', 'easy-invoice'));
1215 }
1216
1217 // Check if we have payment IDs
1218 if (!isset($_POST['payment_ids']) || !is_array($_POST['payment_ids']) || empty($_POST['payment_ids'])) {
1219 wp_redirect(admin_url('admin.php?page=easy-invoice-payments&bulk_error=no_selection'));
1220 exit;
1221 }
1222
1223 // Get bulk action and payment IDs
1224 $bulk_action = isset($_POST['bulk_action']) ? sanitize_text_field($_POST['bulk_action']) : '';
1225 $payment_ids = array_map('intval', $_POST['payment_ids']);
1226
1227 // Process based on action
1228 $processed = 0;
1229 $invoice_updates = array(); // Track invoice updates needed
1230
1231 switch ($bulk_action) {
1232 case 'trash':
1233 foreach ($payment_ids as $id) {
1234 // Get payment info before trashing for invoice status update
1235 $payment = new Payment($id);
1236 $payment_status = $payment->getStatus();
1237 $invoice_id = $payment->getInvoiceId();
1238 $payment_amount = $payment->getAmount();
1239
1240 if (wp_trash_post($id)) {
1241 $processed++;
1242
1243 // Track invoice updates needed for completed payments
1244 if ($payment_status === 'completed' && $invoice_id) {
1245 if (!isset($invoice_updates[$invoice_id])) {
1246 $invoice_updates[$invoice_id] = 0;
1247 }
1248 $invoice_updates[$invoice_id] += $payment_amount;
1249 }
1250 }
1251 }
1252
1253 // Update invoice statuses for completed payments that were trashed
1254 foreach ($invoice_updates as $invoice_id => $deleted_amount) {
1255 $this->updateInvoiceStatusAfterPaymentDeletion($invoice_id, $deleted_amount);
1256 }
1257
1258 wp_redirect(admin_url('admin.php?page=easy-invoice-payments&bulk_trashed=' . $processed));
1259 break;
1260
1261 case 'restore':
1262 foreach ($payment_ids as $id) {
1263 // Get payment info before restoring for invoice status update
1264 $payment = new Payment($id);
1265 $payment_status = $payment->getStatus();
1266 $invoice_id = $payment->getInvoiceId();
1267 $payment_amount = $payment->getAmount();
1268
1269 if (wp_untrash_post($id)) {
1270 // Also set status to publish (since WordPress sets it to draft by default)
1271 wp_update_post(array(
1272 'ID' => $id,
1273 'post_status' => 'publish'
1274 ));
1275 $processed++;
1276
1277 // Track invoice updates needed for completed payments
1278 if ($payment_status === 'completed' && $invoice_id) {
1279 if (!isset($invoice_updates[$invoice_id])) {
1280 $invoice_updates[$invoice_id] = 0;
1281 }
1282 $invoice_updates[$invoice_id] += $payment_amount;
1283 }
1284 }
1285 }
1286
1287 // Update invoice statuses for completed payments that were restored
1288 foreach ($invoice_updates as $invoice_id => $restored_amount) {
1289 $this->updateInvoiceStatusAfterPaymentRestoration($invoice_id, $restored_amount);
1290 }
1291
1292 wp_redirect(admin_url('admin.php?page=easy-invoice-payments&bulk_restored=' . $processed));
1293 break;
1294
1295 case 'delete':
1296 foreach ($payment_ids as $id) {
1297 // Get payment info before deletion for invoice status update
1298 $payment = new Payment($id);
1299 $payment_status = $payment->getStatus();
1300 $invoice_id = $payment->getInvoiceId();
1301 $payment_amount = $payment->getAmount();
1302
1303 if (wp_delete_post($id, true)) {
1304 $processed++;
1305
1306 // Track invoice updates needed for completed payments
1307 if ($payment_status === 'completed' && $invoice_id) {
1308 if (!isset($invoice_updates[$invoice_id])) {
1309 $invoice_updates[$invoice_id] = 0;
1310 }
1311 $invoice_updates[$invoice_id] += $payment_amount;
1312 }
1313 }
1314 }
1315
1316 // Update invoice statuses for completed payments that were deleted
1317 foreach ($invoice_updates as $invoice_id => $deleted_amount) {
1318 $this->updateInvoiceStatusAfterPaymentDeletion($invoice_id, $deleted_amount);
1319 }
1320
1321 wp_redirect(admin_url('admin.php?page=easy-invoice-payments&bulk_deleted=' . $processed));
1322 break;
1323
1324 default:
1325 wp_redirect(admin_url('admin.php?page=easy-invoice-payments&bulk_error=invalid_action'));
1326 }
1327
1328 exit;
1329 }
1330
1331 /**
1332 * Update invoice status after payment deletion
1333 */
1334 private function updateInvoiceStatusAfterPaymentDeletion($invoice_id, $deleted_amount) {
1335 $invoice = new Invoice($invoice_id);
1336
1337 if (!$invoice->getId()) {
1338 return;
1339 }
1340
1341 // Get all remaining payments for this invoice
1342 $remaining_payments = get_posts(array(
1343 'post_type' => 'easy_invoice_payment',
1344 'post_status' => 'publish',
1345 'meta_query' => array(
1346 array(
1347 'key' => '_invoice_id',
1348 'value' => $invoice_id,
1349 'compare' => '='
1350 ),
1351 array(
1352 'key' => '_status',
1353 'value' => 'completed',
1354 'compare' => '='
1355 )
1356 ),
1357 'posts_per_page' => -1
1358 ));
1359
1360 // Calculate total remaining payments
1361 $total_remaining = 0;
1362 foreach ($remaining_payments as $payment_post) {
1363 $payment = new Payment($payment_post);
1364 $total_remaining += floatval($payment->getAmount());
1365 }
1366
1367 $invoice_total = floatval($invoice->getTotal());
1368
1369 // Update invoice status based on remaining payments
1370 if ($total_remaining >= $invoice_total) {
1371 // Still fully paid
1372 update_post_meta($invoice_id, '_status', 'paid');
1373 } elseif ($total_remaining > 0) {
1374 // Partially paid
1375 update_post_meta($invoice_id, '_status', 'partial');
1376 } else {
1377 // No payments remaining
1378 update_post_meta($invoice_id, '_status', 'unpaid');
1379 }
1380 }
1381
1382 /**
1383 * Update invoice status after payment restoration
1384 */
1385 private function updateInvoiceStatusAfterPaymentRestoration($invoice_id, $restored_amount) {
1386 $invoice = new Invoice($invoice_id);
1387
1388 if (!$invoice->getId()) {
1389 return;
1390 }
1391
1392 // Get all payments for this invoice (including the restored one)
1393 $all_payments = get_posts(array(
1394 'post_type' => 'easy_invoice_payment',
1395 'post_status' => 'publish',
1396 'meta_query' => array(
1397 array(
1398 'key' => '_invoice_id',
1399 'value' => $invoice_id,
1400 'compare' => '='
1401 ),
1402 array(
1403 'key' => '_status',
1404 'value' => 'completed',
1405 'compare' => '='
1406 )
1407 ),
1408 'posts_per_page' => -1
1409 ));
1410
1411 // Calculate total payments (including restored ones)
1412 $total_payments = 0;
1413 foreach ($all_payments as $payment_post) {
1414 $payment = new Payment($payment_post);
1415 $total_payments += floatval($payment->getAmount());
1416 }
1417
1418 $invoice_total = floatval($invoice->getTotal());
1419
1420 // Update invoice status based on total payments
1421 if ($total_payments >= $invoice_total) {
1422 // Fully paid
1423 update_post_meta($invoice_id, '_status', 'paid');
1424 } elseif ($total_payments > 0) {
1425 // Partially paid
1426 update_post_meta($invoice_id, '_status', 'partial');
1427 } else {
1428 // No payments
1429 update_post_meta($invoice_id, '_status', 'unpaid');
1430 }
1431 }
1432
1433 // Stripe payment recording moved to Pro plugin
1434
1435
1436 }