Files
alrahma_sunday_school_api/app/Services/School/PaymentEventService.php
T
root e0dfc3ec82
API CI/CD / Validate (composer + pint) (push) Successful in 3m15s
API CI/CD / Test (PHPUnit) (push) Failing after 5m4s
API CI/CD / Build frontend assets (push) Successful in 1m3s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
Fixed many feature failures around preferences, route coverage, administrator enrollment, assignment section names, attendance tracking controller access, finance PDF generation, and finance notification logging.
2026-07-07 21:26:47 -04:00

237 lines
9.5 KiB
PHP

<?php
namespace App\Services\School;
use App\Services\ApplicationUrlService;
use App\Services\EmailService;
use App\Services\Notifications\UserNotificationDispatchService;
use Illuminate\Support\Facades\Log;
class PaymentEventService
{
private ApplicationUrlService $urls;
public function __construct(
private EmailService $emailService,
private UserNotificationDispatchService $notifier,
?ApplicationUrlService $urls = null,
) {
$this->urls = $urls ?? app(ApplicationUrlService::class);
}
public function paymentReceived(array $data, array $studentdata = []): array
{
$parentName = trim((string) ($data['firstname'] ?? '').' '.(string) ($data['lastname'] ?? ''));
$fmtMoney = static fn ($v) => '$'.number_format((float) $v, 2);
$fmtDateTz = static function (?string $s): string {
if (! $s) {
return '';
}
$dt = new \DateTime($s, new \DateTimeZone('UTC'));
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
return $dt->format('m-d-Y h:i A');
};
$txid = $data['transaction_id']
?? $data['transactionId']
?? $data['payment_id']
?? $data['id']
?? '';
$methodRaw = $data['payment_method'] ?? ($data['method'] ?? '');
$method = $methodRaw ? ucfirst((string) $methodRaw) : '';
$amountPaid = $fmtMoney($data['amount'] ?? 0);
$preBalance = $fmtMoney($data['pre_balance'] ?? 0);
$postBalance = $fmtMoney($data['post_balance'] ?? 0);
$invoiceTotal = $fmtMoney($data['invoice_total'] ?? 0);
$invoiceDiscountRaw = isset($data['invoice_discount']) ? (float) $data['invoice_discount'] : 0.0;
$parentYearDiscountTotalRaw = isset($data['parent_year_discount_total']) ? (float) $data['parent_year_discount_total'] : 0.0;
$emailData = [
'title' => 'Payment Receipt',
'parentName' => $parentName,
'schoolYear' => $data['school_year'] ?? '',
'semester' => $data['semester'] ?? '',
'invoiceId' => $data['invoice_id'] ?? null,
'invoiceNumber' => $data['invoice_number'] ?? null,
'transactionId' => $txid,
'paymentDate' => $fmtDateTz($data['payment_date'] ?? null),
'method' => $method,
'paymentMethod' => $method,
'amount' => $amountPaid,
'invoiceTotal' => $invoiceTotal,
'preBalance' => $preBalance,
'postBalance' => $postBalance,
'checkNumber' => $data['check_number'] ?? null,
'installmentSeq' => (int) ($data['installment_seq'] ?? 1),
'invoiceDiscount' => $invoiceDiscountRaw > 0 ? $fmtMoney($invoiceDiscountRaw) : null,
'parentYearDiscountTotal' => $parentYearDiscountTotalRaw > 0 ? $fmtMoney($parentYearDiscountTotalRaw) : null,
'portalLink' => $this->urls->webLoginUrl(),
'students' => $studentdata,
];
$inAppTitle = 'Payment Received';
$inAppMsg = sprintf(
'We received %s for Invoice #%s (Installment #%d). New balance: %s. Check your email for details.',
$emailData['amount'],
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
(int) $emailData['installmentSeq'],
$emailData['postBalance']
);
$this->notifyUser((int) ($data['user_id'] ?? 0), $inAppTitle, $inAppMsg, 'payments');
$emailBody = $this->renderEmail('emails/payment_receipt', $emailData, 'Payment Receipt');
$subject = sprintf(
'Payment Receipt - Invoice #%s (%s) %s',
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
$emailData['amount'],
$txid ? "[TX: {$txid}]" : ''
);
return $this->sendEmail((string) ($data['email'] ?? ''), $subject, $emailBody, 'payments');
}
public function extraCharge(array $data, array $studentdata = []): array
{
$fmtMoney = static fn ($v) => '$'.number_format((float) $v, 2);
$fmtFromUtc = static function (?string $s, string $fmt = 'm-d-Y h:i A'): string {
if (! $s) {
return '';
}
try {
$dt = new \DateTime($s, new \DateTimeZone('UTC'));
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
return $dt->format($fmt);
} catch (\Throwable $e) {
Log::error('extraCharge date error: '.$e->getMessage());
return '';
}
};
$fmtDueLocal = static function (?string $s): string {
if (! $s) {
return '';
}
try {
if (strlen($s) <= 10) {
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$dt = \DateTime::createFromFormat('Y-m-d', $s, new \DateTimeZone($tzName ?: 'UTC'));
return $dt ? $dt->format('m-d-Y') : '';
}
if (preg_match('/(Z|[+\-]\d{2}:\d{2})$/', $s)) {
$dt = new \DateTime($s);
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
return $dt->format('m-d-Y h:i A');
}
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$dt = new \DateTime($s, new \DateTimeZone($tzName ?: 'UTC'));
return $dt->format('m-d-Y h:i A');
} catch (\Throwable $e) {
Log::error('extraCharge due date error: '.$e->getMessage());
return '';
}
};
$parentName = trim((string) ($data['firstname'] ?? '').' '.(string) ($data['lastname'] ?? ''));
$amountSigned = (float) ($data['amount_signed'] ?? 0);
$amountAbs = (float) ($data['amount_abs'] ?? abs($amountSigned));
$preBalance = $fmtMoney($data['pre_balance'] ?? 0);
$postBalance = $fmtMoney($data['post_balance'] ?? 0);
$invoiceTotal = $fmtMoney($data['invoice_total'] ?? 0);
$createdAt = $fmtFromUtc($data['created_at'] ?? null);
$dueDate = $fmtDueLocal($data['due_date'] ?? null);
$typeLabel = (($data['charge_type'] ?? 'add') === 'add') ? 'Added charge' : 'Deducted amount';
$amountFmt = $fmtMoney($amountSigned);
$emailData = [
'title' => 'Account Charge Update',
'parentName' => $parentName,
'schoolYear' => $data['school_year'] ?? '',
'semester' => $data['semester'] ?? '',
'invoiceId' => $data['invoice_id'] ?? null,
'invoiceNumber' => $data['invoice_number'] ?? null,
'typeLabel' => $typeLabel,
'chargeTitle' => $data['charge_title'] ?? '',
'chargeDesc' => $data['charge_desc'] ?? '',
'chargeType' => $data['charge_type'] ?? 'add',
'amountSigned' => $amountFmt,
'amountAbs' => $fmtMoney($amountAbs),
'invoiceTotal' => $invoiceTotal,
'preBalance' => $preBalance,
'postBalance' => $postBalance,
'createdAt' => $createdAt,
'dueDate' => $dueDate !== '' ? $dueDate : null,
'portalLink' => $data['portal_link'] ?? $this->urls->webLoginUrl(),
'invoiceLink' => $data['invoice_link'] ?? null,
'students' => $studentdata,
];
$inAppTitle = 'Account Updated';
$inAppMsg = sprintf(
'%s: %s (%s). New balance: %s.',
$typeLabel,
$emailData['chargeTitle'],
$emailData['amountSigned'],
$emailData['postBalance']
);
$this->notifyUser((int) ($data['user_id'] ?? 0), $inAppTitle, $inAppMsg, 'billing');
$subject = sprintf(
'Charge Update - Invoice #%s (%s)',
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
$emailData['amountSigned']
);
$body = $this->renderEmail('emails/extra_charge_notice', $emailData, 'Account Charge Update');
return $this->sendEmail((string) ($data['email'] ?? ''), $subject, $body, 'billing');
}
private function notifyUser(int $userId, string $title, string $message, string $topic): void
{
if ($userId <= 0) {
return;
}
$this->notifier->notifyUser($userId, $title, $message, ['in_app'], $topic, 'parent');
}
private function sendEmail(string $to, string $subject, string $html, string $fromKey): array
{
if ($to === '') {
Log::warning('Payment event missing recipient email', ['subject' => $subject]);
return ['ok' => false, 'message' => 'Missing recipient email.'];
}
$ok = $this->emailService->send($to, $subject, $html, $fromKey);
if (! $ok) {
Log::error('Payment event email failed', ['to' => $to, 'subject' => $subject]);
}
return ['ok' => $ok];
}
private function renderEmail(string $viewName, array $data, string $fallbackTitle): string
{
Log::debug('Skipping Blade email template', ['view' => $viewName]);
return '<p>'.e($fallbackTitle).'</p>';
}
}