php / easypay-api /includes /ReceiptGenerator.php
kingkay000's picture
Upload 25 files
e31284f verified
<?php
class ReceiptGenerator
{
private $fontPath;
private $logoPath;
public function __construct()
{
if (!extension_loaded('gd')) {
throw new Exception("The PHP GD extension is not enabled. Please enable it in your php.ini or install php-gd.");
}
// Try to find a suitable font
$fonts = [
'C:/Windows/Fonts/arial.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
'/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
'/usr/share/fonts/TTF/arial.ttf'
];
$this->fontPath = null;
foreach ($fonts as $font) {
if (file_exists($font)) {
$this->fontPath = $font;
break;
}
}
$this->logoPath = __DIR__ . '/../assets/logo.png';
}
public function generate($data)
{
$width = 900; // Increased width to accommodate 6 columns comfortably
// Calculate dynamic height
$baseHeight = 450;
$rowHeight = 40;
$numRows = count($data['allocations'] ?? []);
$height = $baseHeight + ($numRows * $rowHeight) + 150;
$image = imagecreatetruecolor($width, $height);
// Colors
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
$grey = imagecolorallocate($image, 100, 100, 100);
$lightGrey = imagecolorallocate($image, 240, 240, 240);
imagefilledrectangle($image, 0, 0, $width, $height, $white);
// Logo
if (file_exists($this->logoPath)) {
$logo = @imagecreatefrompng($this->logoPath);
if ($logo) {
$logoW = imagesx($logo);
$logoH = imagesy($logo);
$targetW = 100;
$targetH = ($targetW / $logoW) * $logoH;
imagecopyresampled($image, $logo, 40, 30, 0, 0, (int) $targetW, (int) $targetH, (int) $logoW, (int) $logoH);
}
}
// School Name Logic
$schoolName = "ACE JUNIOR COLLEGE";
$levelName = isset($data['level_name']) ? strtoupper($data['level_name']) : '';
// Check for Senior classes (SSS 1, SSS 2, SSS 3, or Senior)
if (strpos($levelName, 'SSS') !== false || strpos($levelName, 'SENIOR') !== false) {
$schoolName = "ACE SENIOR COLLEGE";
}
// Header
$this->centerText($image, 18, 50, $schoolName, $black);
$this->centerText($image, 10, 75, "...education for excellence", $grey);
$this->centerText($image, 10, 100, "1, Ebute - Igbogbo Road, Ipakodo, Ikorodu, Lagos, Nigeria", $black);
$this->centerText($image, 10, 120, "Phone No(s): 08027449739, 08077275777", $black);
$this->centerText($image, 10, 140, "mailto:info@acecollege.com.ng https://www.acecollege.com.ng", $black);
// Receipt Title
$titleY = 180;
$receiptNo = $data['receipt_no'] ?? 'N/A';
$this->centerText($image, 12, $titleY, "SCHOOL FEES RECEIPT - REFERENCE NO: $receiptNo", $black);
// Session Info
$session = $data['allocations'][0]['academic_session'] ?? '----';
$term = $data['allocations'][0]['term_of_session'] ?? '-';
$nextSession = is_numeric($session) ? ($session + 1) : '';
$levelPart = isset($data['level_name']) ? ' - ' . strtoupper($data['level_name']) : '';
$sessionStr = "$session/$nextSession ACADEMIC SESSION, TERM $term" . $levelPart;
$this->centerText($image, 10, $titleY + 25, $sessionStr, $grey);
// Student Box
$boxY = 230;
imagerectangle($image, 40, $boxY, $width - 40, $boxY + 40, $grey);
// We expect student_name to be passed in $data. If not, fallback.
$studentName = isset($data['student_name']) ? strtoupper($data['student_name']) : (isset($data['student_code']) ? "STUDENT CODE: " . $data['student_code'] : 'UNKNOWN STUDENT');
$this->centerText($image, 14, $boxY + 26, $studentName, $grey);
// Table Header
$tableY = 300;
// 6 Columns: Description, Term/Session, Amount Billed, Amount Paid, Paid To Date, To Balance
// Width 900. Margins 40. Active 820.
// Approx X: 40, 320, 440, 560, 680, 800 (Right Boundary for last col is width-40 = 860)
// Let's adjust spacing.
$cols = [40, 300, 440, 550, 660, 780];
imageline($image, 40, $tableY, $width - 40, $tableY, $black);
imageline($image, 40, $tableY + 30, $width - 40, $tableY + 30, $black);
if ($this->fontPath) {
imagettftext($image, 8, 0, $cols[0], $tableY + 20, $black, $this->fontPath, "FEE DESCRIPTION");
imagettftext($image, 8, 0, $cols[1], $tableY + 20, $black, $this->fontPath, "TERM/SESSION");
imagettftext($image, 8, 0, $cols[2], $tableY + 20, $black, $this->fontPath, "AMOUNT BILLED");
imagettftext($image, 8, 0, $cols[3], $tableY + 20, $black, $this->fontPath, "AMOUNT PAID");
imagettftext($image, 8, 0, $cols[4], $tableY + 20, $black, $this->fontPath, "PAID TO DATE");
imagettftext($image, 8, 0, $cols[5], $tableY + 20, $black, $this->fontPath, "TO BALANCE");
} else {
// Fallback font
imagestring($image, 3, $cols[0], $tableY + 5, "FEE DESCRIPTION", $black);
imagestring($image, 3, $cols[1], $tableY + 5, "TERM/SESSION", $black);
imagestring($image, 3, $cols[2], $tableY + 5, "BILLED", $black);
imagestring($image, 3, $cols[3], $tableY + 5, "PAID", $black);
imagestring($image, 3, $cols[4], $tableY + 5, "PAID TO DATE", $black);
imagestring($image, 3, $cols[5], $tableY + 5, "TO BALANCE", $black);
}
// Rows
$currY = $tableY + 30;
$totalBilled = 0;
$totalPaidToDate = 0;
$totalBalance = 0;
// Total Paid is passed from outside
$totalAmountPaid = 0;
if (isset($data['allocations']) && is_array($data['allocations'])) {
foreach ($data['allocations'] as $alloc) {
$desc = $alloc['description'];
$session = $alloc['academic_session'] ?? '';
$term = $alloc['term_of_session'] ?? '';
// Format: Term/Session e.g. "1/2025" or "1st/2025"
// Assuming simpler "1/2025" format for space
$termSessionStr = "$term/$session";
$billedVal = $alloc['amount_billed'] ?? 0;
$paidVal = $alloc['amount'] ?? 0;
$paidToDateVal = $alloc['total_paid_to_date'] ?? 0;
$balanceVal = $alloc['balance'] ?? 0;
$totalBilled += $billedVal;
$totalAmountPaid += $paidVal;
$totalPaidToDate += $paidToDateVal;
$totalBalance += $balanceVal;
$billed = number_format($billedVal, 2);
$paid = number_format($paidVal, 2);
$paidToDate = number_format($paidToDateVal, 2);
$balance = number_format($balanceVal, 2);
$currY += 30;
if ($this->fontPath) {
imagettftext($image, 8, 0, $cols[0], $currY, $black, $this->fontPath, substr($desc, 0, 35));
imagettftext($image, 8, 0, $cols[1], $currY, $black, $this->fontPath, $termSessionStr); // Term/Session
} else {
imagestring($image, 3, $cols[0], $currY - 10, substr($desc, 0, 35), $black);
imagestring($image, 3, $cols[1], $currY - 10, $termSessionStr, $black);
}
// Right Alignment using approx right boundaries
// Col 2 (Billed): starts 440. Right align around 530?
// Col 3 (Paid): starts 550.
// Col 4 (PTD): starts 660.
// Col 5 (Bal): starts 780.
$rbBilled = 530;
$rbPaid = 640;
$rbPTD = 760;
$rbBal = $width - 40;
$this->rightAlignText($image, 8, $rbBilled, $currY, $billed, $black);
$this->rightAlignText($image, 8, $rbPaid, $currY, $paid, $black);
$this->rightAlignText($image, 8, $rbPTD, $currY, $paidToDate, $black);
$this->rightAlignText($image, 8, $rbBal, $currY, $balance, $black);
}
}
// Total Row
$currY += 20;
imageline($image, 40, $currY, $width - 40, $currY, $black);
$currY += 26;
$totalPaidStr = number_format($data['total_paid'] ?? 0, 2);
$totalBilledStr = number_format($totalBilled, 2);
$totalPaidToDateStr = number_format($totalPaidToDate, 2);
$totalBalanceStr = number_format($totalBalance, 2);
// Columns for totals
$rbBilled = 530;
$rbPaid = 640;
$rbPTD = 760;
$rbBal = $width - 40;
$this->rightAlignText($image, 9, $rbBilled, $currY, $totalBilledStr, $grey);
$this->rightAlignText($image, 9, $rbPaid, $currY, $totalPaidStr, $grey);
$this->rightAlignText($image, 9, $rbPTD, $currY, $totalPaidToDateStr, $grey);
$this->rightAlignText($image, 9, $rbBal, $currY, $totalBalanceStr, $grey);
imageline($image, 40, $currY + 14, $width - 40, $currY + 14, $black);
// Footer
$footerY = $currY + 50;
if ($this->fontPath) {
imagettftext($image, 9, 0, 40, $footerY, $black, $this->fontPath, "RECEIPT DULY VERIFIED BY");
imagettftext($image, 9, 0, 40, $footerY + 20, $black, $this->fontPath, "BURSAR'S OFFICE");
} else {
imagestring($image, 3, 40, $footerY - 10, "RECEIPT DULY VERIFIED BY", $black);
imagestring($image, 3, 40, $footerY + 10, "BURSAR'S OFFICE", $black);
}
$receivedY = $footerY;
$this->rightAlignText($image, 9, $width - 40, $receivedY, "RECEIVED IN", $black);
$this->rightAlignText($image, 9, $width - 40, $receivedY + 15, "BANK", $black);
$this->rightAlignText($image, 9, $width - 40, $receivedY + 40, "RECEIVED ON", $black);
$dateStr = isset($data['payment_date']) ? strtoupper(date("F d, Y", strtotime($data['payment_date']))) : date("F d, Y");
$this->rightAlignText($image, 9, $width - 40, $receivedY + 55, $dateStr, $black);
ob_start();
imagepng($image);
$content = ob_get_clean();
imagedestroy($image);
return $content;
}
public function generateBase64($data)
{
$content = $this->generate($data);
return base64_encode($content);
}
private function centerText($image, $size, $y, $text, $color)
{
if ($this->fontPath) {
try {
$box = imagettfbbox($size, 0, $this->fontPath, $text);
$textW = $box[2] - $box[0];
$x = (int) ((imagesx($image) - $textW) / 2);
imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text);
return;
} catch (Exception $e) {
// Fallthrough to built-in font
}
}
// Fallback or if fontPath is missing
$gdSize = (int) min(5, max(1, floor($size / 3)));
$charWidth = imagefontwidth($gdSize);
$textW = strlen($text) * $charWidth;
$x = (int) ((imagesx($image) - $textW) / 2);
imagestring($image, $gdSize, $x, (int) $y, $text, $color);
}
private function rightAlignText($image, $size, $rightX, $y, $text, $color)
{
if ($this->fontPath) {
try {
$box = imagettfbbox($size, 0, $this->fontPath, $text);
$textW = $box[2] - $box[0];
$x = (int) ($rightX - $textW);
imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text);
return;
} catch (Exception $e) {
// Fallthrough
}
}
$gdSize = (int) min(5, max(1, floor($size / 3)));
$charWidth = imagefontwidth($gdSize);
$x = (int) ($rightX - (strlen($text) * $charWidth));
imagestring($image, $gdSize, $x, (int) $y, $text, $color);
}
}