urls = $urls ?? app(ApplicationUrlService::class); } public function requestRefund(array $payload, int $actorId): array { $parentId = (int) ($payload['parent_id'] ?? 0); $requestType = strtolower((string) ($payload['request_type'] ?? '')); $amount = (float) ($payload['amount'] ?? 0); $invoiceId = isset($payload['invoice_id']) ? (int) $payload['invoice_id'] : null; $paymentId = isset($payload['payment_id']) ? (int) $payload['payment_id'] : null; $reason = $payload['reason'] ?? null; if (! in_array($requestType, self::REQUEST_TYPES, true)) { return ['ok' => false, 'message' => 'Invalid refund request type.']; } $parent = User::query()->find($parentId); if (! $parent) { return ['ok' => false, 'message' => 'Parent not found.']; } $termSchoolYear = (string) (Configuration::getConfig('school_year') ?? ''); $termSemester = (string) (Configuration::getConfig('semester') ?? ''); $schoolYear = (string) ($payload['school_year'] ?? $termSchoolYear); $semester = (string) ($payload['semester'] ?? $termSemester); if ($requestType === 'tuition') { $result = $this->policyService->processWithdrawalRefund($parentId, $invoiceId, $schoolYear, $semester, $actorId); if (empty($result['ok'])) { return ['ok' => false, 'message' => $result['error'] ?? 'Refund calculation failed.']; } if (($result['refund_amount'] ?? 0) > 0) { $this->notifications->notifyPending($parentId, (float) $result['refund_amount'], $this->urls->webLoginUrl()); } return [ 'ok' => true, 'refund_id' => $result['refund_id'] ?? null, 'refund_amount' => $result['refund_amount'] ?? 0.0, 'status' => $result['status'] ?? null, ]; } if (in_array($requestType, ['overpayment', 'extra'], true)) { $summary = $this->summaryService->getParentFinancialSummary($parentId, $schoolYear, $semester); if (($summary['unapplied_balance'] ?? 0) <= 0) { return ['ok' => false, 'message' => 'No unapplied balance available.']; } if ($amount > (float) ($summary['unapplied_balance'] ?? 0) + 0.0001) { return [ 'ok' => false, 'message' => 'Amount exceeds available unapplied balance.', 'available' => number_format((float) ($summary['unapplied_balance'] ?? 0), 2), ]; } } if ($amount <= 0) { return ['ok' => false, 'message' => 'Refund amount must be greater than zero.']; } return DB::transaction(function () use ( $parentId, $schoolYear, $semester, $invoiceId, $amount, $requestType, $reason, $paymentId, $actorId ) { $refund = Refund::query()->create([ 'parent_id' => $parentId, 'school_year' => $schoolYear, 'semester' => $semester, 'invoice_id' => $invoiceId, 'refund_amount' => $amount, 'refund_paid_amount' => 0.00, 'status' => Refund::STATUS_PENDING, 'reason' => $reason, 'request' => $requestType, 'note' => $paymentId ? ('duplicate-of-payment#'.$paymentId) : null, 'requested_at' => utc_now(), 'created_at' => utc_now(), 'updated_at' => utc_now(), 'updated_by' => $actorId, ]); if (! $refund) { Log::error('Failed to create refund request.', [ 'parent_id' => $parentId, 'type' => $requestType, ]); return ['ok' => false, 'message' => 'Failed to create refund request.']; } $this->notifications->notifyPending($parentId, $amount, $this->urls->webLoginUrl()); return [ 'ok' => true, 'refund_id' => (int) $refund->id, 'refund_amount' => (float) $refund->refund_amount, ]; }); } }