| @@ -37,58 +37,20 @@ | ||
| 37 | 37 | 'pending_payments' => 0, |
| 38 | 38 | 'failed_payments' => 0 |
| 39 | 39 | ], $stats); |
| 40 | 40 | |
| 41 | -// Initialize amounts_by_currency array | |
| 42 | -$amounts_by_currency = []; | |
| 43 | -// Calculate stats from the payments array | |
| 44 | -foreach ($payments as $payment) { | |
| 45 | - $amount = floatval($payment->getAmount()); | |
| 46 | - $status = $payment->getStatus(); | |
| 47 | - | |
| 48 | - $stats['total_amount'] += $amount; | |
| 49 | - | |
| 50 | - switch ($status) { | |
| 51 | - case 'completed': | |
| 52 | - $stats['completed_payments']++; | |
| 53 | - break; | |
| 54 | - case 'pending': | |
| 55 | - $stats['pending_payments']++; | |
| 56 | - break; | |
| 57 | - case 'failed': | |
| 58 | - $stats['failed_payments']++; | |
| 59 | - break; | |
| 60 | - } | |
| 61 | - | |
| 62 | - // Calculate currency breakdown | |
| 63 | - $payment_currency = $payment->getCurrency(); | |
| 64 | - | |
| 65 | - // If currency is empty or "global", get the actual currency that was used | |
| 66 | - if (empty($payment_currency) || $payment_currency === 'global') { | |
| 67 | - $actual_currency = get_post_meta($payment->getId(), '_currency', true); | |
| 68 | - $payment_currency = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD'); | |
| 69 | - } | |
| 70 | - | |
| 71 | - // If currency is still "global", use the global setting | |
| 72 | - if ($payment_currency === 'global') { | |
| 73 | - $payment_currency = get_option('easy_invoice_currency_code', 'USD'); | |
| 41 | +// Site-wide figures come from the controller (one query each); the page's own | |
| 42 | +// twenty rows used to be added on top of them, which inflated "Completed" and | |
| 43 | +// "Awaiting confirmation" and made "Total amount" the page's sum. | |
| 44 | +$amounts_by_currency = $amounts_by_currency ?? []; | |
| 45 | +if (empty($amounts_by_currency) && !empty($stats['total_amount'])) { | |
| 46 | + $ei_code = strtoupper((string) get_option('easy_invoice_currency_code', 'USD')); | |
| 47 | + $amounts_by_currency[$ei_code] = [ | |
| 48 | + 'amount' => (float) $stats['total_amount'], | |
| 49 | + 'symbol' => \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($ei_code), | |
| 50 | + ]; | |
| 74 | 51 | } |
| 75 | 52 | |
| 76 | - $payment_currency_symbol = $payment->getCurrencySymbol() ?: \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($payment_currency); | |
| 77 | - | |
| 78 | - // Normalize currency code to uppercase for consistent grouping | |
| 79 | - $payment_currency = strtoupper($payment_currency); | |
| 80 | - | |
| 81 | - // Group amounts by currency | |
| 82 | - if (!isset($amounts_by_currency[$payment_currency])) { | |
| 83 | - $amounts_by_currency[$payment_currency] = [ | |
| 84 | - 'amount' => 0, | |
| 85 | - 'symbol' => $payment_currency_symbol | |
| 86 | - ]; | |
| 87 | -} | |
| 88 | - $amounts_by_currency[$payment_currency]['amount'] += $amount; | |
| 89 | -} | |
| 90 | - | |
| 91 | 53 | // Check for bulk action notifications |
| 92 | 54 | $notification = false; |
| 93 | 55 | |
| 94 | 56 | if (isset($_GET['bulk_trashed']) && $_GET['bulk_trashed'] > 0) { |
| @@ -519,8 +481,16 @@ | ||
| 519 | 481 | $method_label = __('Manual', 'easy-invoice'); |
| 520 | 482 | } |
| 521 | 483 | |
| 522 | 484 | echo esc_html($method_label); |
| 485 | + $ei_reference = (string) get_post_meta((int) $payment->getId(), \EasyInvoice\Services\OfflinePayments::META_REFERENCE, true); | |
| 486 | + if ('' !== $ei_reference) : | |
| 487 | + /* translators: %s: reference the client gave. */ | |
| 488 | + echo '<span class="block text-xs text-gray-400" title="' . esc_attr__('Reference given by the client', 'easy-invoice') . '">' . esc_html(sprintf(__('Ref: %s', 'easy-invoice'), $ei_reference)) . '</span>'; | |
| 489 | + endif; | |
| 490 | + if (\EasyInvoice\Services\OfflinePayments::hasProof((int) $payment->getId())) : | |
| 491 | + echo '<a class="inline-flex items-center gap-1 mt-1 text-xs font-medium text-indigo-600 hover:text-indigo-900" target="_blank" rel="noopener" href="' . esc_url(\EasyInvoice\Services\OfflinePayments::proofUrl((int) $payment->getId())) . '"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>' . esc_html__('View receipt', 'easy-invoice') . '</a>'; | |
| 492 | + endif; | |
| 523 | 493 | ?> |
| 524 | 494 | </td> |
| 525 | 495 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| 526 | 496 | <?php |
| @@ -533,11 +503,18 @@ | ||
| 533 | 503 | $status_class = 'bg-green-100 text-green-800'; |
| 534 | 504 | $status_text = __('Completed', 'easy-invoice'); |
| 535 | 505 | break; |
| 536 | 506 | case 'pending': |
| 507 | + case 'pending-bank': | |
| 508 | + case 'pending-cheque': | |
| 509 | + case 'pending_verification': | |
| 537 | 510 | $status_class = 'bg-yellow-100 text-yellow-800'; |
| 538 | - $status_text = __('Pending', 'easy-invoice'); | |
| 511 | + $status_text = __('Awaiting confirmation', 'easy-invoice'); | |
| 539 | 512 | break; |
| 513 | + case 'rejected': | |
| 514 | + $status_class = 'bg-gray-200 text-gray-700'; | |
| 515 | + $status_text = __('Rejected', 'easy-invoice'); | |
| 516 | + break; | |
| 540 | 517 | case 'failed': |
| 541 | 518 | $status_class = 'bg-red-100 text-red-800'; |
| 542 | 519 | $status_text = __('Failed', 'easy-invoice'); |
| 543 | 520 | break; |
| @@ -568,10 +545,10 @@ | ||
| 568 | 545 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 569 | 546 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path> |
| 570 | 547 | </svg> |
| 571 | 548 | </a> |
| 572 | - <?php if (in_array($status, ['pending', 'pending-bank', 'pending-cheque'])): ?> | |
| 573 | - <button type="button" class="approve-payment-btn text-green-600 hover:text-green-900" | |
| 549 | + <?php if (in_array($status, \EasyInvoice\Services\OfflinePayments::pendingStatuses(), true)): ?> | |
| 550 | + <button type="button" class="approve-payment-btn text-green-600 hover:text-green-900" title="<?php esc_attr_e('Confirm this payment', 'easy-invoice'); ?>" | |
| 574 | 551 | data-payment-id="<?php echo (int) $payment->getId(); ?>" |
| 575 | 552 | data-invoice-id="<?php echo esc_attr($payment->getInvoiceId()); ?>" |
| 576 | 553 | data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>"> |
| 577 | 554 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| @@ -576,9 +553,18 @@ | ||
| 576 | 553 | data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>"> |
| 577 | 554 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 578 | 555 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> |
| 579 | 556 | </svg> |
| 557 | + <span class="sr-only"><?php esc_html_e('Confirm', 'easy-invoice'); ?></span> | |
| 580 | 558 | </button> |
| 559 | + <button type="button" class="reject-payment-btn text-red-600 hover:text-red-900" title="<?php esc_attr_e('Reject this payment', 'easy-invoice'); ?>" | |
| 560 | + data-payment-id="<?php echo (int) $payment->getId(); ?>" | |
| 561 | + data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>"> | |
| 562 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
| 563 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |
| 564 | + </svg> | |
| 565 | + <span class="sr-only"><?php esc_html_e('Reject', 'easy-invoice'); ?></span> | |
| 566 | + </button> | |
| 581 | 567 | <?php endif; ?> |
| 582 | 568 | |
| 583 | 569 | <?php |
| 584 | 570 | // Hook for additional payment actions (used by Pro plugin for receipts) |
| @@ -702,9 +688,9 @@ | ||
| 702 | 688 | <?php esc_html_e('Approve Payment', 'easy-invoice'); ?> |
| 703 | 689 | </h3> |
| 704 | 690 | <div class="mt-2"> |
| 705 | 691 | <p class="text-sm text-gray-500"> |
| 706 | - <?php esc_html_e('You are about to approve this payment. This will mark the invoice as paid and update the payment records.', 'easy-invoice'); ?> | |
| 692 | + <span data-ei-mode-text="approve"><?php esc_html_e('Confirm that this payment arrived. The payment is marked completed and the invoice becomes Paid when its balance is covered, or Partially paid otherwise.', 'easy-invoice'); ?></span><span data-ei-mode-text="reject" hidden><?php esc_html_e('Reject this submission. The record is kept for the audit trail, nothing counts toward the balance, and the invoice keeps its status.', 'easy-invoice'); ?></span> | |
| 707 | 693 | </p> |
| 708 | 694 | <div class="mt-4"> |
| 709 | 695 | <label for="approval-notes" class="block text-sm font-medium text-gray-700"> |
| 710 | 696 | <?php esc_html_e('Notes (optional)', 'easy-invoice'); ?> |
| @@ -716,9 +702,9 @@ | ||
| 716 | 702 | </div> |
| 717 | 703 | </div> |
| 718 | 704 | <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> |
| 719 | 705 | <button type="button" id="confirm-approve-btn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm"> |
| 720 | - <?php esc_html_e('Approve Payment', 'easy-invoice'); ?> | |
| 706 | + <span data-ei-mode-text="approve"><?php esc_html_e('Confirm payment', 'easy-invoice'); ?></span><span data-ei-mode-text="reject" hidden><?php esc_html_e('Reject payment', 'easy-invoice'); ?></span> | |
| 721 | 707 | </button> |
| 722 | 708 | <button type="button" id="cancel-approve-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> |
| 723 | 709 | <?php esc_html_e('Cancel', 'easy-invoice'); ?> |
| 724 | 710 | </button> |
| @@ -787,13 +773,33 @@ | ||
| 787 | 773 | // Store the data for the current payment |
| 788 | 774 | currentInvoiceId = $(this).data('invoice-id'); |
| 789 | 775 | currentPaymentId = $(this).data('payment-id'); |
| 790 | 776 | currentNonce = $(this).data('nonce'); |
| 777 | + currentMode = 'approve'; | |
| 778 | + setModalMode('approve'); | |
| 791 | 779 | |
| 792 | 780 | // Show the approval modal |
| 793 | 781 | $('#payment-approval-modal').removeClass('hidden'); |
| 794 | 782 | }); |
| 795 | 783 | |
| 784 | + // Reject uses the same dialog with different copy and a different endpoint. | |
| 785 | + var currentMode = 'approve'; | |
| 786 | + function setModalMode(mode) { | |
| 787 | + $('#payment-approval-modal [data-ei-mode-text]').each(function() { | |
| 788 | + $(this).prop('hidden', $(this).data('ei-mode-text') !== mode); | |
| 789 | + }); | |
| 790 | + $('#confirm-approve-btn').toggleClass('bg-red-600 hover:bg-red-700', mode === 'reject').toggleClass('bg-green-600 hover:bg-green-700', mode !== 'reject'); | |
| 791 | + } | |
| 792 | + $('.reject-payment-btn').on('click', function(e) { | |
| 793 | + e.preventDefault(); | |
| 794 | + currentInvoiceId = null; | |
| 795 | + currentPaymentId = $(this).data('payment-id'); | |
| 796 | + currentNonce = $(this).data('nonce'); | |
| 797 | + currentMode = 'reject'; | |
| 798 | + setModalMode('reject'); | |
| 799 | + $('#payment-approval-modal').removeClass('hidden'); | |
| 800 | + }); | |
| 801 | + | |
| 796 | 802 | // Handle Cancel button in modal |
| 797 | 803 | $('#cancel-approve-btn').on('click', function() { |
| 798 | 804 | // Hide the modal and reset the form |
| 799 | 805 | $('#payment-approval-modal').addClass('hidden'); |
| @@ -805,9 +811,9 @@ | ||
| 805 | 811 | // Get the notes from the textarea |
| 806 | 812 | const notes = $('#approval-notes').val(); |
| 807 | 813 | |
| 808 | 814 | // Disable the button to prevent double clicks |
| 809 | - $(this).prop('disabled', true).text('Processing...'); | |
| 815 | + $(this).prop('disabled', true); | |
| 810 | 816 | |
| 811 | 817 | // Make AJAX request to approve the payment |
| 812 | 818 | $.ajax({ |
| 813 | 819 | url: ajaxurl, |
| @@ -812,10 +818,11 @@ | ||
| 812 | 818 | $.ajax({ |
| 813 | 819 | url: ajaxurl, |
| 814 | 820 | type: 'POST', |
| 815 | 821 | data: { |
| 816 | - action: 'easy_invoice_approve_payment', | |
| 822 | + action: currentMode === 'reject' ? 'easy_invoice_reject_payment' : 'easy_invoice_approve_payment', | |
| 817 | 823 | invoice_id: currentInvoiceId, |
| 824 | + payment_id: currentPaymentId, | |
| 818 | 825 | nonce: currentNonce, |
| 819 | 826 | notes: notes |
| 820 | 827 | }, |
| 821 | 828 | success: function(response) { |
| @@ -821,12 +828,13 @@ | ||
| 821 | 828 | success: function(response) { |
| 822 | 829 | // Hide the modal |
| 823 | 830 | $('#payment-approval-modal').addClass('hidden'); |
| 824 | 831 | |
| 832 | + var toast = response && response.data && response.data.toast ? response.data.toast.message : ''; | |
| 825 | 833 | if (response.success) { |
| 826 | 834 | // Show success toast |
| 827 | 835 | if (typeof EasyInvoiceToast !== 'undefined') { |
| 828 | - EasyInvoiceToast.success('Payment approved successfully.'); | |
| 836 | + EasyInvoiceToast.success(toast || (currentMode === 'reject' ? 'Payment rejected.' : 'Payment confirmed.')); | |
| 829 | 837 | } |
| 830 | 838 | |
| 831 | 839 | // Refresh the page after a short delay |
| 832 | 840 | setTimeout(function() { |
| @@ -834,13 +842,13 @@ | ||
| 834 | 842 | }, 1500); |
| 835 | 843 | } else { |
| 836 | 844 | // Show error toast |
| 837 | 845 | if (typeof EasyInvoiceToast !== 'undefined') { |
| 838 | - EasyInvoiceToast.error('Failed to approve payment.'); | |
| 846 | + EasyInvoiceToast.error(toast || 'The payment could not be updated.'); | |
| 839 | 847 | } |
| 840 | 848 | |
| 841 | 849 | // Re-enable the button |
| 842 | - $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment'); | |
| 850 | + $('#confirm-approve-btn').prop('disabled', false); setModalMode(currentMode); | |
| 843 | 851 | } |
| 844 | 852 | }, |
| 845 | 853 | error: function() { |
| 846 | 854 | // Hide the modal |
| @@ -851,9 +859,9 @@ | ||
| 851 | 859 | EasyInvoiceToast.error('Server error occurred. Please try again.'); |
| 852 | 860 | } |
| 853 | 861 | |
| 854 | 862 | // Re-enable the button |
| 855 | - $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment'); | |
| 863 | + $('#confirm-approve-btn').prop('disabled', false); setModalMode(currentMode); | |
| 856 | 864 | } |
| 857 | 865 | }); |
| 858 | 866 | }); |
| 859 | 867 | |