224 lines
9.4 KiB
PHP
224 lines
9.4 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
|
|
{
|
|
public function __construct(
|
|
private EmailService $emailService,
|
|
private UserNotificationDispatchService $notifier,
|
|
private ApplicationUrlService $urls,
|
|
) {
|
|
}
|
|
|
|
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>';
|
|
}
|
|
}
|