fix payment issues
Tests / PHPUnit (push) Successful in 1m17s

This commit is contained in:
root
2026-07-18 19:12:13 -04:00
parent 4aac9472af
commit 068e739408
8 changed files with 1204 additions and 40 deletions
+47 -21
View File
@@ -995,15 +995,11 @@ class PaymentController extends ResourceController
);
}
// Installment sequence
$paidCount = $this->getSuccessfulPaymentCount($invoiceId);
$installmentSeq = $paidCount + 1;
// Generate a stable transaction id
$transactionId = 'INV-' . $invoiceId . '-' . str_replace('.', '', (string)microtime(true));
// Record payment
$ok = $this->processPayment(
$paymentResult = $this->processPayment(
$invoiceId,
$amount,
$paymentMethod,
@@ -1012,15 +1008,16 @@ class PaymentController extends ResourceController
$paymentDate,
$invYear,
$checkNumber,
$installmentSeq,
(array) $this->invoiceModel->find($invoiceId),
null,
(array) $row,
$currentBalance
);
if (!$ok) {
if ($paymentResult === false) {
$this->db->transRollback();
return redirect()->back()->with('error', 'Failed to record payment.');
}
$installmentSeq = (int) ($paymentResult['installment_seq'] ?? 1);
if (!$this->recordManualPayment(
(string)($row['invoice_number'] ?? $invoiceId),
@@ -1383,34 +1380,57 @@ class PaymentController extends ResourceController
$checkNumber = null,
?int $installmentSeq = null,
?array $invoice = null,
?float $currentBalance = null
?float $currentBalance = null,
?string $idempotencyKey = null
) {
$invoice = $invoice ?? $this->invoiceModel->find($invoiceId);
if (!$invoice) {
return false;
}
$invoiceId = (int) ($invoice['id'] ?? $invoiceId);
$amount = round((float) $amount, 2);
if ($amount <= 0) {
return false;
}
$transactionId = $transactionId ?? 'INV-' . $invoiceId . '-' . time();
$paymentDate = $paymentDate ?? utc_now();
// If sequence wasn't provided (legacy callers), compute it here.
// NOTE: If you call this from inside a transaction (recommended), this will be consistent.
if ($this->paymentModel->where('transaction_id', $transactionId)->first()) {
return false;
}
if ($idempotencyKey !== null && $idempotencyKey !== '') {
$existing = $this->paymentModel->where('idempotency_key', $idempotencyKey)->first();
if ($existing) {
return [
'payment_id' => (int) ($existing['id'] ?? 0),
'installment_seq' => (int) ($existing['installment_seq'] ?? $existing['number_of_installments'] ?? 1),
'duplicate' => true,
];
}
}
// If sequence wasn't provided, compute it inside the caller's invoice lock.
if ($installmentSeq === null || $installmentSeq < 1) {
$exclude = ['void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled'];
$priorCount = $this->paymentModel
$row = $this->db->table('payments')
->select('COALESCE(MAX(installment_seq), 0) + 1 AS next_seq', false)
->where('invoice_id', $invoiceId)
->where('paid_amount >', 0)
->whereNotIn('status', $exclude)
->countAllResults();
$installmentSeq = $priorCount + 1;
->get()
->getRowArray();
$installmentSeq = (int) ($row['next_seq'] ?? 1);
}
$preBalance = $currentBalance ?? $this->getCurrentInvoiceBalance((int) $invoiceId);
$newBalance = max(0.0, round($preBalance - (float) $amount, 2));
if ($amount > $preBalance + 0.00001) {
return false;
}
$newBalance = max(0.0, round($preBalance - $amount, 2));
$paymentData = [
'parent_id' => $invoice['parent_id'],
'parent_id' => (int) $invoice['parent_id'],
'invoice_id' => $invoiceId,
'total_amount' => $invoice['total_amount'],
'paid_amount' => $amount,
@@ -1418,6 +1438,7 @@ class PaymentController extends ResourceController
'number_of_installments' => $installmentSeq, // <-- installment sequence (1,2,3,...)
'installment_seq' => $installmentSeq,
'transaction_id' => $transactionId,
'idempotency_key' => $idempotencyKey,
'payment_method' => strtolower($paymentMethod),
'payment_date' => $paymentDate,
'status' => FinancialStatus::PAYMENT_RECORDED,
@@ -1427,11 +1448,16 @@ class PaymentController extends ResourceController
'school_year' => $schoolYear ?? ($invoice['school_year'] ?? $this->schoolYear),
];
if (!$this->paymentModel->insert($paymentData)) {
$paymentId = $this->paymentModel->insert($paymentData);
if (!$paymentId) {
return false;
}
return true;
return [
'payment_id' => (int) $paymentId,
'installment_seq' => $installmentSeq,
'duplicate' => false,
];
}
private function getSuccessfulPaymentCount(int $invoiceId): int