Skip to content

Commit

Permalink
Enhance Invoice Reminder System with Upcoming and Overdue
Browse files Browse the repository at this point in the history
  • Loading branch information
sweep-ai[bot] authored Dec 25, 2024
1 parent e640d10 commit b3e5cfa
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 46 deletions.
37 changes: 37 additions & 0 deletions app/Console/Commands/ProcessInvoiceReminders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@


<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\BillingService;

class ProcessInvoiceReminders extends Command
{
protected $signature = 'invoices:process-reminders';
protected $description = 'Process invoice reminders for upcoming and overdue invoices';

protected $billingService;

public function __construct(BillingService $billingService)
{
parent::__construct();
$this->billingService = $billingService;
}

public function handle()
{
$this->info('Processing invoice reminders...');

// Process upcoming invoice reminders
$upcomingCount = $this->billingService->sendUpcomingInvoiceReminders();
$this->info("Sent {$upcomingCount} upcoming invoice reminders");

// Process overdue invoice reminders
$overdueCount = $this->billingService->sendOverdueReminders();
$this->info("Sent {$overdueCount} overdue invoice reminders");

return Command::SUCCESS;
}
}
5 changes: 1 addition & 4 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ protected function schedule(Schedule $schedule): void
$billingService->processRecurringBilling();
})->daily();

$schedule->call(function () {
$billingService = new BillingService();
$billingService->sendOverdueReminders();
})->daily();
$schedule->command('invoices:process-reminders')->daily();

$schedule->call(function () {
$reports = Report::whereNotNull('schedule')->get();
Expand Down
41 changes: 41 additions & 0 deletions app/Mail/UpcomingInvoiceReminder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@


<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class UpcomingInvoiceReminder extends Mailable
{
use Queueable, SerializesModels;

public $data;
public $template;

public function __construct($data, $template)
{
$this->data = $data;
$this->template = $template;
}

public function build()
{
return $this->subject($this->parseTemplate($this->template->subject))
->view('emails.upcoming-invoice-reminder')
->with([
'content' => $this->parseTemplate($this->template->body),
'data' => $this->data
]);
}

private function parseTemplate($text)
{
foreach ($this->data as $key => $value) {
$text = str_replace('{{'.$key.'}}', $value, $text);
}
return $text;
}
}
108 changes: 66 additions & 42 deletions app/Services/BillingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@
use App\Models\UsageRecord;
use App\Services\PaymentGatewayService;
use App\Services\PricingService;
use App\Services\EmailNotificationService;
use App\Mail\OverdueInvoiceReminder;
use App\Mail\PaymentConfirmation;
use Carbon\Carbon;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
use App\Mail\OverdueInvoiceReminder;

class BillingService
{
Expand All @@ -29,38 +26,17 @@ class BillingService
protected $pricingService;


protected $emailService;

public function __construct(
ServiceProvisioningService $serviceProvisioningService,
CurrencyService $currencyService,
PaymentPlanService $paymentPlanService = null,
PaymentGatewayService $paymentGatewayService = null,
PricingService $pricingService = null,
EmailNotificationService $emailService = null
PaymentGatewayService $paymentGatewayService = null
PricingService $pricingService = null
) {
$this->serviceProvisioningService = $serviceProvisioningService;
$this->currencyService = $currencyService;
$this->paymentPlanService = $paymentPlanService ?? new PaymentPlanService($this);
$this->paymentGatewayService = $paymentGatewayService ?? new PaymentGatewayService();
$this->pricingService = $pricingService ?? new PricingService();
$this->emailService = $emailService ?? new EmailNotificationService();
}

protected function sendOverdueReminderEmail(Invoice $invoice)
{
$customer = $invoice->customer;
$reminder = new OverdueInvoiceReminder($invoice);

return $this->emailService->send($reminder, $customer->email);
}

protected function sendPaymentConfirmation(Payment $payment)
{
$customer = $payment->invoice->customer;
$confirmation = new PaymentConfirmation($payment);

return $this->emailService->send($confirmation, $customer->email);
}

public function createSubscription(Customer $customer, SubscriptionPlan $plan, string $billingCycle)
Expand Down Expand Up @@ -430,8 +406,62 @@ public function processAutomaticPayment(Invoice $invoice)
}
}

public function sendUpcomingInvoiceReminders()
{
$reminderCount = 0;
$teams = Team::all();

foreach ($teams as $team) {
$settings = ReminderSetting::where('team_id', $team->id)
->where('is_active', true)
->first();

if (!$settings) {
continue;
}

$upcomingInvoices = Invoice::where('team_id', $team->id)
->where('status', 'pending')
->where('due_date', '>', Carbon::now())
->where('due_date', '<=', Carbon::now()->addDays($settings->days_before_reminder))
->whereNull('upcoming_reminder_sent')
->get();

foreach ($upcomingInvoices as $invoice) {
$this->sendUpcomingInvoiceEmail($invoice);
$invoice->update(['upcoming_reminder_sent' => true]);
$reminderCount++;
}
}

return $reminderCount;
}

private function sendUpcomingInvoiceEmail(Invoice $invoice)
{
$customer = $invoice->customer;
$template = EmailTemplate::where('type', 'upcoming_invoice')
->where(function($query) use ($invoice) {
$query->where('team_id', $invoice->team_id)
->orWhere('is_default', true);
})
->first();

$data = [
'customer_name' => $customer->name,
'invoice_number' => $invoice->invoice_number,
'due_date' => $invoice->due_date->format('Y-m-d'),
'amount' => $invoice->total_amount,
'currency' => $invoice->currency,
];

Mail::to($customer->email)
->queue(new UpcomingInvoiceReminder($data, $template));
}

public function sendOverdueReminders()
{
$reminderCount = 0;
$teams = Team::all();

foreach ($teams as $team) {
Expand All @@ -447,42 +477,36 @@ public function sendOverdueReminders()
->where('due_date', '<', Carbon::now())
->where('status', 'pending')
->where(function ($query) use ($settings) {
// Only get invoices that haven't exceeded max reminders
$query->whereNull('reminder_count')
->orWhere('reminder_count', '<', $settings->max_reminders);
})
->where(function ($query) use ($settings) {
// Only get invoices that are due for a reminder
$query->whereNull('last_reminder_date')
->orWhere('last_reminder_date', '<=',
Carbon::now()->subDays($settings->reminder_frequency));
})
->get();

foreach ($overdueInvoices as $invoice) {
// Apply late fee
$invoice->applyLateFee();

// Send reminder email
$this->sendOverdueReminderEmail($invoice);

$invoice->update([
'reminder_count' => ($invoice->reminder_count ?? 0) + 1,
'last_reminder_date' => Carbon::now()
]);

// Suspend service if applicable
$this->serviceProvisioningService->manageService($invoice->subscription, 'suspend');

$reminderCount++;
}
$overdueInvoices = Invoice::where('due_date', '<', Carbon::now())
->where('status', 'pending')
->get();

foreach ($overdueInvoices as $invoice) {
// Apply late fee
$invoice->applyLateFee();

// Send overdue reminder email
$this->sendOverdueReminderEmail($invoice);

// Suspend service if applicable
$this->serviceProvisioningService->manageService($invoice->subscription, 'suspend');
}

return $reminderCount;
}

public function processLateFees()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddUpcomingReminderSentToInvoices extends Migration
{
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
$table->boolean('upcoming_reminder_sent')->nullable();
});
}

public function down()
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('upcoming_reminder_sent');
});
}
}

0 comments on commit b3e5cfa

Please sign in to comment.