mirror of
https://github.com/itflow-org/itflow
synced 2026-08-05 07:07:14 +00:00
Backed out of Refunds which still keeps the fix to properly set the invoice status when payment is deleted
This commit is contained in:
@@ -649,25 +649,13 @@ if (isset($_GET['invoice_id'])) {
|
||||
$payment_reference = escapeHtml($row['payment_reference']);
|
||||
$account_name = escapeHtml($row['account_name']);
|
||||
|
||||
// Refunds are negative rows linked back to the payment they reverse
|
||||
$payment_is_refund = !is_null($row['payment_refund_of_id']);
|
||||
$payment_refundable = $payment_is_refund ? 0.00 : round($payment_amount - getPaymentRefundedTotal($payment_id), 2);
|
||||
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $payment_date ?></td>
|
||||
<td class="text-right <?php if ($payment_is_refund) { echo "text-danger"; } ?>"><?= numfmt_format_currency($currency_format, $payment_amount, $payment_currency_code) ?></td>
|
||||
<td>
|
||||
<?php if ($payment_is_refund) { ?><span class="badge badge-danger mr-1">Refund</span><?php } ?>
|
||||
<?= $payment_reference ?>
|
||||
</td>
|
||||
<td class="text-right"><?= numfmt_format_currency($currency_format, $payment_amount, $payment_currency_code) ?></td>
|
||||
<td><?= $payment_reference ?></td>
|
||||
<td><?= $account_name ?></td>
|
||||
<td class="text-center text-nowrap">
|
||||
<?php if ($payment_refundable > 0) { ?>
|
||||
<button type="button" class="btn btn-light text-warning ajax-modal" data-modal-url="modals/payment/payment_refund.php?id=<?= $payment_id ?>" title="Refund"><i class="fa fa-undo"></i></button>
|
||||
<?php } ?>
|
||||
<a class="btn btn-light text-danger confirm-link" href="post.php?delete_payment=<?= $payment_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>" title="Delete"><i class="fa fa-times"></i></a>
|
||||
</td>
|
||||
<td class="text-center"><a class="btn btn-light text-danger confirm-link" href="post.php?delete_payment=<?= $payment_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fa fa-times"></i></a></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once '../../../includes/modal_header.php';
|
||||
|
||||
$payment_id = intval($_GET['id']);
|
||||
|
||||
$sql = mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT * FROM payments
|
||||
LEFT JOIN invoices ON payment_invoice_id = invoice_id
|
||||
LEFT JOIN clients ON invoice_client_id = client_id
|
||||
LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
|
||||
WHERE payment_id = $payment_id
|
||||
LIMIT 1"
|
||||
);
|
||||
|
||||
$row = mysqli_fetch_assoc($sql);
|
||||
|
||||
if (!$row) {
|
||||
exit("Payment not found");
|
||||
}
|
||||
|
||||
$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']);
|
||||
$payment_account_id = intval($row['payment_account_id']);
|
||||
$payment_refund_of_id = $row['payment_refund_of_id'];
|
||||
$invoice_id = intval($row['invoice_id']);
|
||||
$invoice_prefix = escapeHtml($row['invoice_prefix']);
|
||||
$invoice_number = intval($row['invoice_number']);
|
||||
$client_id = intval($row['client_id']);
|
||||
$contact_name = escapeHtml($row['contact_name']);
|
||||
$contact_email = escapeHtml($row['contact_email']);
|
||||
|
||||
enforceClientAccess();
|
||||
|
||||
// A refund row cannot itself be refunded
|
||||
if (!is_null($payment_refund_of_id) || $payment_amount <= 0) {
|
||||
exit("This entry is a refund and cannot be refunded");
|
||||
}
|
||||
|
||||
$refunded_amount = getPaymentRefundedTotal($payment_id);
|
||||
$refundable_amount = round($payment_amount - $refunded_amount, 2);
|
||||
|
||||
// Stripe payments carry the PaymentIntent in their reference - that is what makes
|
||||
// them refundable through the gateway rather than only on paper
|
||||
$stripe_pi_id = getStripePaymentIntentId($row['payment_reference']);
|
||||
$stripe_available = false;
|
||||
|
||||
if ($stripe_pi_id) {
|
||||
$stripe_provider = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM payment_providers WHERE payment_provider_name = 'Stripe' LIMIT 1"));
|
||||
if ($stripe_provider && !empty($stripe_provider['payment_provider_private_key'])) {
|
||||
$stripe_available = true;
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
?>
|
||||
|
||||
<div class="modal-header bg-dark">
|
||||
<h5 class="modal-title"><i class="fa fa-fw fa-undo mr-2"></i><?= "$invoice_prefix$invoice_number" ?>: Refund Payment</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="post.php" method="post" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||
<input type="hidden" name="payment_id" value="<?= $payment_id ?>">
|
||||
<input type="hidden" name="idempotency_key" value="<?= bin2hex(random_bytes(16)) ?>">
|
||||
<div class="modal-body">
|
||||
|
||||
<?php if ($refundable_amount <= 0) { ?>
|
||||
|
||||
<div class="alert alert-warning mb-0">
|
||||
<i class="fas fa-fw fa-exclamation-triangle mr-2"></i>This payment has already been refunded in full.
|
||||
</div>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<div class="callout callout-info">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted d-block">Original Payment</small>
|
||||
<span class="text-bold"><?= numfmt_format_currency($currency_format, $payment_amount, $payment_currency_code) ?></span>
|
||||
on <?= $payment_date ?>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted d-block">Available to Refund</small>
|
||||
<span class="text-bold"><?= numfmt_format_currency($currency_format, $refundable_amount, $payment_currency_code) ?></span>
|
||||
<?php if ($refunded_amount > 0) { ?>
|
||||
<small class="text-muted">(<?= numfmt_format_currency($currency_format, $refunded_amount, $payment_currency_code) ?> already refunded)</small>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($payment_method || $payment_reference) { ?>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted"><?= $payment_method ?><?php if ($payment_reference) { ?> · <?= $payment_reference ?><?php } ?></small>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="col-md">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Refund Date <strong class="text-danger">*</strong></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
|
||||
</div>
|
||||
<input type="date" class="form-control" name="date" max="2999-12-31" value="<?= date("Y-m-d") ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Refund Amount <strong class="text-danger">*</strong></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="amount" value="<?= number_format($refundable_amount, 2, '.', '') ?>" placeholder="0.00" required>
|
||||
</div>
|
||||
<small class="form-text text-muted">Lower this to issue a partial refund.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Refund From Account <strong class="text-danger">*</strong></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
|
||||
</div>
|
||||
<select class="form-control select2" name="account" required>
|
||||
<option value="">- Select an Account -</option>
|
||||
<?php
|
||||
|
||||
$sql = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
|
||||
while ($row = mysqli_fetch_assoc($sql)) {
|
||||
$account_id = intval($row['account_id']);
|
||||
$account_name = escapeHtml($row['account_name']);
|
||||
$account_currency = escapeHtml($row['account_currency_code']);
|
||||
?>
|
||||
<option <?php if ($payment_account_id == $account_id) { echo "selected"; } ?>
|
||||
value="<?= $account_id ?>">
|
||||
<?= $account_name ?>
|
||||
</option>
|
||||
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<small class="form-text text-muted">Defaults to the account the payment was deposited into.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Reason / Reference</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-file-alt"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" name="reference" placeholder="Overpayment, service credit, etc" maxlength="150">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($stripe_available) { ?>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="refundViaStripe" name="refund_stripe" value="1" checked>
|
||||
<label class="custom-control-label" for="refundViaStripe">Refund this amount through Stripe</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
Sends the money back to the card used for <?= escapeHtml($stripe_pi_id) ?>. Leave unchecked to record the refund in ITFlow only.
|
||||
Stripe does not return the processing fee on a refund, so the gateway fee expense is left in place.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<?php } elseif ($stripe_pi_id) { ?>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-fw fa-exclamation-triangle mr-2"></i>This was a Stripe payment, but no Stripe secret key is configured. The refund will be recorded in ITFlow only and must be issued manually in the Stripe dashboard.
|
||||
</div>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<?php if (!empty($config_smtp_provider) && !empty($contact_email)) { ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email Notification</label>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="refundEmailReceipt" name="email_receipt" value="1">
|
||||
<label class="custom-control-label" for="refundEmailReceipt"><?= $contact_email ?></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<?php if ($refundable_amount > 0) { ?>
|
||||
<button type="submit" name="refund_payment" class="btn btn-danger text-bold"><i class="fas fa-undo mr-2"></i>Issue Refund</button>
|
||||
<?php } ?>
|
||||
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
|
||||
require_once '../../../includes/modal_footer.php';
|
||||
@@ -205,200 +205,6 @@ if (isset($_POST['edit_payment'])) {
|
||||
|
||||
}
|
||||
|
||||
if (isset($_POST['refund_payment'])) {
|
||||
|
||||
validateCSRFToken();
|
||||
|
||||
enforceUserPermission('module_sales', 3);
|
||||
enforceUserPermission('module_financial', 3);
|
||||
|
||||
$payment_id = intval($_POST['payment_id']);
|
||||
$date = escapeSql($_POST['date']);
|
||||
$amount = floatval($_POST['amount']);
|
||||
$account = intval($_POST['account']);
|
||||
$reference = escapeSql($_POST['reference']);
|
||||
$refund_stripe = intval($_POST['refund_stripe'] ?? 0);
|
||||
$email_receipt = intval($_POST['email_receipt'] ?? 0);
|
||||
$idempotency_key = preg_replace('/[^a-f0-9]/', '', $_POST['idempotency_key'] ?? '');
|
||||
|
||||
// Payment, invoice and client in one read - payments has no client column, so the
|
||||
// client always comes from the invoice
|
||||
$sql = mysqli_query($mysqli, "SELECT * FROM payments
|
||||
LEFT JOIN invoices ON payment_invoice_id = invoice_id
|
||||
LEFT JOIN clients ON invoice_client_id = client_id
|
||||
LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
|
||||
WHERE payment_id = $payment_id
|
||||
LIMIT 1"
|
||||
);
|
||||
$row = mysqli_fetch_assoc($sql);
|
||||
|
||||
if (!$row) {
|
||||
flashAlert("Payment not found", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
$payment_amount = floatval($row['payment_amount']);
|
||||
$payment_currency_code = escapeSql($row['payment_currency_code']);
|
||||
$payment_method = escapeSql($row['payment_method']);
|
||||
$payment_refund_of_id = $row['payment_refund_of_id'];
|
||||
$original_reference = $row['payment_reference'];
|
||||
$invoice_id = intval($row['invoice_id']);
|
||||
$invoice_prefix = escapeSql($row['invoice_prefix']);
|
||||
$invoice_number = intval($row['invoice_number']);
|
||||
$invoice_url_key = escapeSql($row['invoice_url_key']);
|
||||
$client_id = intval($row['client_id']);
|
||||
$client_name = escapeSql($row['client_name']);
|
||||
$contact_name = escapeSql($row['contact_name']);
|
||||
$contact_email = escapeSql($row['contact_email']);
|
||||
|
||||
enforceClientAccess();
|
||||
|
||||
// Sanity checks - a refund row is not itself refundable
|
||||
if (!is_null($payment_refund_of_id) || $payment_amount <= 0) {
|
||||
flashAlert("That entry is a refund and cannot be refunded", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
$refundable_amount = round($payment_amount - getPaymentRefundedTotal($payment_id), 2);
|
||||
|
||||
// Compare in whole cents so a partial refund cannot be rounded past the cap
|
||||
$amount_cents = (int) round($amount * 100);
|
||||
$refundable_cents = (int) round($refundable_amount * 100);
|
||||
|
||||
if ($amount_cents <= 0) {
|
||||
flashAlert("Refund amount must be greater than zero", 'error');
|
||||
redirect();
|
||||
} elseif ($amount_cents > $refundable_cents) {
|
||||
flashAlert("Refund can not be more than the " . numfmt_format_currency($currency_format, $refundable_amount, $payment_currency_code) . " remaining on this payment", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
$amount = round($amount, 2);
|
||||
$stripe_pi_id = getStripePaymentIntentId($original_reference);
|
||||
$refund_reference = $reference;
|
||||
$stripe_refunded = false;
|
||||
|
||||
// Refund through Stripe when the payment came in through Stripe and the agent asked for it
|
||||
if ($refund_stripe && $stripe_pi_id) {
|
||||
|
||||
$stripe_provider = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM payment_providers WHERE payment_provider_name = 'Stripe' LIMIT 1"));
|
||||
$provider_private_key = $stripe_provider['payment_provider_private_key'] ?? '';
|
||||
|
||||
if (empty($provider_private_key)) {
|
||||
flashAlert("Stripe is not configured - refund not issued", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../includes/stripe_init.php';
|
||||
$stripe = new \Stripe\StripeClient($provider_private_key);
|
||||
|
||||
try {
|
||||
// The idempotency key is minted once per rendered modal, so a double submit
|
||||
// of the same form returns the original refund instead of sending the money twice
|
||||
$stripe_refund = $stripe->refunds->create(
|
||||
[
|
||||
'payment_intent' => $stripe_pi_id,
|
||||
'amount' => intval(round($amount * 100)), // Stripe expects cents
|
||||
'metadata' => [
|
||||
'itflow_client_id' => $client_id,
|
||||
'itflow_invoice_id' => $invoice_id,
|
||||
'itflow_invoice_number' => $invoice_prefix . $invoice_number,
|
||||
'itflow_payment_id' => $payment_id,
|
||||
]
|
||||
],
|
||||
$idempotency_key ? ['idempotency_key' => "itflow_refund_$idempotency_key"] : []
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
error_log("Stripe refund error - payment ID $payment_id / $stripe_pi_id on invoice $invoice_prefix$invoice_number: $error");
|
||||
logApp("Stripe", "error", "Refund failed for payment ID $payment_id ($stripe_pi_id): $error");
|
||||
flashAlert("Stripe refund failed: " . escapeHtml($error) . " - nothing was recorded", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
if ($stripe_refund->status === 'failed' || $stripe_refund->status === 'canceled') {
|
||||
logApp("Stripe", "error", "Refund for payment ID $payment_id ($stripe_pi_id) came back as {$stripe_refund->status}");
|
||||
flashAlert("Stripe reported the refund as {$stripe_refund->status} - nothing was recorded", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
$stripe_refund_id = escapeSql($stripe_refund->id);
|
||||
$refund_reference = "Stripe Refund - $stripe_refund_id";
|
||||
$stripe_refunded = true;
|
||||
|
||||
// An idempotent replay hands back the same refund object - do not book it twice
|
||||
$sql_existing = mysqli_query($mysqli, "SELECT payment_id FROM payments WHERE payment_reference = '$refund_reference' LIMIT 1");
|
||||
if (mysqli_num_rows($sql_existing) > 0) {
|
||||
flashAlert("That refund has already been recorded", 'error');
|
||||
redirect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Refunds are stored as a negative payment - every SUM(payment_amount) in the app
|
||||
// then reports the right invoice balance and account balance with no other changes
|
||||
$refund_amount_signed = -1 * $amount;
|
||||
|
||||
mysqli_query($mysqli, "INSERT INTO payments SET
|
||||
payment_date = '$date',
|
||||
payment_amount = $refund_amount_signed,
|
||||
payment_currency_code = '$payment_currency_code',
|
||||
payment_account_id = $account,
|
||||
payment_method = '$payment_method',
|
||||
payment_reference = '$refund_reference',
|
||||
payment_invoice_id = $invoice_id,
|
||||
payment_refund_of_id = $payment_id"
|
||||
);
|
||||
|
||||
$invoice_status = updateInvoiceStatusFromPayments($invoice_id);
|
||||
|
||||
$refund_type = ($amount_cents === $refundable_cents && $refundable_cents === (int) round($payment_amount * 100)) ? 'Refund' : 'Partial refund';
|
||||
$refund_channel = $stripe_refunded ? ' via Stripe' : '';
|
||||
|
||||
mysqli_query($mysqli, "INSERT INTO history SET history_status = '$invoice_status', history_description = '$refund_type issued$refund_channel', history_invoice_id = $invoice_id");
|
||||
|
||||
logAudit("Invoice", "Refund", "$refund_type of " . numfmt_format_currency($currency_format, $amount, $payment_currency_code) . " issued$refund_channel against payment ID $payment_id on invoice $invoice_prefix$invoice_number", $client_id, $invoice_id);
|
||||
|
||||
// Email the client a refund notification
|
||||
if ($email_receipt == 1 && !empty($config_smtp_provider) && !empty($contact_email)) {
|
||||
|
||||
$sql = mysqli_query($mysqli, "SELECT * FROM companies WHERE company_id = 1");
|
||||
$row = mysqli_fetch_assoc($sql);
|
||||
$company_name = escapeSql($row['company_name']);
|
||||
$company_phone = escapeSql(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code']));
|
||||
|
||||
$config_invoice_from_name = escapeSql($config_invoice_from_name);
|
||||
$config_invoice_from_email = escapeSql($config_invoice_from_email);
|
||||
|
||||
$subject = "Refund Issued - Invoice $invoice_prefix$invoice_number";
|
||||
$body = "Hello $contact_name,<br><br>A refund of " . numfmt_format_currency($currency_format, $amount, $payment_currency_code) . " has been issued against invoice <a href=\\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\\'>$invoice_prefix$invoice_number</a>.<br><br>Refund Amount: " . numfmt_format_currency($currency_format, $amount, $payment_currency_code) . "<br>Original Payment Method: $payment_method<br><br>Card refunds usually appear on your statement within 5-10 business days.<br><br><br>--<br>$company_name - Billing Department<br>$config_invoice_from_email<br>$company_phone";
|
||||
|
||||
$email_data = [
|
||||
[
|
||||
'from' => $config_invoice_from_email,
|
||||
'from_name' => $config_invoice_from_name,
|
||||
'recipient' => $contact_email,
|
||||
'recipient_name' => $contact_name,
|
||||
'subject' => $subject,
|
||||
'body' => $body
|
||||
]
|
||||
];
|
||||
|
||||
addToMailQueue($email_data);
|
||||
|
||||
$email_id = mysqli_insert_id($mysqli);
|
||||
|
||||
mysqli_query($mysqli, "INSERT INTO history SET history_status = '$invoice_status', history_description = 'Refund notification sent to mail queue ID: $email_id!', history_invoice_id = $invoice_id");
|
||||
logAudit("Invoice", "Refund", "Refund notification for invoice $invoice_prefix$invoice_number queued to $contact_email Email ID: $email_id", $client_id, $invoice_id);
|
||||
|
||||
}
|
||||
|
||||
flashAlert("$refund_type of <strong>" . numfmt_format_currency($currency_format, $amount, $payment_currency_code) . "</strong> issued$refund_channel");
|
||||
|
||||
redirect();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Apply Credit Not ready for use 2025-08-27 - JQ
|
||||
|
||||
@@ -852,34 +658,24 @@ if (isset($_GET['delete_payment'])) {
|
||||
);
|
||||
$row = mysqli_fetch_assoc($sql);
|
||||
$invoice_id = intval($row['payment_invoice_id']);
|
||||
$payment_is_refund = !is_null($row['payment_refund_of_id']);
|
||||
$invoice_prefix = escapeSql($row['invoice_prefix']);
|
||||
$invoice_number = intval($row['invoice_number']);
|
||||
$client_id = intval($row['invoice_client_id']);
|
||||
|
||||
enforceClientAccess();
|
||||
|
||||
// Deleting a payment that has been refunded would leave the refund rows dangling
|
||||
if (!$payment_is_refund && getPaymentRefundedTotal($payment_id) > 0) {
|
||||
flashAlert("This payment has been refunded - delete the refund first", '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);
|
||||
|
||||
$deleted_description = $payment_is_refund ? 'Refund deleted' : 'Payment deleted';
|
||||
mysqli_query($mysqli,"INSERT INTO history SET history_status = '$invoice_status', history_description = 'Payment deleted', history_invoice_id = $invoice_id");
|
||||
|
||||
mysqli_query($mysqli,"INSERT INTO history SET history_status = '$invoice_status', history_description = '$deleted_description', history_invoice_id = $invoice_id");
|
||||
logAudit("Invoice", "Edit", "$session_name deleted Payment on Invoice $invoice_prefix$invoice_number", $client_id, $invoice_id);
|
||||
|
||||
logAudit("Invoice", "Edit", "$session_name deleted $deleted_description on Invoice $invoice_prefix$invoice_number", $client_id, $invoice_id);
|
||||
|
||||
if (!$payment_is_refund && $config_stripe_enable) {
|
||||
flashAlert("Payment deleted - deleting a payment does not refund it. Use Refund to send the money back through Stripe", 'error');
|
||||
} else {
|
||||
flashAlert("$deleted_description", 'error');
|
||||
flashAlert("Payment deleted", 'error');
|
||||
if ($config_stripe_enable) {
|
||||
flashAlert("Payment deleted - Stripe payments must be manually refunded in Stripe", 'error');
|
||||
}
|
||||
|
||||
redirect();
|
||||
|
||||
Reference in New Issue
Block a user