Files
alrahma_sunday_school_api/app/Services/School/PaymentEventService.php
T
2026-06-09 01:03:53 -04:00

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>';
}
}