diff --git a/app/Jobs/SendStatementEmailJob.php b/app/Jobs/SendStatementEmailJob.php
index 6984a11..98a970d 100644
--- a/app/Jobs/SendStatementEmailJob.php
+++ b/app/Jobs/SendStatementEmailJob.php
@@ -9,7 +9,6 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Modules\Webstatement\Models\Account;
use Modules\Webstatement\Models\PrintStatementLog;
@@ -19,6 +18,7 @@ use Modules\Basicdata\Models\Branch;
/**
* Job untuk mengirim email PDF statement ke nasabah
* Mendukung pengiriman per rekening, per cabang, atau seluruh cabang
+ * Menggunakan PHPMailer dengan dukungan NTLM/GSSAPI
*/
class SendStatementEmailJob implements ShouldQueue
{
@@ -47,7 +47,7 @@ class SendStatementEmailJob implements ShouldQueue
$this->batchId = $batchId ?? uniqid('batch_');
$this->logId = $logId;
- Log::info('SendStatementEmailJob created', [
+ Log::info('SendStatementEmailJob created with PHPMailer', [
'period' => $this->period,
'request_type' => $this->requestType,
'target_value' => $this->targetValue,
@@ -320,12 +320,15 @@ class SendStatementEmailJob implements ShouldQueue
// Dapatkan path absolut file
$absolutePdfPath = Storage::path($pdfPath);
- // Kirim email
- // Add delay between email sends to prevent rate limiting
- sleep(1); // 2 second delay
- Mail::to($emailAddress)->send(
- new StatementEmail($statementLog, $absolutePdfPath, false)
- );
+ // Buat instance StatementEmail dengan PHPMailer
+ $statementEmail = new StatementEmail($statementLog, $absolutePdfPath, false);
+
+ // Kirim email menggunakan PHPMailer
+ $emailSent = $statementEmail->send($emailAddress);
+
+ if (!$emailSent) {
+ throw new \Exception("Failed to send email to {$emailAddress} for account {$account->account_number}");
+ }
// Update status log dengan email yang digunakan
$statementLog->update([
@@ -334,7 +337,7 @@ class SendStatementEmailJob implements ShouldQueue
'email_address' => $emailAddress // Simpan email yang digunakan untuk tracking
]);
- Log::info('Email sent for account', [
+ Log::info('Email sent via PHPMailer for account', [
'account_number' => $account->account_number,
'branch_code' => $account->branch_code,
'email' => $emailAddress,
@@ -342,6 +345,9 @@ class SendStatementEmailJob implements ShouldQueue
'pdf_path' => $pdfPath,
'batch_id' => $this->batchId
]);
+
+ // Add delay between email sends to prevent rate limiting
+ sleep(2); // 2 second delay for NTLM/GSSAPI connections
}
/**
diff --git a/app/Mail/StatementEmail.php b/app/Mail/StatementEmail.php
index ee77925..51cb2af 100644
--- a/app/Mail/StatementEmail.php
+++ b/app/Mail/StatementEmail.php
@@ -2,19 +2,22 @@
namespace Modules\Webstatement\Mail;
-use Illuminate\Bus\Queueable;
-use Illuminate\Mail\Mailable;
-use Illuminate\Queue\SerializesModels;
use Modules\Webstatement\Models\Account;
use Modules\Webstatement\Models\PrintStatementLog;
+use Modules\Webstatement\Services\PHPMailerService;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\View;
-class StatementEmail extends Mailable
+/**
+ * Service untuk mengirim email statement menggunakan PHPMailer
+ * dengan dukungan autentikasi NTLM/GSSAPI
+ */
+class StatementEmail
{
- use Queueable, SerializesModels;
-
protected $statement;
protected $filePath;
protected $isZip;
+ protected $phpMailerService;
/**
* Create a new message instance.
@@ -29,15 +32,69 @@ class StatementEmail extends Mailable
$this->statement = $statement;
$this->filePath = $filePath;
$this->isZip = $isZip;
+ $this->phpMailerService = new PHPMailerService();
}
/**
- * Build the message.
- * Membangun struktur email dengan attachment statement
+ * Kirim email statement
*
- * @return $this
+ * @param string $emailAddress
+ * @return bool
*/
- public function build()
+ public function send(string $emailAddress): bool
+ {
+ try {
+ // Generate subject
+ $subject = $this->generateSubject();
+
+ // Generate email body
+ $body = $this->generateEmailBody();
+
+ // Generate attachment name
+ $attachmentName = $this->generateAttachmentName();
+
+ // Determine MIME type
+ $mimeType = $this->isZip ? 'application/zip' : 'application/pdf';
+
+ // Send email using PHPMailer
+ $result = $this->phpMailerService->sendEmail(
+ $emailAddress,
+ $subject,
+ $body,
+ $this->filePath,
+ $attachmentName,
+ $mimeType
+ );
+
+ Log::info('Statement email sent via PHPMailer', [
+ 'to' => $emailAddress,
+ 'subject' => $subject,
+ 'attachment' => $attachmentName,
+ 'account_number' => $this->statement->account_number,
+ 'period' => $this->statement->period_from,
+ 'success' => $result
+ ]);
+
+ return $result;
+
+ } catch (\Exception $e) {
+ Log::error('Failed to send statement email via PHPMailer', [
+ 'to' => $emailAddress,
+ 'account_number' => $this->statement->account_number,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString()
+ ]);
+
+ return false;
+ }
+ }
+
+ /**
+ * Generate email subject
+ *
+ * @return string
+ */
+ protected function generateSubject(): string
{
$subject = 'Statement Rekening Bank Artha Graha Internasional';
@@ -47,33 +104,92 @@ class StatementEmail extends Mailable
$subject .= " - " . \Carbon\Carbon::createFromFormat('Ym', $this->statement->period_from)->locale('id')->isoFormat('MMMM Y');
}
- $email = $this->subject($subject)
- ->view('webstatement::statements.email')
- ->with([
- 'statement' => $this->statement,
- 'accountNumber' => $this->statement->account_number,
- 'periodFrom' => $this->statement->period_from,
- 'periodTo' => $this->statement->period_to,
- 'isRange' => $this->statement->is_period_range,
- 'requestType' => $this->statement->request_type,
- 'batchId' => $this->statement->batch_id,
- 'accounts' => Account::where('account_number', $this->statement->account_number)->first()
- ]);
-
- if ($this->isZip) {
- $fileName = "{$this->statement->account_number}_{$this->statement->period_from}_to_{$this->statement->period_to}.zip";
- $email->attach($this->filePath, [
- 'as' => $fileName,
- 'mime' => 'application/zip',
- ]);
- } else {
- $fileName = "{$this->statement->account_number}_{$this->statement->period_from}.pdf";
- $email->attach($this->filePath, [
- 'as' => $fileName,
- 'mime' => 'application/pdf',
- ]);
+ // Add batch info for batch requests
+ if ($this->statement->request_type && $this->statement->request_type !== 'single_account') {
+ $subject .= " [{$this->statement->request_type}]";
}
- return $email;
+ if ($this->statement->batch_id) {
+ $subject .= " [Batch: {$this->statement->batch_id}]";
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Generate email body HTML
+ *
+ * @return string
+ */
+ protected function generateEmailBody(): string
+ {
+ try {
+ // Get account data
+ $account = Account::where('account_number', $this->statement->account_number)->first();
+
+ // Prepare data for view
+ $data = [
+ 'statement' => $this->statement,
+ 'accountNumber' => $this->statement->account_number,
+ 'periodFrom' => $this->statement->period_from,
+ 'periodTo' => $this->statement->period_to,
+ 'isRange' => $this->statement->is_period_range,
+ 'requestType' => $this->statement->request_type,
+ 'batchId' => $this->statement->batch_id,
+ 'accounts' => $account
+ ];
+
+ // Render view to HTML
+ return View::make('webstatement::statements.email', $data)->render();
+
+ } catch (\Exception $e) {
+ Log::error('Failed to generate email body', [
+ 'account_number' => $this->statement->account_number,
+ 'error' => $e->getMessage()
+ ]);
+
+ // Fallback to simple HTML
+ return $this->generateFallbackEmailBody();
+ }
+ }
+
+ /**
+ * Generate fallback email body
+ *
+ * @return string
+ */
+ protected function generateFallbackEmailBody(): string
+ {
+ $periodText = $this->statement->is_period_range
+ ? "periode {$this->statement->period_from} sampai {$this->statement->period_to}"
+ : "periode " . \Carbon\Carbon::createFromFormat('Ym', $this->statement->period_from)->locale('id')->isoFormat('MMMM Y');
+
+ return "
+
+
+ Statement Rekening Bank Artha Graha Internasional
+ Yth. Nasabah,
+ Terlampir adalah statement rekening Anda untuk {$periodText}.
+ Nomor Rekening: {$this->statement->account_number}
+ Terima kasih atas kepercayaan Anda.
+
+ Salam,
Bank Artha Graha Internasional
+
+
+ ";
+ }
+
+ /**
+ * Generate attachment filename
+ *
+ * @return string
+ */
+ protected function generateAttachmentName(): string
+ {
+ if ($this->isZip) {
+ return "{$this->statement->account_number}_{$this->statement->period_from}_to_{$this->statement->period_to}.zip";
+ } else {
+ return "{$this->statement->account_number}_{$this->statement->period_from}.pdf";
+ }
}
}
diff --git a/app/Services/PHPMailerService.php b/app/Services/PHPMailerService.php
new file mode 100644
index 0000000..78d6b32
--- /dev/null
+++ b/app/Services/PHPMailerService.php
@@ -0,0 +1,289 @@
+mailer = new PHPMailer(true);
+ $this->configureSMTP();
+
+ Log::info('PHPMailerService initialized with NTLM/GSSAPI support');
+ }
+
+ /**
+ * Konfigurasi SMTP dengan dukungan NTLM/GSSAPI dan fallback untuk development
+ */
+ protected function configureSMTP(): void
+ {
+ try {
+ // Server settings
+ $this->mailer->isSMTP();
+ $this->mailer->Host = config('mail.mailers.phpmailer.host', env('MAIL_HOST'));
+ $this->mailer->Port = config('mail.mailers.phpmailer.port', env('MAIL_PORT', 587));
+
+ // Deteksi apakah perlu autentikasi berdasarkan username
+ $username = config('mail.mailers.phpmailer.username', env('MAIL_USERNAME'));
+ $password = config('mail.mailers.phpmailer.password', env('MAIL_PASSWORD'));
+
+ // Hanya aktifkan autentikasi jika username dan password tersedia
+ if (!empty($username) && $username !== 'null' && !empty($password) && $password !== 'null') {
+ $this->mailer->SMTPAuth = true;
+ $this->mailer->Username = $username;
+ $this->mailer->Password = $password;
+
+ Log::info('SMTP authentication enabled', [
+ 'username' => $username,
+ 'host' => $this->mailer->Host
+ ]);
+
+ // Dukungan NTLM/GSSAPI untuk production
+ $authType = config('mail.mailers.phpmailer.auth_type', env('MAIL_AUTH_TYPE', 'NTLM'));
+
+ if (strtoupper($authType) === 'NTLM') {
+ $this->mailer->AuthType = 'NTLM';
+ $this->mailer->Realm = config('mail.mailers.phpmailer.realm', env('MAIL_REALM', ''));
+ $this->mailer->Workstation = config('mail.mailers.phpmailer.workstation', env('MAIL_WORKSTATION', ''));
+
+ Log::info('NTLM authentication configured', [
+ 'realm' => $this->mailer->Realm,
+ 'workstation' => $this->mailer->Workstation
+ ]);
+ } elseif (strtoupper($authType) === 'GSSAPI') {
+ $this->mailer->AuthType = 'XOAUTH2';
+ Log::info('GSSAPI authentication configured');
+ }
+ } else {
+ // Untuk development server seperti Mailpit
+ $this->mailer->SMTPAuth = false;
+
+ Log::info('SMTP authentication disabled for development', [
+ 'host' => $this->mailer->Host,
+ 'port' => $this->mailer->Port
+ ]);
+ }
+
+ // Encryption configuration
+ $encryption = config('mail.mailers.phpmailer.encryption', env('MAIL_ENCRYPTION'));
+ $port = $this->mailer->Port;
+
+ if (!empty($encryption) && $encryption !== 'null') {
+ if ($encryption === 'tls' && ($port == 587 || $port == 25)) {
+ $this->mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+ Log::info('Using STARTTLS encryption', ['port' => $port]);
+ } elseif ($encryption === 'ssl' && ($port == 465 || $port == 993)) {
+ $this->mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
+ Log::info('Using SSL encryption', ['port' => $port]);
+ }
+ } else {
+ // Untuk development/testing server
+ $this->mailer->SMTPSecure = false;
+ $this->mailer->SMTPAutoTLS = false;
+ Log::info('Using no encryption (plain text)', ['port' => $port]);
+ }
+
+ // Tambahan konfigurasi untuk kompatibilitas
+ $this->mailer->SMTPOptions = array(
+ 'ssl' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ 'allow_self_signed' => true
+ )
+ );
+
+ // Debug mode - COMMENTED OUT
+ // if (config('app.debug')) {
+ // $this->mailer->SMTPDebug = SMTP::DEBUG_SERVER;
+ // }
+
+ // Timeout settings
+ $this->mailer->Timeout = config('mail.mailers.phpmailer.timeout', 30);
+ $this->mailer->SMTPKeepAlive = true;
+
+ Log::info('SMTP configured successfully', [
+ 'host' => $this->mailer->Host,
+ 'port' => $this->mailer->Port,
+ 'auth_enabled' => $this->mailer->SMTPAuth,
+ 'encryption' => $encryption ?: 'none',
+ 'smtp_secure' => $this->mailer->SMTPSecure ?: 'none'
+ ]);
+
+ } catch (Exception $e) {
+ Log::error('Failed to configure SMTP', [
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString()
+ ]);
+ throw $e;
+ }
+ }
+
+ /**
+ * Kirim email dengan attachment
+ *
+ * @param string $to Email tujuan
+ * @param string $subject Subjek email
+ * @param string $body Body email (HTML)
+ * @param string|null $attachmentPath Path file attachment
+ * @param string|null $attachmentName Nama file attachment
+ * @param string|null $mimeType MIME type attachment
+ * @return bool
+ */
+ /**
+ * Kirim email dengan handling khusus untuk development dan production
+ *
+ * @param string $to Email tujuan
+ * @param string $subject Subjek email
+ * @param string $body Body email (HTML)
+ * @param string|null $attachmentPath Path file attachment
+ * @param string|null $attachmentName Nama file attachment
+ * @param string|null $mimeType MIME type attachment
+ * @return bool
+ */
+ public function sendEmail(
+ string $to,
+ string $subject,
+ string $body,
+ ?string $attachmentPath = null,
+ ?string $attachmentName = null,
+ ?string $mimeType = 'application/pdf'
+ ): bool {
+ try {
+ // Reset recipients dan attachments
+ $this->mailer->clearAddresses();
+ $this->mailer->clearAttachments();
+
+ // Set sender
+ $fromAddress = config('mail.from.address', env('MAIL_FROM_ADDRESS'));
+ $fromName = config('mail.from.name', env('MAIL_FROM_NAME'));
+
+ if (!empty($fromAddress)) {
+ $this->mailer->setFrom($fromAddress, $fromName);
+ } else {
+ // Fallback untuk development
+ $this->mailer->setFrom('noreply@localhost', 'Development Server');
+ }
+
+ // Add recipient
+ $this->mailer->addAddress($to);
+
+ // Content
+ $this->mailer->isHTML(true);
+ $this->mailer->Subject = $subject;
+ $this->mailer->Body = $body;
+ $this->mailer->AltBody = strip_tags($body);
+
+ // Attachment
+ if ($attachmentPath && file_exists($attachmentPath)) {
+ $this->mailer->addAttachment(
+ $attachmentPath,
+ $attachmentName ?: basename($attachmentPath),
+ 'base64',
+ $mimeType
+ );
+
+ Log::info('Attachment added to email', [
+ 'path' => $attachmentPath,
+ 'name' => $attachmentName,
+ 'mime_type' => $mimeType,
+ 'file_size' => filesize($attachmentPath)
+ ]);
+ }
+
+ // Attempt to send
+ $result = $this->mailer->send();
+
+ Log::info('Email sent successfully via PHPMailer', [
+ 'to' => $to,
+ 'subject' => $subject,
+ 'has_attachment' => !is_null($attachmentPath),
+ 'host' => $this->mailer->Host,
+ 'port' => $this->mailer->Port,
+ 'auth_enabled' => $this->mailer->SMTPAuth
+ ]);
+
+ return $result;
+
+ } catch (Exception $e) {
+ Log::error('Failed to send email via PHPMailer', [
+ 'to' => $to,
+ 'subject' => $subject,
+ 'host' => $this->mailer->Host,
+ 'port' => $this->mailer->Port,
+ 'auth_enabled' => $this->mailer->SMTPAuth,
+ 'error' => $e->getMessage(),
+ 'error_code' => $e->getCode(),
+ 'trace' => $e->getTraceAsString()
+ ]);
+
+ return false;
+ }
+ }
+
+ /**
+ * Test koneksi SMTP dengan fallback encryption
+ *
+ * @return bool
+ */
+ public function testConnection(): bool
+ {
+ try {
+ // Coba koneksi dengan konfigurasi saat ini
+ $this->mailer->smtpConnect();
+ $this->mailer->smtpClose();
+
+ Log::info('SMTP connection test successful with current config');
+ return true;
+
+ } catch (Exception $e) {
+ Log::warning('SMTP connection failed, trying fallback', [
+ 'error' => $e->getMessage()
+ ]);
+
+ // Fallback: coba tanpa encryption
+ try {
+ $this->mailer->SMTPSecure = false;
+ $this->mailer->SMTPAutoTLS = false;
+
+ $this->mailer->smtpConnect();
+ $this->mailer->smtpClose();
+
+ Log::info('SMTP connection successful with fallback (no encryption)');
+ return true;
+
+ } catch (Exception $fallbackError) {
+ Log::error('SMTP connection test failed completely', [
+ 'original_error' => $e->getMessage(),
+ 'fallback_error' => $fallbackError->getMessage()
+ ]);
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Dapatkan instance PHPMailer
+ *
+ * @return PHPMailer
+ */
+ public function getMailer(): PHPMailer
+ {
+ return $this->mailer;
+ }
+}
diff --git a/resources/views/statements/email.blade.php b/resources/views/statements/email.blade.php
index ae4665c..5589b70 100644
--- a/resources/views/statements/email.blade.php
+++ b/resources/views/statements/email.blade.php
@@ -89,25 +89,21 @@
Silahkan gunakan password Electronic Statement Anda untuk membukanya.
Password standar Elektronic Statement ini adalah ddMonyyyyxx (contoh: 01Aug1970xx)
dimana :
-
- - dd : 2 digit tanggal lahir anda, contoh: 01
- - Mon :
+
+ - - dd : 2 digit tanggal lahir anda, contoh: 01
+ - - Mon :
3 huruf pertama bulan lahir anda dalam bahasa Ingris. Huruf pertama adalah
huruf besar dan selanjutnya huruf kecil, contoh : Aug
- - yyyy : 4 digit tahun kelahiran anda, contoh : 1970
- - xx : 2 digit terakhir dari nomer rekening anda, contoh : 12
+ - - yyyy : 4 digit tahun kelahiran anda, contoh : 1970
+ - - xx : 2 digit terakhir dari nomer rekening anda, contoh : 12
Terima Kasih,
Bank Artha Graha Internasional
- ------------------------------
-
- ------------------------------
-
- --------
+ ------------------------------------------------------------
Kami sangat menghargai masukan dan saran Anda untuk meningkatkan layanan dan produk kami.
Untuk memberikan masukan, silakan hubungi GrahaCall 24 Jam kami di
0-800-191-8880.
@@ -118,25 +114,21 @@
Please use your Electronic Statement password to open it.
The Electronic Statement standard password is ddMonyyyyxx (example: 01Aug1970xx) where:
-
- - dd : The first 2 digits of your birthdate, example: 01
- - Mon :
+
+ - - dd : The first 2 digits of your birthdate, example: 01
+ - - Mon :
The first 3 letters of your birth month in English. The first letter is
uppercase and the rest are lowercase, example: Aug
- - yyyy : 4 digit of your birth year, example: 1970
- - xx : The last 2 digits of your account number, example: 12.
+ - - yyyy : 4 digit of your birth year, example: 1970
+ - - xx : The last 2 digits of your account number, example: 12.
Regards,
Bank Artha Graha Internasional
- ------------------------------
-
- ------------------------------
-
- --------
+ ------------------------------------------------------------
We welcome any feedback or suggestions to improve our product and services.
If you have any feedback, please contact our GrahaCall 24 Hours at
0-800-191-8880.