175 lines
5.8 KiB
PHP
175 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Payments;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\Enrollment;
|
|
use App\Models\Invoice;
|
|
use App\Models\Payment;
|
|
use App\Models\PayPalPayment;
|
|
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');
|
|
}
|
|
}
|