diff --git a/debug_invoice.html b/debug_invoice.html new file mode 100644 index 00000000..8d81acc1 --- /dev/null +++ b/debug_invoice.html @@ -0,0 +1,188 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8" /> + <title>Invoice No: INV-284</title> + + <style> + .invoice-box { + max-width: 800px; + margin: auto; + padding: 30px; + border: 1px solid #eee; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); + font-size: 16px; + line-height: 24px; + font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; + color: #555; + } + + .invoice-box table { + width: 100%; + line-height: inherit; + text-align: left; + } + + .invoice-box table td { + padding: 5px; + vertical-align: top; + } + + .invoice-box table tr td:nth-child(2) { + text-align: right; + } + + .invoice-box table tr.top table td { + padding-bottom: 20px; + } + + .invoice-box table tr.top table td.title { + font-size: 45px; + line-height: 45px; + color: #333; + } + + .invoice-box table tr.information table td { + padding-bottom: 40px; + } + + .invoice-box table tr.heading td { + background: #eee; + border-bottom: 1px solid #ddd; + font-weight: bold; + } + + .invoice-box table tr.details td { + padding-bottom: 20px; + } + + .invoice-box table tr.item td { + border-bottom: 1px solid #eee; + } + + .invoice-box table tr.item.last td { + border-bottom: none; + } + + .invoice-box table tr.total td:nth-child(2) { + border-top: 2px solid #eee; + font-weight: bold; + } + + @media only screen and (max-width: 600px) { + .invoice-box table tr.top table td { + width: 100%; + display: block; + text-align: center; + } + + .invoice-box table tr.information table td { + width: 100%; + display: block; + text-align: center; + } + } + + /** RTL **/ + .invoice-box.rtl { + direction: rtl; + font-family: Tahoma, 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; + } + + .invoice-box.rtl table { + text-align: right; + } + + .invoice-box.rtl table tr td:nth-child(2) { + text-align: left; + } + </style> +</head> + +<body> + <div class="invoice-box"> + <table cellpadding="0" cellspacing="0"> + <tr class="top"> + <td colspan="2"> + <table> + <tr> + <td class="title"> + <img src="system/uploads/logo.default.png" style="max-width: 100px" /> + </td> + + <td> + Invoice #: INV-284<br /> + Created: March 16, 2025<br /> + Due: March 23, 2025 + </td> + </tr> + </table> + </td> + </tr> + + <tr class="information"> + <td colspan="2"> + <table> + <tr> + <td> + PHPNuxBill<br /> + No 2, udo wogu street chevy view estate, Road c17 mercyland, losoro lakowe<br /> + +2347054020007<br /> + </td> + + <td> + admin<br /> + admin <br /> + admin@gmail.com <br /> + 9006721321 <br /> + </td> + </tr> + </table> + </td> + </tr> + + <table style="width: 100%; border-collapse: collapse; margin: 20px 0;"> + <thead> + <tr> + <th style="background: #3498db; color: white; padding: 12px; text-align: left;">Service + Description</th> + <th style="background: #3498db; color: white; padding: 12px; text-align: left;">Plan Details + </th> + <th style="background: #3498db; color: white; padding: 12px; text-align: left;">Amount</th> + </tr> + </thead> + <tbody> + <tr> + <td style='padding: 12px; border-bottom: 1px solid #ddd;'>Test Plan</td> + <td style='padding: 12px; border-bottom: 1px solid #ddd;'>Monthly Subscription</td> + <td style='padding: 12px; border-bottom: 1px solid #ddd;'>₦200</td> + </tr> + </tbody> + </table> + </table> + + <div style="text-align: right; margin-top: 20px;"> + <p style="margin: 0;">Subtotal: ₦202.00<br> + TAX (1%): ₦2.00<br> + <strong>Total Due: ₦202.00</strong> + </p> + </div> + + <div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 20px;"> + <h4 style="margin: 0;">Payment Options:</h4> + <p style="margin: 0;">Online Portal: <a href="pay.phpnuxbill.com">pay.phpnuxbill.com</a> <br> + Bank Transfer: Account # 1234-567890<br> + Auto Pay: Enabled (Next payment: 2023-11-12)</p> + </div> + + <footer + style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; text-align: center;"> + <p style="margin: 0;">Thank you for choosing PHPNuxBill!<br> + Late payments may result in service interruption<br> + Need help? Contact support@PHPNuxBill.com or call +2347054020007</p> + </footer> + </div> +</body> + +</html> \ No newline at end of file diff --git a/system/autoload/Invoice.php b/system/autoload/Invoice.php new file mode 100644 index 00000000..51e54750 --- /dev/null +++ b/system/autoload/Invoice.php @@ -0,0 +1,235 @@ +<?php + +use Mpdf\Mpdf; + +class Invoice +{ + public static function generateInvoice($invoiceData) + { + try { + if (empty($invoiceData['id'])) { + throw new Exception("Invoice ID is required"); + } + + $templatePath = 'pages/custom_invoice.html'; + if (!file_exists($templatePath)) { + $templatePath = 'pages/default_invoice.html'; + } + + $template = file_get_contents($templatePath); + + if (!$template) { + throw new Exception("Invoice template not found"); + } + + if (strpos($template, '<body') === false) { + $template = "<html><body>$template</body></html>"; + } + + $processedHtml = self::renderTemplate($template, $invoiceData); + + // Debugging: Save processed HTML to file for review + // file_put_contents('debug_invoice.html', $processedHtml); + + // Generate PDF + $mpdf = new Mpdf([ + 'mode' => 'utf-8', + 'format' => 'A4', + 'margin_left' => 10, + 'margin_right' => 10, + 'margin_top' => 10, + 'margin_bottom' => 10, + 'default_font' => 'helvetica', + 'orientation' => 'P', + ]); + + $mpdf->SetDisplayMode('fullpage'); + $mpdf->SetProtection(['print']); + $mpdf->shrink_tables_to_fit = 1; + $mpdf->SetWatermarkText(strtoupper($invoiceData['status'] ?? 'UNPAID'), 0.15); + $mpdf->showWatermarkText = true; + $mpdf->WriteHTML($processedHtml); + + // Save PDF + $filename = "invoice_{$invoiceData['id']}.pdf"; + $outputPath = "system/uploads/invoices/{$filename}"; + $mpdf->Output($outputPath, 'F'); + + if (!file_exists($outputPath)) { + throw new Exception("Failed to save PDF file"); + } + + return $filename; + + } catch (\Exception $e) { + _log("Invoice generation failed: " . $e->getMessage()); + sendTelegram("Invoice generation failed: " . $e->getMessage()); + return false; + } + } + + private static function renderTemplate($template, $invoiceData) + { + return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) { + $key = $matches[1]; + if (!isset($invoiceData[$key])) { + _log("Missing invoice key: $key"); + return ''; + } + + if (in_array($key, ['created_at', 'due_date'])) { + return date('F j, Y', strtotime($invoiceData[$key])); + } + + if (in_array($key, ['amount', 'total', 'subtotal', 'tax'])) { + return $invoiceData['currency_code'] . number_format((float) $invoiceData[$key], 2); + } + + if ($key === 'bill_rows') { + return html_entity_decode($invoiceData[$key]); + } + + + return htmlspecialchars($invoiceData[$key] ?? ''); + }, $template); + } + + public static function sendInvoice($userId, $status = "Unpaid") + { + global $config; + + if (empty($config['currency_code'])) { + $config['currency_code'] = '$'; + } + + $account = ORM::for_table('tbl_customers')->find_one($userId); + + if (!$account) { + _log("Failed to send invoice: User not found"); + sendTelegram("Failed to send invoice: User not found"); + return false; + } + + $invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one(); + + if (!$invoice) { + _log("Failed to send invoice: Transaction not found"); + sendTelegram("Failed to send invoice: Transaction not found"); + return false; + } + + [$additionalBills, $add_cost] = User::getBills($account->id); + + $invoiceItems = [ + [ + 'description' => $invoice->plan_name, + 'details' => 'Monthly Subscription', + 'amount' => (float) $invoice->price + ] + ]; + $subtotal = (float) $invoice->price; + + if ($add_cost > 0 && $invoice->routers != 'balance') { + foreach ($additionalBills as $description => $amount) { + if (is_numeric($amount)) { + $invoiceItems[] = [ + 'description' => $description, + 'details' => 'Additional Bill', + 'amount' => (float) $amount + ]; + $subtotal += (float) $amount; + } else { + _log("Invalid bill amount for {$description}: {$amount}"); + } + } + } + + $tax_rate = (float) ($config['tax_rate'] ?? 0); + $tax = $config['enable_tax'] ? Package::tax($subtotal) : 0; + $total = ($tax > 0) ? $subtotal + $tax : $subtotal + $tax; + + $invoiceData = [ + 'id' => "INV-" . Package::_raid(), + 'fullname' => $account->fullname, + 'email' => $account->email, + 'address' => $account->address, + 'phone' => $account->phonenumber, + 'bill_rows' => self::generateBillRows($invoiceItems, $config['currency_code'], $subtotal, $tax_rate, $tax, $total), + 'status' => $status, + 'created_at' => date('Y-m-d H:i:s'), + 'due_date' => date('Y-m-d H:i:s', strtotime('+7 days')), + 'currency' => $config['currency_code'], + 'company_address' => $config['address'], + 'company_name' => $config['CompanyName'], + 'company_phone' => $config['phone'], + 'logo' => $config['logo'], + ]; + + if (!isset($invoiceData['bill_rows']) || empty($invoiceData['bill_rows'])) { + _log("Invoice Error: Bill rows data is empty."); + } + + $filename = self::generateInvoice($invoiceData); + + if ($filename) { + $pdfPath = "system/uploads/invoices/{$filename}"; + + try { + Message::sendEmail( + $account->email, + "Invoice for Account {$account->fullname}", + "Please find your invoice attached", + $pdfPath + ); + return true; + } catch (\Exception $e) { + _log("Failed to send invoice email: " . $e->getMessage()); + sendTelegram("Failed to send invoice email: " . $e->getMessage()); + return false; + } + } + + return false; + } + + private static function generateBillRows($items, $currency, $subtotal, $tax_rate, $tax, $total) + { + $html = "<table style='width: 100%; border-collapse: collapse; margin: 20px 0;'> + <thead> + <tr> + <th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Description</th> + <th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Details</th> + <th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Amount</th> + </tr> + </thead> + <tbody>"; + + foreach ($items as $item) { + $html .= "<tr> + <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$item['description']}</td> + <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$item['details']}</td> + <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$currency}" . number_format((float) $item['amount'], 2) . "</td> + </tr>"; + } + + $html .= "<tr> + <td colspan='2' style='text-align: right; padding: 10px; border-top: 2px solid #3498db;'>Subtotal:</td> + <td style='padding: 10px; border-top: 2px solid #3498db;'>{$currency}" . number_format($subtotal, 2) . "</td> + </tr> + <tr> + <td colspan='2' style='text-align: right; padding: 10px;'>TAX ({$tax_rate}%):</td> + <td style='padding: 10px;'>{$currency}" . number_format($tax, 2) . "</td> + </tr> + <tr> + <td colspan='2' style='text-align: right; padding: 10px; font-weight: bold;'>Total:</td> + <td style='padding: 10px; font-weight: bold;'>{$currency}" . number_format($total, 2) . "</td> + </tr>"; + + $html .= "</tbody></table>"; + + return $html; + } + + + +} diff --git a/system/lan/english.json b/system/lan/english.json index 7dbb534a..0ea29526 100644 --- a/system/lan/english.json +++ b/system/lan/english.json @@ -1148,5 +1148,6 @@ "Continue_the_process_of_sending_messages": "Continue the process of sending messages", "Yours_Balances": "Yours Balances", "Friend_username": "Friend username", - "This_will_sync_dan_send_Customer_active_package_to_Mikrotik": "This will sync dan send Customer active package to Mikrotik" + "This_will_sync_dan_send_Customer_active_package_to_Mikrotik": "This will sync dan send Customer active package to Mikrotik", + "Email_not_sent__Mailer_Error__": "Email not sent, Mailer Error: " } \ No newline at end of file