Fix Pint formatting
This commit is contained in:
@@ -10,8 +10,7 @@ class PaymentEnrollmentEventService
|
||||
{
|
||||
public function __construct(
|
||||
private ApplicationUrlService $urls,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function buildEventData(
|
||||
int $parentId,
|
||||
@@ -29,7 +28,7 @@ class PaymentEnrollmentEventService
|
||||
->where('id', $parentId)
|
||||
->first();
|
||||
|
||||
if (!$parent) {
|
||||
if (! $parent) {
|
||||
throw new \RuntimeException("Parent user #{$parentId} not found");
|
||||
}
|
||||
|
||||
@@ -104,7 +103,7 @@ class PaymentEnrollmentEventService
|
||||
}
|
||||
$seen[$sid] = true;
|
||||
|
||||
$teacherFull = trim(trim((string) ($row['t_firstname'] ?? '')) . ' ' . trim((string) ($row['t_lastname'] ?? '')));
|
||||
$teacherFull = trim(trim((string) ($row['t_firstname'] ?? '')).' '.trim((string) ($row['t_lastname'] ?? '')));
|
||||
$teacherFull = $teacherFull !== '' ? $teacherFull : null;
|
||||
|
||||
$students[] = [
|
||||
@@ -112,7 +111,7 @@ class PaymentEnrollmentEventService
|
||||
'student_id' => $sid,
|
||||
'firstname' => (string) ($row['s_firstname'] ?? ''),
|
||||
'lastname' => (string) ($row['s_lastname'] ?? ''),
|
||||
'name' => trim(((string) ($row['s_firstname'] ?? '')) . ' ' . ((string) ($row['s_lastname'] ?? ''))),
|
||||
'name' => trim(((string) ($row['s_firstname'] ?? '')).' '.((string) ($row['s_lastname'] ?? ''))),
|
||||
'registration_grade' => (string) ($row['registration_grade'] ?? ''),
|
||||
'class_section_id' => isset($row['class_section_id']) ? (int) $row['class_section_id'] : null,
|
||||
'class_section_name' => $row['class_section_name'] ?? null,
|
||||
|
||||
@@ -11,9 +11,7 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PaymentEventChargesService
|
||||
{
|
||||
public function __construct(private ChargeService $chargeService)
|
||||
{
|
||||
}
|
||||
public function __construct(private ChargeService $chargeService) {}
|
||||
|
||||
public function listCharges(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
@@ -56,7 +54,7 @@ class PaymentEventChargesService
|
||||
|
||||
$count = 0;
|
||||
|
||||
if (!empty($studentIds)) {
|
||||
if (! empty($studentIds)) {
|
||||
foreach ($studentIds as $studentId) {
|
||||
$result = $this->chargeService->createEventCharge([
|
||||
'parent_id' => $parentId,
|
||||
@@ -73,7 +71,7 @@ class PaymentEventChargesService
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($payload['student_id'])) {
|
||||
} elseif (! empty($payload['student_id'])) {
|
||||
$result = $this->chargeService->createEventCharge([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => (int) $payload['student_id'],
|
||||
|
||||
@@ -17,8 +17,7 @@ class PaymentManualService
|
||||
public function __construct(
|
||||
private DiscountInvoiceService $invoiceService,
|
||||
private PaymentEnrollmentEventService $enrollmentEventService
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function search(string $searchTerm, ?string $schoolYear = null): array
|
||||
{
|
||||
@@ -48,9 +47,9 @@ class PaymentManualService
|
||||
$builder->where(function ($q) use ($searchTerm, $searchClean, $searchFormatted) {
|
||||
$q->where('email', $searchTerm);
|
||||
|
||||
if (!empty($searchClean)) {
|
||||
if (! empty($searchClean)) {
|
||||
if (strlen($searchClean) === 10) {
|
||||
$formattedPhone = substr($searchClean, 0, 3) . '-' . substr($searchClean, 3, 3) . '-' . substr($searchClean, 6, 4);
|
||||
$formattedPhone = substr($searchClean, 0, 3).'-'.substr($searchClean, 3, 3).'-'.substr($searchClean, 6, 4);
|
||||
$q->orWhere('cellphone', $formattedPhone);
|
||||
}
|
||||
|
||||
@@ -60,13 +59,13 @@ class PaymentManualService
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($searchFormatted)) {
|
||||
if (! empty($searchFormatted)) {
|
||||
$q->orWhere('cellphone', $searchFormatted);
|
||||
}
|
||||
});
|
||||
|
||||
$parent = (array) $builder->first();
|
||||
if (!empty($parent)) {
|
||||
if (! empty($parent)) {
|
||||
unset($parent['password']);
|
||||
$parentData = $parent;
|
||||
$parentId = (int) ($parent['id'] ?? 0);
|
||||
@@ -127,23 +126,23 @@ class PaymentManualService
|
||||
OR email LIKE ?
|
||||
OR cellphone LIKE ?";
|
||||
|
||||
$params = ['%' . $q . '%', '%' . $q . '%', '%' . $q . '%'];
|
||||
$params = ['%'.$q.'%', '%'.$q.'%', '%'.$q.'%'];
|
||||
|
||||
if ($digits !== '') {
|
||||
$sql .= " OR REPLACE(REPLACE(REPLACE(REPLACE(cellphone, '(', ''), ')', ''), '-', ''), ' ', '') LIKE ?";
|
||||
$params[] = '%' . $digits . '%';
|
||||
$params[] = '%'.$digits.'%';
|
||||
}
|
||||
|
||||
$sql .= ") ORDER BY lastname, firstname LIMIT 20";
|
||||
$sql .= ') ORDER BY lastname, firstname LIMIT 20';
|
||||
|
||||
$rows = DB::select($sql, $params);
|
||||
|
||||
$items = [];
|
||||
foreach ($rows as $row) {
|
||||
$label = trim(($row->firstname ?? '') . ' ' . ($row->lastname ?? ''));
|
||||
$label = trim(($row->firstname ?? '').' '.($row->lastname ?? ''));
|
||||
$email = trim((string) ($row->email ?? ''));
|
||||
$phone = trim((string) ($row->cellphone ?? ''));
|
||||
$sub = trim($email . ' ' . $phone);
|
||||
$sub = trim($email.' '.$phone);
|
||||
$value = $email !== '' ? $email : ($phone !== '' ? $phone : $label);
|
||||
|
||||
$items[] = [
|
||||
@@ -172,7 +171,7 @@ class PaymentManualService
|
||||
$paymentMethod = strtolower(trim($paymentMethod));
|
||||
$allowedMethods = ['cash', 'check', 'card'];
|
||||
|
||||
if (!in_array($paymentMethod, $allowedMethods, true)) {
|
||||
if (! in_array($paymentMethod, $allowedMethods, true)) {
|
||||
throw new \InvalidArgumentException('Invalid payment method.');
|
||||
}
|
||||
|
||||
@@ -185,7 +184,7 @@ class PaymentManualService
|
||||
}
|
||||
|
||||
$invoice = Invoice::query()->find($invoiceId);
|
||||
if (!$invoice) {
|
||||
if (! $invoice) {
|
||||
throw new \RuntimeException('Invoice not found.');
|
||||
}
|
||||
|
||||
@@ -209,7 +208,7 @@ class PaymentManualService
|
||||
|
||||
try {
|
||||
$locked = DB::table('invoices')->where('id', $invoiceId)->lockForUpdate()->first();
|
||||
if (!$locked) {
|
||||
if (! $locked) {
|
||||
DB::rollBack();
|
||||
throw new \RuntimeException('Invoice not found.');
|
||||
}
|
||||
@@ -230,7 +229,7 @@ class PaymentManualService
|
||||
$paidCount = $this->getSuccessfulPaymentCount($invoiceId);
|
||||
$installmentSeq = $paidCount + 1;
|
||||
|
||||
$transactionId = 'INV-' . $invoiceId . '-' . str_replace('.', '', (string) microtime(true));
|
||||
$transactionId = 'INV-'.$invoiceId.'-'.str_replace('.', '', (string) microtime(true));
|
||||
|
||||
$ok = $this->processPayment(
|
||||
$invoiceId,
|
||||
@@ -246,7 +245,7 @@ class PaymentManualService
|
||||
$userId
|
||||
);
|
||||
|
||||
if (!$ok) {
|
||||
if (! $ok) {
|
||||
DB::rollBack();
|
||||
throw new \RuntimeException('Failed to record payment.');
|
||||
}
|
||||
@@ -308,7 +307,7 @@ class PaymentManualService
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
Log::error('[manualPayUpdate] ' . $e->getMessage());
|
||||
Log::error('[manualPayUpdate] '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -328,12 +327,12 @@ class PaymentManualService
|
||||
}
|
||||
|
||||
$payment = Payment::query()->find($paymentId);
|
||||
if (!$payment) {
|
||||
if (! $payment) {
|
||||
throw new \RuntimeException('Original payment not found.');
|
||||
}
|
||||
|
||||
$invoice = Invoice::query()->find((int) $payment->invoice_id);
|
||||
if (!$invoice) {
|
||||
if (! $invoice) {
|
||||
throw new \RuntimeException('Linked invoice not found.');
|
||||
}
|
||||
|
||||
@@ -367,10 +366,10 @@ class PaymentManualService
|
||||
{
|
||||
$filename = basename($filename);
|
||||
$roots = [
|
||||
storage_path('app/uploads/checks/' . $filename),
|
||||
storage_path('app/uploads/cards/' . $filename),
|
||||
storage_path('app/uploads/misc/' . $filename),
|
||||
storage_path('app/uploads/' . $filename),
|
||||
storage_path('app/uploads/checks/'.$filename),
|
||||
storage_path('app/uploads/cards/'.$filename),
|
||||
storage_path('app/uploads/misc/'.$filename),
|
||||
storage_path('app/uploads/'.$filename),
|
||||
];
|
||||
|
||||
$path = null;
|
||||
@@ -381,14 +380,14 @@ class PaymentManualService
|
||||
}
|
||||
}
|
||||
|
||||
if (!$path) {
|
||||
if (! $path) {
|
||||
abort(404, 'Payment file not found.');
|
||||
}
|
||||
|
||||
if ($mode === 'inline') {
|
||||
return response()->file($path, [
|
||||
'Content-Type' => mime_content_type($path) ?: 'application/octet-stream',
|
||||
'Content-Disposition' => 'inline; filename="' . $filename . '"',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -409,7 +408,7 @@ class PaymentManualService
|
||||
?int $userId
|
||||
): bool {
|
||||
$invoice = Invoice::query()->find($invoiceId);
|
||||
if (!$invoice) {
|
||||
if (! $invoice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -433,7 +432,7 @@ class PaymentManualService
|
||||
'updated_by' => $userId,
|
||||
];
|
||||
|
||||
if (!Invoice::query()->whereKey($invoiceId)->update($invoiceUpdateData)) {
|
||||
if (! Invoice::query()->whereKey($invoiceId)->update($invoiceUpdateData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -466,7 +465,7 @@ class PaymentManualService
|
||||
private function getCurrentInvoiceBalanceExcludingPayment(int $invoiceId, int $excludePaymentId): float
|
||||
{
|
||||
$invoice = Invoice::query()->find($invoiceId);
|
||||
if (!$invoice) {
|
||||
if (! $invoice) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -530,7 +529,7 @@ class PaymentManualService
|
||||
$discountByInvoice = [];
|
||||
$refundByInvoice = [];
|
||||
|
||||
if (!empty($invoiceIds)) {
|
||||
if (! empty($invoiceIds)) {
|
||||
$rows = Payment::query()
|
||||
->selectRaw('invoice_id, COALESCE(SUM(paid_amount),0) AS total_paid')
|
||||
->whereIn('invoice_id', $invoiceIds)
|
||||
@@ -581,7 +580,7 @@ class PaymentManualService
|
||||
|
||||
$issueYmd = '';
|
||||
foreach (['issue_date', 'start_date', 'created_at'] as $k) {
|
||||
if (!empty($inv[$k])) {
|
||||
if (! empty($inv[$k])) {
|
||||
$its = strtotime((string) $inv[$k]);
|
||||
if ($its !== false) {
|
||||
$issueYmd = date('Y-m-d', $its);
|
||||
@@ -613,7 +612,7 @@ class PaymentManualService
|
||||
$okTypes = ['image/jpeg', 'image/png', 'application/pdf'];
|
||||
$okExts = ['jpg', 'jpeg', 'png', 'pdf'];
|
||||
|
||||
if (!in_array($mime, $okTypes, true) || !in_array($ext, $okExts, true)) {
|
||||
if (! in_array($mime, $okTypes, true) || ! in_array($ext, $okExts, true)) {
|
||||
throw new \InvalidArgumentException('Unsupported file type. Use JPG, PNG, or PDF.');
|
||||
}
|
||||
|
||||
@@ -623,9 +622,9 @@ class PaymentManualService
|
||||
|
||||
$fileName = $file->hashName();
|
||||
$subdir = $paymentMethod === 'check' ? 'checks' : ($paymentMethod === 'card' ? 'cards' : 'misc');
|
||||
$targetDir = storage_path('app/uploads/' . $subdir);
|
||||
$targetDir = storage_path('app/uploads/'.$subdir);
|
||||
|
||||
if (!is_dir($targetDir)) {
|
||||
if (! is_dir($targetDir)) {
|
||||
mkdir($targetDir, 0775, true);
|
||||
}
|
||||
|
||||
@@ -641,7 +640,7 @@ class PaymentManualService
|
||||
event($name, $payload);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning($name . ' hook failed: ' . $e->getMessage());
|
||||
Log::warning($name.' hook failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ class PaymentMissedCheckService
|
||||
public function __construct(
|
||||
private UserNotificationDispatchService $notifier,
|
||||
private EmailService $emailService
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function findUsersWithMissedPayments(): array
|
||||
{
|
||||
@@ -46,7 +45,7 @@ class PaymentMissedCheckService
|
||||
foreach ($users as $user) {
|
||||
$userId = (int) ($user['user_id'] ?? 0);
|
||||
$email = (string) ($user['email'] ?? '');
|
||||
$name = trim((string) ($user['firstname'] ?? '') . ' ' . (string) ($user['lastname'] ?? ''));
|
||||
$name = trim((string) ($user['firstname'] ?? '').' '.(string) ($user['lastname'] ?? ''));
|
||||
|
||||
$this->notifier->notifyUser(
|
||||
$userId,
|
||||
@@ -59,8 +58,8 @@ class PaymentMissedCheckService
|
||||
|
||||
if ($email !== '') {
|
||||
$subject = 'Payment Missed';
|
||||
$body = '<p>Dear ' . e($name !== '' ? $name : 'Parent') . ',</p>'
|
||||
. '<p>You have a missed payment. Please pay to avoid penalty.</p>';
|
||||
$body = '<p>Dear '.e($name !== '' ? $name : 'Parent').',</p>'
|
||||
.'<p>You have a missed payment. Please pay to avoid penalty.</p>';
|
||||
$ok = $this->emailService->send($email, $subject, $body, 'finance');
|
||||
$ok ? $sent++ : $failed++;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Services\Payments;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Notification;
|
||||
use App\Models\UserNotification;
|
||||
use App\Models\Configuration;
|
||||
|
||||
class PaymentNotificationDispatchService
|
||||
{
|
||||
|
||||
@@ -15,20 +15,19 @@ class PaymentNotificationService
|
||||
public function __construct(
|
||||
private EmailService $emailService,
|
||||
private PaymentNotificationDispatchService $dispatchService
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function listLogs(?string $from, ?string $to, ?string $type): array
|
||||
{
|
||||
$query = PaymentNotificationLog::query()->with('parent')->orderByDesc('sent_at');
|
||||
|
||||
if (!empty($from)) {
|
||||
if (! empty($from)) {
|
||||
$query->where('sent_at', '>=', $from);
|
||||
}
|
||||
if (!empty($to)) {
|
||||
if (! empty($to)) {
|
||||
$query->where('sent_at', '<=', $to);
|
||||
}
|
||||
if (!empty($type)) {
|
||||
if (! empty($type)) {
|
||||
$query->where('type', $type);
|
||||
}
|
||||
|
||||
@@ -85,15 +84,17 @@ class PaymentNotificationService
|
||||
|
||||
$type = $this->resolveType($parentId, $schoolYear, $typeInput);
|
||||
|
||||
if (!$force && PaymentNotificationLog::existsForPeriod($parentId, $year, $month, $type)) {
|
||||
if (! $force && PaymentNotificationLog::existsForPeriod($parentId, $year, $month, $type)) {
|
||||
$results['skipped']++;
|
||||
$results['details'][] = ['parent_id' => $parentId, 'result' => 'already_sent'];
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
if ($balance <= 0 && !$force) {
|
||||
if ($balance <= 0 && ! $force) {
|
||||
$results['skipped']++;
|
||||
$results['details'][] = ['parent_id' => $parentId, 'result' => 'no_balance'];
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
@@ -228,13 +229,13 @@ class PaymentNotificationService
|
||||
private function composeMessage(int $parentId, float $balance, string $schoolYear, \DateTime $now): array
|
||||
{
|
||||
$user = User::query()->find($parentId);
|
||||
$parentName = $user ? trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')) : '';
|
||||
$parentName = $user ? trim(($user->firstname ?? '').' '.($user->lastname ?? '')) : '';
|
||||
$greeting = $parentName !== '' ? "Dear {$parentName}," : 'Dear Parent,';
|
||||
|
||||
$monthYear = $now->format('F Y');
|
||||
$subject = sprintf('Monthly Tuition Reminder — %s', $schoolYear);
|
||||
$balanceFmt = '$' . number_format($balance, 2);
|
||||
$intro = 'This is your monthly installment reminder for ' . $schoolYear . '.';
|
||||
$balanceFmt = '$'.number_format($balance, 2);
|
||||
$intro = 'This is your monthly installment reminder for '.$schoolYear.'.';
|
||||
|
||||
$installmentInfo = $this->computeInstallmentSuggestion($balance);
|
||||
|
||||
@@ -298,14 +299,14 @@ class PaymentNotificationService
|
||||
'remaining_installments' => $remainingInstallments,
|
||||
'max_installments' => $maxInstallments,
|
||||
'installment_due' => round($installmentDue, 2),
|
||||
'installment_due_fmt' => '$' . number_format($installmentDue, 2),
|
||||
'installment_due_fmt' => '$'.number_format($installmentDue, 2),
|
||||
];
|
||||
}
|
||||
|
||||
private function getSecondaryGuardianEmail(int $primaryUserId): ?string
|
||||
{
|
||||
$row = DB::table('family_guardians')->where('user_id', $primaryUserId)->first();
|
||||
if (!$row || empty($row->family_id)) {
|
||||
if (! $row || empty($row->family_id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -330,9 +331,10 @@ class PaymentNotificationService
|
||||
private function getUserEmail(int $userId): ?string
|
||||
{
|
||||
$email = (string) (DB::table('users')->where('id', $userId)->value('email') ?? '');
|
||||
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
@@ -353,7 +355,7 @@ class PaymentNotificationService
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Unable to notify heads of finance: ' . $e->getMessage());
|
||||
Log::warning('Unable to notify heads of finance: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,9 +364,10 @@ class PaymentNotificationService
|
||||
$base['sent'] += $update['sent'] ?? 0;
|
||||
$base['skipped'] += $update['skipped'] ?? 0;
|
||||
$base['failed'] += $update['failed'] ?? 0;
|
||||
if (!empty($update['details'])) {
|
||||
if (! empty($update['details'])) {
|
||||
$base['details'] = array_merge($base['details'], $update['details']);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
@@ -385,6 +388,7 @@ class PaymentNotificationService
|
||||
if (function_exists('user_timezone')) {
|
||||
return (string) user_timezone();
|
||||
}
|
||||
|
||||
return (string) (config('app.timezone') ?: 'UTC');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,12 @@ use DateTimeZone;
|
||||
|
||||
class PaymentTestNotificationService
|
||||
{
|
||||
public function __construct(private EmailService $emailService)
|
||||
{
|
||||
}
|
||||
public function __construct(private EmailService $emailService) {}
|
||||
|
||||
public function send(string $email, string $type = 'no_payment'): array
|
||||
{
|
||||
$email = trim($email);
|
||||
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return ['ok' => false, 'message' => 'Invalid email address.'];
|
||||
}
|
||||
|
||||
@@ -53,11 +51,11 @@ class PaymentTestNotificationService
|
||||
|
||||
$ccEmail = $this->findSecondaryGuardianEmail($parentId, $email);
|
||||
|
||||
$parentName = $user ? trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')) : '';
|
||||
$parentName = $user ? trim(($user->firstname ?? '').' '.($user->lastname ?? '')) : '';
|
||||
$monthYear = $now->format('F Y');
|
||||
$subject = sprintf('Monthly Tuition Reminder — %s', $schoolYear);
|
||||
$greeting = $parentName !== '' ? "Dear {$parentName}," : 'Dear Parent,';
|
||||
$balanceFmt = '$' . number_format($totalBalance, 2);
|
||||
$balanceFmt = '$'.number_format($totalBalance, 2);
|
||||
$intro = ($type === 'no_payment')
|
||||
? "We noticed there are outstanding tuition charges for {$schoolYear} but no payment has been recorded yet."
|
||||
: "This is your monthly installment reminder for {$schoolYear}.";
|
||||
@@ -79,11 +77,11 @@ class PaymentTestNotificationService
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
$bodyHtml .= "<ul>"
|
||||
. "<li><strong>Remaining installments:</strong> {$installmentInfo['remaining_installments']}</li>"
|
||||
. "<li><strong>Suggested installment this month:</strong> {$installmentInfo['installment_due_fmt']}</li>"
|
||||
. "<li><strong>Maximum installments available:</strong> {$installmentInfo['max_installments']}</li>"
|
||||
. "</ul>";
|
||||
$bodyHtml .= '<ul>'
|
||||
."<li><strong>Remaining installments:</strong> {$installmentInfo['remaining_installments']}</li>"
|
||||
."<li><strong>Suggested installment this month:</strong> {$installmentInfo['installment_due_fmt']}</li>"
|
||||
."<li><strong>Maximum installments available:</strong> {$installmentInfo['max_installments']}</li>"
|
||||
.'</ul>';
|
||||
|
||||
$body = MailHtml::document('Monthly Tuition Reminder', $bodyHtml);
|
||||
|
||||
@@ -168,7 +166,7 @@ class PaymentTestNotificationService
|
||||
'remaining_installments' => $remainingInstallments,
|
||||
'max_installments' => $maxInstallments,
|
||||
'installment_due' => round($installmentDue, 2),
|
||||
'installment_due_fmt' => '$' . number_format($installmentDue, 2),
|
||||
'installment_due_fmt' => '$'.number_format($installmentDue, 2),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -179,7 +177,7 @@ class PaymentTestNotificationService
|
||||
}
|
||||
|
||||
$row = FamilyGuardian::query()->where('user_id', $parentId)->first();
|
||||
if (!$row || empty($row->family_id)) {
|
||||
if (! $row || empty($row->family_id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class PaymentTransactionService
|
||||
'payment_status' => $payload['payment_status'] ?? 'Pending',
|
||||
'transaction_fee' => $payload['transaction_fee'] ?? null,
|
||||
'payment_reference' => $payload['payment_reference'] ?? null,
|
||||
'is_full_payment' => !empty($payload['is_full_payment']),
|
||||
'is_full_payment' => ! empty($payload['is_full_payment']),
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
];
|
||||
|
||||
@@ -6,13 +6,19 @@ use App\Models\Payment;
|
||||
use App\Services\ApplicationUrlService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PayPal\Api\Amount;
|
||||
use PayPal\Api\Payer;
|
||||
use PayPal\Api\PaymentExecution;
|
||||
use PayPal\Api\RedirectUrls;
|
||||
use PayPal\Api\Transaction;
|
||||
use PayPal\Auth\OAuthTokenCredential;
|
||||
use PayPal\Rest\ApiContext;
|
||||
|
||||
class PaypalPaymentService
|
||||
{
|
||||
public function __construct(
|
||||
private ApplicationUrlService $urls,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function getRedirectUrl(): string
|
||||
{
|
||||
@@ -24,11 +30,11 @@ class PaypalPaymentService
|
||||
public function createPayment(int $paymentId): array
|
||||
{
|
||||
$payment = Payment::query()->find($paymentId);
|
||||
if (!$payment) {
|
||||
if (! $payment) {
|
||||
throw new \RuntimeException('Payment not found.');
|
||||
}
|
||||
|
||||
if (!$this->sdkAvailable()) {
|
||||
if (! $this->sdkAvailable()) {
|
||||
return [
|
||||
'redirect_url' => $this->getRedirectUrl(),
|
||||
'mode' => 'hosted',
|
||||
@@ -39,23 +45,23 @@ class PaypalPaymentService
|
||||
try {
|
||||
$apiContext = $this->buildApiContext();
|
||||
|
||||
$payer = new \PayPal\Api\Payer();
|
||||
$payer = new Payer;
|
||||
$payer->setPaymentMethod('paypal');
|
||||
|
||||
$amount = new \PayPal\Api\Amount();
|
||||
$amount = new Amount;
|
||||
$amount->setCurrency('USD')->setTotal($payment->balance ?? $payment->total_amount ?? 0);
|
||||
|
||||
$transaction = new \PayPal\Api\Transaction();
|
||||
$transaction = new Transaction;
|
||||
$transaction->setAmount($amount)
|
||||
->setDescription('Payment for school fees')
|
||||
->setInvoiceNumber(uniqid('inv_', true));
|
||||
|
||||
$paypalPayment = new \PayPal\Api\Payment();
|
||||
$paypalPayment = new \PayPal\Api\Payment;
|
||||
$paypalPayment->setIntent('sale')
|
||||
->setPayer($payer)
|
||||
->setTransactions([$transaction]);
|
||||
|
||||
$redirectUrls = new \PayPal\Api\RedirectUrls();
|
||||
$redirectUrls = new RedirectUrls;
|
||||
$redirectUrls->setReturnUrl($this->urls->paypalExecuteReturnUrl())
|
||||
->setCancelUrl($this->urls->paypalCancelUrl());
|
||||
|
||||
@@ -71,14 +77,14 @@ class PaypalPaymentService
|
||||
'payment_id' => $paymentId,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('PayPal create payment failed: ' . $e->getMessage());
|
||||
Log::error('PayPal create payment failed: '.$e->getMessage());
|
||||
throw new \RuntimeException('Unable to create PayPal payment.');
|
||||
}
|
||||
}
|
||||
|
||||
public function executePayment(string $payerId, ?string $paypalPaymentId = null, ?int $paymentId = null): void
|
||||
{
|
||||
if (!$this->sdkAvailable()) {
|
||||
if (! $this->sdkAvailable()) {
|
||||
throw new \RuntimeException('PayPal SDK not installed.');
|
||||
}
|
||||
|
||||
@@ -89,13 +95,13 @@ class PaypalPaymentService
|
||||
|
||||
$apiContext = $this->buildApiContext();
|
||||
$payment = \PayPal\Api\Payment::get($paypalId, $apiContext);
|
||||
$execution = new \PayPal\Api\PaymentExecution();
|
||||
$execution = new PaymentExecution;
|
||||
$execution->setPayerId($payerId);
|
||||
|
||||
$payment->execute($execution, $apiContext);
|
||||
|
||||
$internalPaymentId = $paymentId;
|
||||
if (!$internalPaymentId && $paypalId !== '') {
|
||||
if (! $internalPaymentId && $paypalId !== '') {
|
||||
$internalPaymentId = (int) Cache::get($this->cacheKey($paypalId));
|
||||
}
|
||||
if ($internalPaymentId) {
|
||||
@@ -113,18 +119,18 @@ class PaypalPaymentService
|
||||
|
||||
private function sdkAvailable(): bool
|
||||
{
|
||||
return class_exists(\PayPal\Rest\ApiContext::class)
|
||||
&& class_exists(\PayPal\Auth\OAuthTokenCredential::class);
|
||||
return class_exists(ApiContext::class)
|
||||
&& class_exists(OAuthTokenCredential::class);
|
||||
}
|
||||
|
||||
private function buildApiContext(): \PayPal\Rest\ApiContext
|
||||
private function buildApiContext(): ApiContext
|
||||
{
|
||||
$clientId = (string) (config('services.paypal.client_id') ?: env('PAYPAL_CLIENT_ID'));
|
||||
$secret = (string) (config('services.paypal.secret') ?: env('PAYPAL_SECRET'));
|
||||
$mode = (string) (config('services.paypal.mode') ?: env('PAYPAL_MODE') ?: 'sandbox');
|
||||
|
||||
$apiContext = new \PayPal\Rest\ApiContext(
|
||||
new \PayPal\Auth\OAuthTokenCredential($clientId, $secret)
|
||||
$apiContext = new ApiContext(
|
||||
new OAuthTokenCredential($clientId, $secret)
|
||||
);
|
||||
|
||||
$apiContext->setConfig([
|
||||
@@ -137,6 +143,6 @@ class PaypalPaymentService
|
||||
|
||||
private function cacheKey(string $paypalPaymentId): string
|
||||
{
|
||||
return 'paypal_payment:' . $paypalPaymentId;
|
||||
return 'paypal_payment:'.$paypalPaymentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace App\Services\Payments;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Enrollment;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PayPalPayment;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PayPalPayment;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use App\Services\EmailService;
|
||||
@@ -14,9 +14,7 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PaypalPaymentSyncService
|
||||
{
|
||||
public function __construct(private EmailService $emailService)
|
||||
{
|
||||
}
|
||||
public function __construct(private EmailService $emailService) {}
|
||||
|
||||
public function sync(bool $dryRun = false, bool $reportOnly = false): array
|
||||
{
|
||||
@@ -36,15 +34,16 @@ class PaypalPaymentSyncService
|
||||
$failed = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!$reportOnly) {
|
||||
if (! $reportOnly) {
|
||||
$entry->sync_attempts = (int) $entry->sync_attempts + 1;
|
||||
$entry->save();
|
||||
}
|
||||
|
||||
$user = User::query()->where('school_id', $entry->parent_school_id)->first();
|
||||
if (!$user) {
|
||||
if (! $user) {
|
||||
$failed[] = (string) $entry->transaction_id;
|
||||
Log::error('PayPal sync: no user for parent_school_id', ['parent_school_id' => $entry->parent_school_id]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -54,11 +53,12 @@ class PaypalPaymentSyncService
|
||||
->latest('created_at')
|
||||
->first();
|
||||
|
||||
if (!$reportOnly && !$dryRun) {
|
||||
if (! $reportOnly && ! $dryRun) {
|
||||
if ($invoice) {
|
||||
$ok = $this->applyPayment($invoice, $entry->amount, (string) $entry->transaction_id, $schoolYear, $semester, $entry->created_at);
|
||||
if (!$ok) {
|
||||
if (! $ok) {
|
||||
$failed[] = (string) $entry->transaction_id;
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
@@ -106,7 +106,7 @@ class PaypalPaymentSyncService
|
||||
$invoice->status = 'Paid';
|
||||
}
|
||||
|
||||
if (!$invoice->save()) {
|
||||
if (! $invoice->save()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class PaypalPaymentSyncService
|
||||
private function updateEnrollmentStatusIfPaid(int $invoiceId, string $schoolYear): void
|
||||
{
|
||||
$invoice = Invoice::query()->find($invoiceId);
|
||||
if (!$invoice || (float) $invoice->balance > 0) {
|
||||
if (! $invoice || (float) $invoice->balance > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,15 +155,16 @@ class PaypalPaymentSyncService
|
||||
{
|
||||
if ($processed === 0 && empty($failed)) {
|
||||
Log::info("[{$mode}] No PayPal sync updates.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$subject = "[{$mode}] PayPal Sync Report - " . now()->format('Y-m-d H:i');
|
||||
$subject = "[{$mode}] PayPal Sync Report - ".now()->format('Y-m-d H:i');
|
||||
$body = "PayPal Sync Mode: {$mode}\n\n";
|
||||
$body .= "{$processed} PayPal payments processed.\n\n";
|
||||
|
||||
if (!empty($failed)) {
|
||||
$body .= count($failed) . " failed transactions:\n" . implode("\n", $failed);
|
||||
if (! empty($failed)) {
|
||||
$body .= count($failed)." failed transactions:\n".implode("\n", $failed);
|
||||
} else {
|
||||
$body .= "No failed transactions.\n";
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ class PaypalTransactionsService
|
||||
|
||||
if ($keyword) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('transaction_id', 'like', '%' . $keyword . '%')
|
||||
->orWhere('payer_email', 'like', '%' . $keyword . '%')
|
||||
->orWhere('event_type', 'like', '%' . $keyword . '%')
|
||||
->orWhere('order_id', 'like', '%' . $keyword . '%')
|
||||
->orWhere('parent_school_id', 'like', '%' . $keyword . '%');
|
||||
$q->where('transaction_id', 'like', '%'.$keyword.'%')
|
||||
->orWhere('payer_email', 'like', '%'.$keyword.'%')
|
||||
->orWhere('event_type', 'like', '%'.$keyword.'%')
|
||||
->orWhere('order_id', 'like', '%'.$keyword.'%')
|
||||
->orWhere('parent_school_id', 'like', '%'.$keyword.'%');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ class PaypalTransactionsService
|
||||
|
||||
if ($keyword) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('transaction_id', 'like', '%' . $keyword . '%')
|
||||
->orWhere('payer_email', 'like', '%' . $keyword . '%')
|
||||
->orWhere('event_type', 'like', '%' . $keyword . '%')
|
||||
->orWhere('order_id', 'like', '%' . $keyword . '%')
|
||||
->orWhere('parent_school_id', 'like', '%' . $keyword . '%');
|
||||
$q->where('transaction_id', 'like', '%'.$keyword.'%')
|
||||
->orWhere('payer_email', 'like', '%'.$keyword.'%')
|
||||
->orWhere('event_type', 'like', '%'.$keyword.'%')
|
||||
->orWhere('order_id', 'like', '%'.$keyword.'%')
|
||||
->orWhere('parent_school_id', 'like', '%'.$keyword.'%');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user