PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.10
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.10
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.10, at includes/Controllers/PaymentController.php

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