@@ -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
|
||||
|
||||
Reference in New Issue
Block a user