From d1e58ec94cebe93e04112f024b2f6ba12118f0fe Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Mon, 1 Jun 2026 22:29:17 +0000 Subject: [PATCH 1/9] log errors when attaching invoices to emails --- app/app/Helpers/WorkspaceInvoiceHelper.php | 37 ++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/app/app/Helpers/WorkspaceInvoiceHelper.php b/app/app/Helpers/WorkspaceInvoiceHelper.php index 7b7beb78a..4013a236a 100644 --- a/app/app/Helpers/WorkspaceInvoiceHelper.php +++ b/app/app/Helpers/WorkspaceInvoiceHelper.php @@ -63,22 +63,27 @@ public static function sendInvoiceForWorkspace(Workspace $workspace, $period, Us $filePrefix = 'monthly'; } - Mail::send('emails.workspace_invoices', [ - 'user' => $owner, - 'workspace' => $workspace, - 'site' => $siteName, - 'site_name' => $siteName, - 'customizations' => $customizations, - 'period' => $period - ], function ($message) use ($owner, $mailConfig, $subject, $pdf, $invoice, $filePrefix) { - $message->to($owner->email); - $message->subject($subject); - $from = $mailConfig['from']; - $message->from($from['address'], $from['name']); - $message->attachData($pdf, sprintf('%s_invoice_%s.pdf', $filePrefix, $invoice->invoice_no), [ - 'mime' => 'application/pdf' - ]); - }); + try { + Mail::send('emails.workspace_invoices', [ + 'user' => $owner, + 'workspace' => $workspace, + 'site' => $siteName, + 'site_name' => $siteName, + 'customizations' => $customizations, + 'period' => $period + ], function ($message) use ($owner, $mailConfig, $subject, $pdf, $invoice, $filePrefix) { + $message->to($owner->email); + $message->subject($subject); + $from = $mailConfig['from']; + $message->from($from['address'], $from['name']); + $message->attachData($pdf, sprintf('%s_invoice_%s.pdf', $filePrefix, $invoice->invoice_no), [ + 'mime' => 'application/pdf' + ]); + }); + } catch (\Exception $e) { + \Log::error('Failed to send invoice email for workspace #' . $workspace->id . ': ' . $e->getMessage()); + throw $e; + } return [ 'workspace_id' => $workspace->id, From 5904c1a124010f595af1853170a651d2f147d28e Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 18:30:35 +0000 Subject: [PATCH 2/9] add function to get available service plans --- app/app/Enums/ServicePlanStatus.php | 25 +++++++++++++++++++ .../Admin/ServicePlanController.php | 9 +++++-- app/app/Http/Controllers/HomeController.php | 6 +---- app/app/Http/Controllers/MergedController.php | 3 ++- app/app/ServicePlan.php | 14 +++++++++++ app/resources/lang/en/admin/serviceplans.php | 3 ++- .../admin/serviceplan/create_edit.blade.php | 10 ++++++++ 7 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 app/app/Enums/ServicePlanStatus.php diff --git a/app/app/Enums/ServicePlanStatus.php b/app/app/Enums/ServicePlanStatus.php new file mode 100644 index 000000000..2ebcfa9cd --- /dev/null +++ b/app/app/Enums/ServicePlanStatus.php @@ -0,0 +1,25 @@ +getFeatureOptions(); $callDurations = $this->getCallDurations(); $recordingSpace = $this->getRecordingSpaceOptions(); - return view('admin.serviceplan.create_edit', compact('features', 'callDurations', 'recordingSpace')); + $statuses = ServicePlanStatus::all(); + + return view('admin.serviceplan.create_edit', compact('features', 'callDurations', 'recordingSpace', 'statuses')); } /** @@ -86,12 +90,13 @@ public function edit(ServicePlan $serviceplan) $features = $this->getFeatureOptions(); $callDurations = $this->getCallDurations(); $recordingSpace = $this->getRecordingSpaceOptions(); + $statuses = ServicePlanStatus::all(); $migratePlans = []; $allPlans = ServicePlan::where('id', '!=', $serviceplan->id)->get()->toArray(); foreach ($allPlans as $key => $plan) { $migratePlans[$plan['nice_name']] = $plan['nice_name'] . ' (' . $plan['key_name'] . ')'; } - return view('admin.serviceplan.create_edit', compact('serviceplan', 'features', 'callDurations', 'recordingSpace', 'migratePlans')); + return view('admin.serviceplan.create_edit', compact('serviceplan', 'features', 'callDurations', 'recordingSpace', 'migratePlans', 'statuses')); } /** * Migrate all users on this service plan to a target service plan. diff --git a/app/app/Http/Controllers/HomeController.php b/app/app/Http/Controllers/HomeController.php index f0c00b8b1..5b3ff313f 100755 --- a/app/app/Http/Controllers/HomeController.php +++ b/app/app/Http/Controllers/HomeController.php @@ -69,11 +69,7 @@ public function index() } public function pricing(Request $request) { - $plans = ServicePlan::where('include_in_pricing_pages', '1') - ->orderBy('rank') - ->orderBy('nice_name') - ->get(); - $plans = ServicePlan::sortPlansByFeatures($plans); + $plans = ServicePlan::getAvailablePlans(TRUE); $competitors = Competitor::all(); $savings = CostSaving::select(array('cost_savings.*', DB::raw('competitors.name AS competitor_name'))); $savings = $savings->join('competitors', 'competitors.id', '=', 'cost_savings.competitor_id'); diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index d009173f3..5afc144ea 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -873,7 +873,8 @@ public function getAllSettings(Request $request) { } public function getServicePlans(Request $request) { - $plans = ServicePlan::orderBy('rank') + $plans = ServicePlan::where('status', ServicePlanStatus::ACTIVE) + ->orderBy('rank') ->orderBy('nice_name') ->get() ->toArray(); diff --git a/app/app/ServicePlan.php b/app/app/ServicePlan.php index 4ce15ccbf..60ca0d310 100755 --- a/app/app/ServicePlan.php +++ b/app/app/ServicePlan.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use App\ApiResource; use App\Helpers\MainHelper; +use App\Enums\ServicePlanStatus; class ServicePlan extends Model { use SoftDeletes; @@ -97,4 +98,17 @@ public static function getRecurringMembershipPlans() { return self::where('pay_as_you_go', '0')->get(); } + public static function getAvailablePlans($pricingPage = false) { + $plans = ServicePlan::where('status', ServicePlanStatus::ACTIVE) + ->orderBy('rank') + ->orderBy('nice_name'); + if ($pricingPage) { + $plans = $plans->where('include_in_pricing_pages', '1'); + } + + return $plans->get(); + } + + + } diff --git a/app/resources/lang/en/admin/serviceplans.php b/app/resources/lang/en/admin/serviceplans.php index 1fab0f52a..6c706d4b8 100755 --- a/app/resources/lang/en/admin/serviceplans.php +++ b/app/resources/lang/en/admin/serviceplans.php @@ -30,5 +30,6 @@ 'minutes_per_month' => 'Call Minutes per month', 'migrate_users' => 'Migrate users', 'migrate_warning' => 'Warning: You cannot reverse this change. Users will be moved over to the new plan in the next billing cycle.', - 'select_plan' => 'Select plan' + 'select_plan' => 'Select plan', + 'status' => 'Status', ]; \ No newline at end of file diff --git a/app/resources/views/admin/serviceplan/create_edit.blade.php b/app/resources/views/admin/serviceplan/create_edit.blade.php index d971037e2..b511bbed3 100755 --- a/app/resources/views/admin/serviceplan/create_edit.blade.php +++ b/app/resources/views/admin/serviceplan/create_edit.blade.php @@ -81,6 +81,16 @@ +
+ {!! Form::label('status', trans("admin/serviceplans.status"), array('class' => 'control-label')) !!} +
+ {!! Form::select('status', array_combine($statuses, $statuses), isset($serviceplan) ? $serviceplan->status : null, ['class' => 'form-control']) !!} + {{ $errors->first('status', ':message') }} +
+
+ + + @foreach ( $features as $feature )
{!! form::label($feature['key'], trans("admin/serviceplans." . $feature['key']), array('class' => 'control-label')) !!} From 1faef21c7ed298c61d8dffff8de1c99d1f3bbe39 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 18:36:52 +0000 Subject: [PATCH 3/9] fix some plan sorting issues --- app/app/Http/Controllers/MergedController.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index 5afc144ea..969785711 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -873,11 +873,7 @@ public function getAllSettings(Request $request) { } public function getServicePlans(Request $request) { - $plans = ServicePlan::where('status', ServicePlanStatus::ACTIVE) - ->orderBy('rank') - ->orderBy('nice_name') - ->get() - ->toArray(); + $plans = ServicePlan::getAvailablePlans(); $features = [ 'fax', @@ -917,7 +913,7 @@ public function getServicePlans(Request $request) { $plan_benefits = []; // compare the previous plan with the current one to get the benefits $last_cnt = $cnt - 1; - if ( array_key_exists( $last_cnt, $plans) ) { + if ( isset( $plans[$last_cnt] ) ) { $last_plan = $plans[$last_cnt]; foreach ($features as $feature) { $has_feature_1 = $plan[$feature]; From 5ff102a86da2d3a45de1fc8acb76ee8e53885f5c Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 20:15:46 +0000 Subject: [PATCH 4/9] send back more data in billing APIs --- app/app/Http/Controllers/MergedController.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index 969785711..8ffc5f784 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -205,10 +205,19 @@ public function upgradePlan(Request $request) // 1. Get the current subscription and the target plan $subscription = Subscription::where('workspace_id', $workspace->id)->first(); + + if (!$subscription) { + return $this->response->errorBadRequest("Invalid subscription or plan."); + } + + if (!empty($subscription->scheduled_plan_id)) { + return $this->response->errorBadRequest("A plan upgrade is already scheduled. Cannot upgrade again until it takes effect."); + } + $newPlan = ServicePlan::where('key_name', $planKey)->first(); $oldPlan = ServicePlan::find($subscription->current_plan_id); - if (!$subscription || !$newPlan) { + if (!$newPlan) { return $this->response->errorBadRequest("Invalid subscription or plan."); } @@ -256,7 +265,7 @@ public function upgradePlan(Request $request) // 4. Update Subscription and Workspace $subscription->update([ - 'current_plan_id' => $newPlan->id, + 'scheduled_plan_id' => $newPlan->id, 'updated_at' => $now ]); @@ -467,7 +476,8 @@ public function billing(Request $request) $cards, $config, $billingHistory, - $triggers + $triggers, + $subscription->toArray() ]); } public function getCountries() { From 70126e72a1eb4785499fd5752d12f4518a262000 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 20:43:20 +0000 Subject: [PATCH 5/9] improvements to billing code --- app/app/Http/Controllers/MergedController.php | 10 +++++- app/app/Subscription.php | 11 +++++++ .../2026_06_02_204015_add_source_service.php | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 app/database/migrations/2026_06_02_204015_add_source_service.php diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index 8ffc5f784..0c4dfb320 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -312,7 +312,15 @@ public function dashboard(Request $request) $workspace = $this->getWorkspace($request); //$plans = \Config::get("service_plans"); //$plan = $plans[ $workspace->plan ]; - $plan = ServicePlan::where('key_name', $workspace->plan)->firstOrFail(); + $subscription = Subscription::where('workspace_id', $workspace->id)->first(); + $currentPlan = ServicePlan::find($subscription->current_plan_id); + $scheduledPlan = ServicePlan::find($subscription->scheduled_plan_id); + $effectivePlan = $currentPlan; + if (!empty($scheduledPlan)) { + $effectivePlan = $scheduledPlan; + } + + $plan = $effectivePlan; while ($currentDay != $dayCount) { $labels[] = $cloned1->format("M-d"); $cloned2 = clone $cloned1; diff --git a/app/app/Subscription.php b/app/app/Subscription.php index 7d50598a3..cea0a4ebc 100755 --- a/app/app/Subscription.php +++ b/app/app/Subscription.php @@ -16,6 +16,17 @@ class Subscription extends Model { protected $table = "subscriptions"; protected $casts = array( ); + + public function toArray() + { + $array = parent::toArray(); + if (!empty($this->scheduled_plan_id)) { + $array['effective_plan_id'] = $this->scheduled_plan_id; + } else { + $array['effective_plan_id'] = $this->current_plan_id; + } + return $array; + } } diff --git a/app/database/migrations/2026_06_02_204015_add_source_service.php b/app/database/migrations/2026_06_02_204015_add_source_service.php new file mode 100644 index 000000000..c384bd5c3 --- /dev/null +++ b/app/database/migrations/2026_06_02_204015_add_source_service.php @@ -0,0 +1,33 @@ +string('source_service')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users_invoices', function (Blueprint $table) { + // + $table->dropColumn('source_service'); + }); + } +} From fa9b77daaf5213072492757a57779c3bffa80d41 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 20:46:00 +0000 Subject: [PATCH 6/9] set source_service when creating invoices --- app/app/Helpers/WorkspaceInvoiceHelper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/app/Helpers/WorkspaceInvoiceHelper.php b/app/app/Helpers/WorkspaceInvoiceHelper.php index 4013a236a..c7cd7c681 100644 --- a/app/app/Helpers/WorkspaceInvoiceHelper.php +++ b/app/app/Helpers/WorkspaceInvoiceHelper.php @@ -262,7 +262,8 @@ private static function createInvoice(User $owner, Workspace $workspace, $period 'recording_costs' => $recordingCosts, 'fax_costs' => $faxCosts, 'membership_costs' => $membershipCosts, - 'number_costs' => $numberCosts + 'number_costs' => $numberCosts, + 'source_service' => 'SITE' ]); $invoice->created_at = $rangeStart; From 0389fc7ffbf43f2490b524b9ba713eee9bc2afd0 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 22:20:17 +0000 Subject: [PATCH 7/9] further integrate code for handling plan upgrades. --- app/app/Http/Controllers/MergedController.php | 134 ++++++++++++------ 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index 0c4dfb320..e3928faef 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -197,13 +197,20 @@ public function plans(Request $request) public function upgradePlan(Request $request) { $json = $request->json()->all(); - $planKey = $json['plan']; + $planKey = null; + if (isset($json['plan'])) { + $planKey = $json['plan']; + } + + if (!$planKey) { + return $this->response->errorBadRequest("Plan key is required."); + } $workspace = $this->getWorkspace($request); $user = $this->getUser($request); $now = new \DateTime(); - // 1. Get the current subscription and the target plan + // 1. Fetch data & early guard clauses $subscription = Subscription::where('workspace_id', $workspace->id)->first(); if (!$subscription) { @@ -211,80 +218,119 @@ public function upgradePlan(Request $request) } if (!empty($subscription->scheduled_plan_id)) { - return $this->response->errorBadRequest("A plan upgrade is already scheduled. Cannot upgrade again until it takes effect."); + return $this->response->errorBadRequest("A plan upgrade is already scheduled."); } $newPlan = ServicePlan::where('key_name', $planKey)->first(); $oldPlan = ServicePlan::find($subscription->current_plan_id); - if (!$newPlan) { - return $this->response->errorBadRequest("Invalid subscription or plan."); + if (!$newPlan || !$oldPlan) { + return $this->response->errorBadRequest("Invalid subscription or plan config."); } - // 2. Calculate Proration - // We determine the daily rate difference and multiply by days remaining - $periodEnd = new \DateTime($subscription->current_period_end); - $remainingDays = $now->diff($periodEnd)->days; - - // Determine the total days in this cycle to get an accurate daily ratio + // 2. Determine start date cleanly without a ternary $start_date = $subscription->last_billed_at; if (!$start_date) { $start_date = $subscription->created_at; } + + // 3. Proration Calculation using Timestamp Precision + $periodEnd = new \DateTime($subscription->current_period_end); $lastBilled = new \DateTime($start_date); - $totalCycleDays = $lastBilled->diff($periodEnd)->days; - if ($totalCycleDays == 0) { - $totalCycleDays = 30; // Default to 30 if zero + + $totalCycleSeconds = $periodEnd->getTimestamp() - $lastBilled->getTimestamp(); + $remainingSeconds = $periodEnd->getTimestamp() - $now->getTimestamp(); + + if ($totalCycleSeconds <= 0) { + $totalCycleSeconds = 30 * 86400; // Default fallback to 30 days in seconds } - if ($subscription->billing_cycle === 'ANNUAL') { + // Explicit pricing logic without ternaries + $isAnnual = ($subscription->billing_cycle === 'ANNUAL'); + if ($isAnnual) { $oldPrice = $oldPlan->annual_cost_cents; $newPrice = $newPlan->annual_cost_cents; } else { $oldPrice = $oldPlan->monthly_cost_cents; $newPrice = $newPlan->monthly_cost_cents; } - + $priceDiff = $newPrice - $oldPrice; $prorationCents = 0; - if ($priceDiff > 0) { - $prorationCents = ($priceDiff * $remainingDays) / $totalCycleDays; + if ($priceDiff > 0 && $remainingSeconds > 0) { + $prorationCents = ($priceDiff * $remainingSeconds) / $totalCycleSeconds; + } + + // 4. Determine Scheduled Effective Date without ternaries + $scheduledEffectiveDate = null; + $customizations = CustomizationsKVStore::getRecord(); + + $billingFlow = 'ANNIVERSARY'; + if (isset($customizations['billing_flow'])) { + $billingFlow = $customizations['billing_flow']; + } + + if ($billingFlow === 'ANNUAL') { + if ($isAnnual) { + $modifier = 'first day of january next year'; + } else { + $modifier = 'first day of next month'; + } + $scheduledEffectiveDate = (clone $now)->modify($modifier)->setTime(0, 0, 0); + } else { + $anchor_date = $subscription->last_billed_at; + if (!$anchor_date) { + $anchor_date = $subscription->created_at; + } + $anchorDateTime = new \DateTime($anchor_date); - // 3. Create the pending line item (orphan item with invoice_id = NULL) - UserInvoiceLineItem::create([ - 'created_at' => $now, - 'updated_at' => $now, - 'is_recurring' => 0, - 'name' => "Upgrade Adjustment: {$oldPlan->name} to {$newPlan->name}", - 'cents' => (int) $prorationCents, - 'invoice_id' => null, - 'key_name' => 'plan_upgrade_proration' - ]); + if ($isAnnual) { + $modifier = '+1 year'; + } else { + $modifier = '+1 month'; + } + $scheduledEffectiveDate = (clone $anchorDateTime)->modify($modifier); } - // 4. Update Subscription and Workspace - $subscription->update([ - 'scheduled_plan_id' => $newPlan->id, - 'updated_at' => $now - ]); + // 5. Atomic Database Changes + DB::transaction(function () use ($now, $workspace, $subscription, $newPlan, $oldPlan, $prorationCents, $scheduledEffectiveDate, $planKey) { + + if ($prorationCents > 0) { + UserInvoiceLineItem::create([ + 'created_at' => $now, + 'updated_at' => $now, + 'is_recurring' => 0, + 'name' => "Upgrade Adjustment: {$oldPlan->name} to {$newPlan->name}", + 'cents' => (int) round($prorationCents), + 'invoice_id' => null, + 'workspace_id' => $workspace->id, // Added for reliable audit trail + 'key_name' => 'plan_upgrade_proration' + ]); + } - // Update Plan Usage Periods - PlanUsagePeriod::where('workspace_id', $workspace->id) - ->whereNull('ended_at') - ->update(['ended_at' => $now]); + $subscription->update([ + 'scheduled_plan_id' => $newPlan->id, + 'scheduled_effective_date' => $scheduledEffectiveDate->format('Y-m-d H:i:s'), + 'updated_at' => $now + ]); - PlanUsagePeriod::create([ - 'workspace_id' => $workspace->id, - 'started_at' => $now, - 'plan' => $planKey - ]); + PlanUsagePeriod::where('workspace_id', $workspace->id) + ->whereNull('ended_at') + ->update(['ended_at' => $now]); + + PlanUsagePeriod::create([ + 'workspace_id' => $workspace->id, + 'started_at' => $now, + 'plan' => $planKey + ]); + }); - // Analytics and Communication + // 6. Side Effect Operations (Ideally should be queued background jobs) $props = [ "billing_status" => "immediate_upgrade_pending_reconciliation", "new_plan" => $planKey, - "proration_applied" => $prorationCents + "proration_applied" => (int) round($prorationCents) ]; WorkspaceEvent::addEvent($workspace, 'PLAN_UPGRADED', $props); From d31488f86ca612a4edd020df710d5263e97943b6 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 2 Jun 2026 22:32:22 +0000 Subject: [PATCH 8/9] dispatch event to upgrade workspace in upgrade API --- app/app/Helpers/RabbitMQHelper.php | 17 +++++++- app/app/Http/Controllers/MergedController.php | 40 ++++--------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/app/app/Helpers/RabbitMQHelper.php b/app/app/Helpers/RabbitMQHelper.php index 43d752aa4..45eaa9d7d 100755 --- a/app/app/Helpers/RabbitMQHelper.php +++ b/app/app/Helpers/RabbitMQHelper.php @@ -201,5 +201,20 @@ public static function dispatchAnnualInvoiceTask($workspaceIdOrPayload, $trigger return self::publish(self::INVOICE_QUEUE_ANNUAL, $payload); } - + + public static function dispatchWorkspaceUpgrade($workspaceId, $upgradeFee, $currentPlan, $scheduledPlan, $scheduledEffectiveDate) + { + $payload = [ + 'run_id' => 'workspace_upgrade_' . (int) $workspaceId . '_' . time(), + 'workspace_id' => (int) $workspaceId, + 'upgrade_fee' => (int) $upgradeFee, + 'current_plan' => (int) $currentPlan, + 'scheduled_plan' => (int) $scheduledPlan, + 'scheduled_effective_date' => (string) $scheduledEffectiveDate, + ]; + + return self::publish('workspace_upgrades', $payload); + } + + } diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index e3928faef..995ed7d5f 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -56,6 +56,7 @@ use App\Helpers\DNSHelper; use App\Helpers\PhoneProvisionHelper; use App\Helpers\BillingDataHelper; +use App\Helpers\RabbitMQHelper; use App\Helpers\PortalSearchHelper; use App\Helpers\SMSHelper; @@ -293,38 +294,13 @@ public function upgradePlan(Request $request) $scheduledEffectiveDate = (clone $anchorDateTime)->modify($modifier); } - // 5. Atomic Database Changes - DB::transaction(function () use ($now, $workspace, $subscription, $newPlan, $oldPlan, $prorationCents, $scheduledEffectiveDate, $planKey) { - - if ($prorationCents > 0) { - UserInvoiceLineItem::create([ - 'created_at' => $now, - 'updated_at' => $now, - 'is_recurring' => 0, - 'name' => "Upgrade Adjustment: {$oldPlan->name} to {$newPlan->name}", - 'cents' => (int) round($prorationCents), - 'invoice_id' => null, - 'workspace_id' => $workspace->id, // Added for reliable audit trail - 'key_name' => 'plan_upgrade_proration' - ]); - } - - $subscription->update([ - 'scheduled_plan_id' => $newPlan->id, - 'scheduled_effective_date' => $scheduledEffectiveDate->format('Y-m-d H:i:s'), - 'updated_at' => $now - ]); - - PlanUsagePeriod::where('workspace_id', $workspace->id) - ->whereNull('ended_at') - ->update(['ended_at' => $now]); - - PlanUsagePeriod::create([ - 'workspace_id' => $workspace->id, - 'started_at' => $now, - 'plan' => $planKey - ]); - }); + RabbitMQHelper::dispatchWorkspaceUpgrade( + $workspace->id, + (int) round($prorationCents), + $subscription->current_plan_id, + $newPlan->id, + $scheduledEffectiveDate ? $scheduledEffectiveDate->format('Y-m-d H:i:s') : null + ); // 6. Side Effect Operations (Ideally should be queued background jobs) $props = [ From 2122881ea8a7e7dd743cdf6588b3bfb6a5e35301 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Wed, 3 Jun 2026 17:07:19 +0000 Subject: [PATCH 9/9] send subscription ID in workspace upgrade event --- app/app/Helpers/RabbitMQHelper.php | 3 ++- app/app/Http/Controllers/MergedController.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/app/Helpers/RabbitMQHelper.php b/app/app/Helpers/RabbitMQHelper.php index 45eaa9d7d..974fded96 100755 --- a/app/app/Helpers/RabbitMQHelper.php +++ b/app/app/Helpers/RabbitMQHelper.php @@ -202,11 +202,12 @@ public static function dispatchAnnualInvoiceTask($workspaceIdOrPayload, $trigger return self::publish(self::INVOICE_QUEUE_ANNUAL, $payload); } - public static function dispatchWorkspaceUpgrade($workspaceId, $upgradeFee, $currentPlan, $scheduledPlan, $scheduledEffectiveDate) + public static function dispatchWorkspaceUpgrade($workspaceId, $upgradeFee, $subscriptionId, $currentPlan, $scheduledPlan, $scheduledEffectiveDate) { $payload = [ 'run_id' => 'workspace_upgrade_' . (int) $workspaceId . '_' . time(), 'workspace_id' => (int) $workspaceId, + 'subscription_id' => (int) $subscriptionId, 'upgrade_fee' => (int) $upgradeFee, 'current_plan' => (int) $currentPlan, 'scheduled_plan' => (int) $scheduledPlan, diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php index 995ed7d5f..53a2efd65 100755 --- a/app/app/Http/Controllers/MergedController.php +++ b/app/app/Http/Controllers/MergedController.php @@ -297,6 +297,7 @@ public function upgradePlan(Request $request) RabbitMQHelper::dispatchWorkspaceUpgrade( $workspace->id, (int) round($prorationCents), + $subscription->id, $subscription->current_plan_id, $newPlan->id, $scheduledEffectiveDate ? $scheduledEffectiveDate->format('Y-m-d H:i:s') : null