Files
alrahma_sunday_school_api/app/Services/Refunds/RefundPayoutService.php
T
2026-06-08 23:30:22 -04:00

179 lines
5.8 KiB
PHP

<?php
namespace App\Services\Refunds;
use App\Models\Invoice;
use App\Models\Refund;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
class RefundPayoutService
{
public function recordPayment(int $refundId, array $payload, int $actorId): array
{
$refund = Refund::query()->find($refundId);
if (! $refund) {
return ['ok' => false, 'message' => 'Refund not found.'];
}
if (! in_array($refund->status, [Refund::STATUS_APPROVED, Refund::STATUS_PARTIAL], true)) {
return ['ok' => false, 'message' => 'Refund must be Approved or Partial to pay.'];
}
$newPaidAmount = (float) ($payload['paid_amount'] ?? 0);
if ($newPaidAmount <= 0) {
return ['ok' => false, 'message' => 'Valid paid amount is required.'];
}
$oldPaid = (float) ($refund->refund_paid_amount ?? 0);
$target = (float) ($refund->refund_amount ?? 0);
$total = $oldPaid + $newPaidAmount;
if ($total > $target + 0.0001) {
return ['ok' => false, 'message' => 'Total paid cannot exceed refund amount.'];
}
$refundMethod = (string) ($payload['payment_method'] ?? '');
$checkNumber = $payload['check_number'] ?? null;
$checkFileName = $refund->check_file;
if ($refundMethod === 'Check' && ($payload['check_file'] ?? null) instanceof UploadedFile) {
$file = $payload['check_file'];
if ($file->isValid()) {
$checkFileName = $file->hashName();
$file->storeAs('checks', $checkFileName);
}
}
$newStatus = ($total < $target) ? Refund::STATUS_PARTIAL : Refund::STATUS_PAID;
return DB::transaction(function () use (
$refund,
$total,
$newStatus,
$refundMethod,
$checkNumber,
$checkFileName,
$actorId
) {
$assignInvoiceId = $refund->invoice_id ?: $this->assignInvoiceForRefund($refund);
$refund->update([
'refund_paid_amount' => $total,
'status' => $newStatus,
'refunded_at' => utc_now(),
'updated_at' => utc_now(),
'updated_by' => $actorId,
'refund_method' => $refundMethod,
'check_nbr' => $checkNumber,
'check_file' => $checkFileName,
'invoice_id' => $assignInvoiceId,
]);
return ['ok' => true];
});
}
private function assignInvoiceForRefund(Refund $refund): ?int
{
$parentId = (int) ($refund->parent_id ?? 0);
$schoolYear = (string) ($refund->school_year ?? '');
if ($parentId <= 0 || $schoolYear === '') {
return null;
}
try {
$invRows = Invoice::query()
->select('id', 'total_amount')
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->get()
->map(fn ($row) => (array) $row)
->all();
if (empty($invRows)) {
return null;
}
$invoiceIds = array_map(static fn ($r) => (int) $r['id'], $invRows);
$paidBy = $this->sumPaymentsByInvoice($invoiceIds);
$discBy = $this->sumDiscountsByInvoice($invoiceIds);
$minRaw = 0.0;
$minId = null;
foreach ($invRows as $row) {
$iid = (int) $row['id'];
$raw = (float) ($row['total_amount'] ?? 0) - (float) ($discBy[$iid] ?? 0) - (float) ($paidBy[$iid] ?? 0);
if ($raw < $minRaw) {
$minRaw = $raw;
$minId = $iid;
}
}
if ($minId !== null) {
return $minId;
}
$latest = Invoice::query()
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->orderByDesc('created_at')
->first();
return $latest ? (int) $latest->id : null;
} catch (\Throwable $e) {
Log::warning('Refund invoice assignment failed.', ['error' => $e->getMessage()]);
return null;
}
}
private function sumPaymentsByInvoice(array $invoiceIds): array
{
$query = DB::table('payments')
->select('invoice_id', DB::raw('COALESCE(SUM(paid_amount),0) AS total_paid'))
->whereIn('invoice_id', $invoiceIds)
->groupBy('invoice_id');
if (Schema::hasColumn('payments', 'status')) {
$query->where(function ($q) {
$q->whereNotIn('status', [
'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
])->orWhereNull('status');
});
}
if (Schema::hasColumn('payments', 'is_void')) {
$query->where(function ($q) {
$q->where('is_void', 0)->orWhereNull('is_void');
});
}
$rows = $query->get();
$paidBy = [];
foreach ($rows as $row) {
$paidBy[(int) $row->invoice_id] = (float) ($row->total_paid ?? 0);
}
return $paidBy;
}
private function sumDiscountsByInvoice(array $invoiceIds): array
{
$rows = DB::table('discount_usages')
->select('invoice_id', DB::raw('COALESCE(SUM(discount_amount),0) AS total_disc'))
->whereIn('invoice_id', $invoiceIds)
->groupBy('invoice_id')
->get();
$discBy = [];
foreach ($rows as $row) {
$discBy[(int) $row->invoice_id] = (float) ($row->total_disc ?? 0);
}
return $discBy;
}
}