add notifications logic and add support of both JWT and Sanctum
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
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\Student;
|
||||
use App\Models\User;
|
||||
use App\Services\EmailService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PaypalPaymentSyncService
|
||||
{
|
||||
public function __construct(private EmailService $emailService)
|
||||
{
|
||||
}
|
||||
|
||||
public function sync(bool $dryRun = false, bool $reportOnly = false): array
|
||||
{
|
||||
$mode = $reportOnly ? 'REPORT-ONLY' : ($dryRun ? 'DRY-RUN' : 'LIVE');
|
||||
|
||||
$semester = (string) (Configuration::getConfig('semester') ?? '');
|
||||
$schoolYear = (string) (Configuration::getConfig('school_year') ?? '');
|
||||
|
||||
$entries = PayPalPayment::query()
|
||||
->where('status', 'COMPLETED')
|
||||
->where('synced', 0)
|
||||
->where('sync_attempts', '<', 3)
|
||||
->whereNotNull('transaction_id')
|
||||
->get();
|
||||
|
||||
$synced = 0;
|
||||
$failed = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
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) {
|
||||
$failed[] = (string) $entry->transaction_id;
|
||||
Log::error('PayPal sync: no user for parent_school_id', ['parent_school_id' => $entry->parent_school_id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoice = Invoice::query()
|
||||
->where('parent_id', $user->id)
|
||||
->where('school_year', $schoolYear)
|
||||
->latest('created_at')
|
||||
->first();
|
||||
|
||||
if (!$reportOnly && !$dryRun) {
|
||||
if ($invoice) {
|
||||
$ok = $this->applyPayment($invoice, $entry->amount, (string) $entry->transaction_id, $schoolYear, $semester, $entry->created_at);
|
||||
if (!$ok) {
|
||||
$failed[] = (string) $entry->transaction_id;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Payment::query()->create([
|
||||
'parent_id' => $user->id,
|
||||
'invoice_id' => 0,
|
||||
'total_amount' => $entry->amount,
|
||||
'paid_amount' => $entry->amount,
|
||||
'balance' => 0.00,
|
||||
'number_of_installments' => 1,
|
||||
'transaction_id' => $entry->transaction_id,
|
||||
'payment_method' => 'PayPal',
|
||||
'payment_date' => optional($entry->created_at)->format('Y-m-d'),
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'status' => 'Completed',
|
||||
'updated_by' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
$entry->synced = 1;
|
||||
$entry->save();
|
||||
}
|
||||
|
||||
$synced++;
|
||||
}
|
||||
|
||||
$this->sendReport($mode, $synced, $failed);
|
||||
|
||||
return [
|
||||
'mode' => $mode,
|
||||
'processed' => $synced,
|
||||
'failed' => $failed,
|
||||
];
|
||||
}
|
||||
|
||||
private function applyPayment(Invoice $invoice, float $amount, string $transactionId, string $schoolYear, string $semester, $createdAt): bool
|
||||
{
|
||||
$newPaid = (float) $invoice->paid_amount + $amount;
|
||||
$newBalance = (float) $invoice->balance - $amount;
|
||||
|
||||
$invoice->paid_amount = $newPaid;
|
||||
$invoice->balance = $newBalance;
|
||||
if ($newBalance <= 0) {
|
||||
$invoice->status = 'Paid';
|
||||
}
|
||||
|
||||
if (!$invoice->save()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Payment::query()->create([
|
||||
'parent_id' => $invoice->parent_id,
|
||||
'invoice_id' => $invoice->id,
|
||||
'total_amount' => $invoice->total_amount,
|
||||
'paid_amount' => $amount,
|
||||
'balance' => $newBalance,
|
||||
'number_of_installments' => 1,
|
||||
'transaction_id' => $transactionId,
|
||||
'payment_method' => 'PayPal',
|
||||
'payment_date' => $createdAt ? date('Y-m-d', strtotime((string) $createdAt)) : date('Y-m-d'),
|
||||
'status' => $newBalance <= 0 ? 'Full' : 'Partial',
|
||||
'check_file' => null,
|
||||
'updated_by' => null,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
]);
|
||||
|
||||
$this->updateEnrollmentStatusIfPaid($invoice->id, $schoolYear);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updateEnrollmentStatusIfPaid(int $invoiceId, string $schoolYear): void
|
||||
{
|
||||
$invoice = Invoice::query()->find($invoiceId);
|
||||
if (!$invoice || (float) $invoice->balance > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$students = Student::query()
|
||||
->where('parent_id', $invoice->parent_id)
|
||||
->where('school_year', $schoolYear)
|
||||
->get();
|
||||
|
||||
foreach ($students as $student) {
|
||||
Enrollment::query()
|
||||
->where('student_id', $student->id)
|
||||
->update(['enrollment_status' => 'enrolled']);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendReport(string $mode, int $processed, array $failed): void
|
||||
{
|
||||
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');
|
||||
$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);
|
||||
} else {
|
||||
$body .= "No failed transactions.\n";
|
||||
}
|
||||
|
||||
$this->emailService->send('support@alrahmaisgl.org', $subject, nl2br($body), 'finance');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user