From 16b46c1f1a7d69f1805cc7569f6540c8e1a8d526 Mon Sep 17 00:00:00 2001 From: Wrongecho Date: Sun, 30 Aug 2026 22:54:01 +0100 Subject: [PATCH] Stripe Invoice Payments - Add ability to refund Stripe payments rather than just deleting & manually refunding - Limit refunding / deleting payments to Full/3 Sales&Financial permissions, as this is a delete rather than modify action --- agent/invoice.php | 16 +++++-- agent/post/payment.php | 103 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 5 deletions(-) diff --git a/agent/invoice.php b/agent/invoice.php index abb28a43b..4bf4d071b 100644 --- a/agent/invoice.php +++ b/agent/invoice.php @@ -119,7 +119,7 @@ if (isset($_GET['invoice_id'])) { $sql_history = mysqli_query($mysqli, "SELECT history_created_at, history_description, history_status FROM history WHERE history_invoice_id = $invoice_id ORDER BY history_id DESC"); - $sql_payments = mysqli_query($mysqli, "SELECT account_name, payment_amount, payment_currency_code, payment_date, payment_id, + $sql_payments = mysqli_query($mysqli, "SELECT account_name, payment_amount, payment_currency_code, payment_method, payment_date, payment_id, payment_reference FROM payments, accounts WHERE payment_account_id = account_id AND payment_invoice_id = $invoice_id ORDER BY payments.payment_id DESC"); $sql_tickets = mysqli_query($mysqli, " @@ -681,7 +681,9 @@ if (isset($_GET['invoice_id'])) { Amount Reference Account - + = 3 && lookupUserPermission("module_financial") >= 3) { ?> + + @@ -692,6 +694,7 @@ if (isset($_GET['invoice_id'])) { $payment_date = escapeHtml($row['payment_date']); $payment_amount = floatval($row['payment_amount']); $payment_currency_code = escapeHtml($row['payment_currency_code']); + $payment_method = escapeHtml($row['payment_method']); $payment_reference = escapeHtml($row['payment_reference']); $account_name = escapeHtml($row['account_name']); @@ -701,7 +704,14 @@ if (isset($_GET['invoice_id'])) { - + = 3 && lookupUserPermission("module_financial") >= 3) { ?> + + + + + + + pi_[A-Za-z0-9]+)/', $row['payment_reference'], $matches)) { + $stripe_payment_intent_id = $matches['payment_intent']; + } + + // Get invoice details + $sql = mysqli_query($mysqli,"SELECT client_id, client_name, contact_email, contact_extension, contact_mobile, + contact_mobile_country_code, contact_name, contact_phone, contact_phone_country_code, + invoice_amount, invoice_currency_code, invoice_number, invoice_prefix, invoice_status, + invoice_url_key FROM invoices + LEFT JOIN clients ON invoice_client_id = client_id + LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1 + WHERE invoice_id = $invoice_id" + ); + $row = mysqli_fetch_assoc($sql); + $invoice_number = intval($row['invoice_number']); + $invoice_status = escapeSql($row['invoice_status']); + $invoice_amount = floatval($row['invoice_amount']); + $invoice_prefix = escapeSql($row['invoice_prefix']); + $invoice_number = intval($row['invoice_number']); + $invoice_url_key = escapeSql($row['invoice_url_key']); + $invoice_currency_code = escapeSql($row['invoice_currency_code']); + $client_id = intval($row['client_id']); + $client_name = escapeSql($row['client_name']); + $contact_name = escapeSql($row['contact_name']); + $contact_email = escapeSql($row['contact_email']); + $contact_phone = escapeSql(formatPhoneNumber($row['contact_phone'], $row['contact_phone_country_code'])); + $contact_extension = preg_replace("/[^0-9]/", '',$row['contact_extension']); + $contact_mobile = escapeSql(formatPhoneNumber($row['contact_mobile'], $row['contact_mobile_country_code'])); + + $stripe_provider = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT payment_provider_private_key FROM payment_providers WHERE payment_provider_name = 'Stripe' LIMIT 1")); + if (!$stripe_provider) { + exit("Stripe not enabled / configured"); + } + $stripe_secret_key = $stripe_provider['payment_provider_private_key']; + + // Initialize Stripe + try { + require_once __DIR__ . '/../../includes/stripe_init.php'; + $stripe = new \Stripe\StripeClient($stripe_secret_key); + + // Refund + $refund = $stripe->refunds->create([ + 'payment_intent' => $stripe_payment_intent_id + ]); + } catch (Exception $e) { + $error = $e->getMessage(); + error_log("Stripe refund error - encountered exception during refund for invoice ID $invoice_id / $invoice_prefix$invoice_number: $error"); + logApp("Stripe", "error", "Exception during refund for invoice ID $invoice_id: $error"); + flashAlert("Stripe refund failed: $error", 'error'); + redirect(); + } + + mysqli_query($mysqli,"DELETE FROM payments WHERE payment_id = $payment_id"); + + // Recalculate from what is left rather than from the pre-delete total + $invoice_status = updateInvoiceStatusFromPayments($invoice_id); + + mysqli_query($mysqli,"INSERT INTO history SET history_status = '$invoice_status', history_description = 'Payment $stripe_payment_intent_id deleted and refunded', history_invoice_id = $invoice_id"); + + // Log info + $extended_log_desc = ''; + if (!$pi_livemode) { + $extended_log_desc = '(DEV MODE)'; + } + logAudit("Invoice", "Edit", "$session_name refunded and deleted Payment $stripe_payment_intent_id on Invoice $invoice_prefix$invoice_number", $client_id, $invoice_id); + + flashAlert("Payment deleted and refunded in Stripe", 'error'); + + redirect(); + +}