diff --git a/app/app/Console/Commands/RabbitMQEventConsumer.php b/app/app/Console/Commands/RabbitMQEventConsumer.php
index cf3898d0d..d4a4319f5 100644
--- a/app/app/Console/Commands/RabbitMQEventConsumer.php
+++ b/app/app/Console/Commands/RabbitMQEventConsumer.php
@@ -13,6 +13,7 @@
use App\WorkspaceUser;
use App\Mail\WorkspaceSuspendedNotification;
use Carbon\Carbon;
+use App\ServicePlan;
use Exception;
use Log;
@@ -51,6 +52,7 @@ public function handle()
$channel->queue_declare('subscription_updates', false, true, false, false);
$channel->queue_declare('payment_receipts', false, true, false, false);
$channel->queue_declare('call_quality_surveys', false, true, false, false);
+ $channel->queue_declare('workspace_upgrades', false, true, false, false);
$channel->queue_declare(self::CALL_ACTIVITY_QUEUE, false, true, false, false);
$channel->queue_declare(RabbitMQHelper::INVOICE_QUEUE_MONTHLY, false, true, false, false);
$channel->queue_declare(RabbitMQHelper::INVOICE_QUEUE_ANNUAL, false, true, false, false);
@@ -78,6 +80,8 @@ public function handle()
$channel->basic_consume('subscription_updates', '', false, false, false, false, [$this, 'handleSubscriptionUpdate']);
$channel->basic_consume('payment_receipts', '', false, false, false, false, [$this, 'handlePaymentReceipt']);
$channel->basic_consume('call_quality_surveys', '', false, false, false, false, [$this, 'handleSurvey']);
+ $channel->basic_consume('workspace_upgrades', '', false, false, false, false, [$this, 'handleWorkspaceUpgrade']);
+
$channel->basic_consume(self::CALL_ACTIVITY_QUEUE, '', false, false, false, false, [$this, 'handleCallActivity']);
$channel->basic_consume(RabbitMQHelper::INVOICE_QUEUE_MONTHLY, '', false, false, false, false, [$this, 'handleMonthlyInvoiceTask']);
$channel->basic_consume(RabbitMQHelper::INVOICE_QUEUE_ANNUAL, '', false, false, false, false, [$this, 'handleAnnualInvoiceTask']);
@@ -604,6 +608,93 @@ private function logConsumerError($message, array $context = [])
Log::error($message, $context);
}
+ /**
+ * Handler for Workspace Upgrades
+ */
+ public function handleWorkspaceUpgrade($msg)
+ {
+ try {
+ $data = json_decode($msg->body, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
+ $this->logConsumerError(' [WORKSPACE_UPGRADE] Malformed JSON: ' . json_last_error_msg(), [
+ 'body' => $msg->body
+ ]);
+ $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
+ return;
+ }
+
+ if (!isset($data['workspace_id']) || !isset($data['creator_id'])) {
+ $this->logConsumerError(' [WORKSPACE_UPGRADE] Missing workspace_id or creator_id.', [
+ 'payload' => $data
+ ]);
+ $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
+ return;
+ }
+
+ $workspaceId = (int) $data['workspace_id'];
+ $userId = (int) $data['creator_id'];
+
+ $this->logConsumerInfo(' [WORKSPACE_UPGRADE] Entire payload:' . json_encode(['payload' => $data]));
+
+ if ($workspaceId <= 0 || $userId <= 0) {
+ $this->logConsumerError(' [WORKSPACE_UPGRADE] Invalid workspace_id or creator_id.', [
+ 'payload' => $data
+ ]);
+ $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
+ return;
+ }
+
+ $this->info(" [WORKSPACE_UPGRADE] Received upgrade for workspace ID: " . $workspaceId);
+
+ $action = isset($data['action']) ? $data['action'] : null;
+ $this->info(" [WORKSPACE_UPGRADE] Action: " . $action);
+
+ switch ($action) {
+ case 'SUCCESSFUL_UPGRADE':
+ $this->info(" [WORKSPACE_UPGRADE] Processing successful upgrade for workspace ID: " . $workspaceId);
+ // Implementation for successful upgrade logic goes here
+ $user = \App\User::find($userId);
+ $newPlan = \App\ServicePlan::find($data['plan_id']);
+ if ($user && $newPlan) {
+ $planKey = $newPlan->key_name;
+ $emailData = ['user' => $user, 'plan' => $planKey];
+ $subject = \App\Helpers\MainHelper::createEmailSubject(sprintf("Upgraded plan to %s", $planKey));
+ \App\Helpers\EmailHelper::sendEmail($subject, $user->email, 'plan_upgraded', $emailData);
+ }
+ break;
+
+ case 'FAILED_UPGRADE':
+ $this->error(" [WORKSPACE_UPGRADE] Processing failed upgrade for workspace ID: " . $workspaceId);
+ // Implementation for failed upgrade logic goes here
+ $user = \App\User::find($userId);
+ $newPlan = \App\ServicePlan::find($data['plan_id']);
+ if ($user && $newPlan) {
+ $planKey = $newPlan->key_name;
+ $emailData = ['user' => $user, 'plan' => $planKey];
+ $subject = \App\Helpers\MainHelper::createEmailSubject(sprintf("Failed to upgrade plan to %s", $planKey));
+ \App\Helpers\EmailHelper::sendEmail($subject, $user->email, 'failed_upgrade', $emailData);
+ }
+ break;
+
+ default:
+ $this->logConsumerError(' [WORKSPACE_UPGRADE] Unknown action: ' . $action, [
+ 'action' => $action,
+ 'workspace_id' => $workspaceId
+ ]);
+ break;
+ }
+
+ $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
+ $this->info(" [v] Workspace upgrade event acknowledged for workspace #" . $workspaceId);
+ } catch (Exception $e) {
+ $this->logConsumerError(' [WORKSPACE_UPGRADE] ' . $e->getMessage(), [
+ 'exception' => get_class($e)
+ ]);
+ $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
+ }
+ }
+
public function handleMonthlyInvoiceTask($msg)
{
$this->handleInvoiceTask($msg, 'MONTHLY');
diff --git a/app/app/Enums/WorkspaceSuspensionStatus.php b/app/app/Enums/WorkspaceSuspensionStatus.php
index 6486494d8..bff6ee311 100644
--- a/app/app/Enums/WorkspaceSuspensionStatus.php
+++ b/app/app/Enums/WorkspaceSuspensionStatus.php
@@ -7,23 +7,23 @@
*/
abstract class WorkspaceSuspensionStatus
{
- public const NOT_SUSPENDED = '0';
- public const PENDING_SUSPENSION = 'pending_suspension';
- public const REAL_SUSPENSION = 'real_suspension';
+ public const INITIATED = 'INITIATED';
+ public const SUSPENDED = 'SUSPENDED';
+ public const LIFTED = 'LIFTED';
public static function all(): array
{
return [
- self::NOT_SUSPENDED,
- self::PENDING_SUSPENSION,
- self::REAL_SUSPENSION,
+ self::INITIATED,
+ self::SUSPENDED,
+ self::LIFTED,
];
}
public static function activeValues(): array
{
return [
- self::REAL_SUSPENSION,
+ self::SUSPENDED,
true,
1,
'1',
@@ -33,7 +33,7 @@ public static function activeValues(): array
public static function activeDatabaseValues(): array
{
return [
- self::REAL_SUSPENSION,
+ self::SUSPENDED,
'1',
];
}
@@ -41,7 +41,7 @@ public static function activeDatabaseValues(): array
public static function isActive($status): bool
{
return in_array($status, [
- self::REAL_SUSPENSION,
+ self::SUSPENDED,
true,
1,
'1',
@@ -51,13 +51,13 @@ public static function isActive($status): bool
public static function label($status): string
{
if (self::isActive($status)) {
- return 'Real Suspension';
+ return 'Suspended';
}
- if ($status === self::PENDING_SUSPENSION) {
- return 'Pending Suspension';
+ if ($status === self::INITIATED) {
+ return 'Initiated';
}
- return 'Not Suspended';
+ return 'Lifted';
}
}
diff --git a/app/app/Helpers/BillingDataHelper.php b/app/app/Helpers/BillingDataHelper.php
index fc2ca8535..c66d5e649 100755
--- a/app/app/Helpers/BillingDataHelper.php
+++ b/app/app/Helpers/BillingDataHelper.php
@@ -11,6 +11,7 @@
use App\CustomizationsKVStore;
use App\ApiCredentialKVStore;
+use App\ServicePlan;
use App\Settings;
use App\UserCredit;
use App\UserDebit;
@@ -34,7 +35,7 @@ public static function updateWorkspaceBilling($gateway, $cardData, $user, $works
}
public static function getBillingHistory($workspace) {
//$data = DB::select(sprintf('select * from (select status, cents, created_at, \'credit\' as type, user_id from users_credits union select status, cents, created_at, \'invoice\' as type, user_id from users_invoices order by created_at desc) as U where U.user_id = "%s";', $user->id)); $data = DB::select(sprintf('select * from (select status, cents, created_at, \'credit\' as type, user_id from users_credits union select status, cents, created_at, \'invoice\' as type, user_id from users_invoices order by created_at desc) as U where U.user_id = "%s";', $user->id));
- $data = DB::select(sprintf('select id, status, cents, created_at, \'invoice\' as type, user_id from users_invoices order by created_at desc where workspace_id = "%s";', $workspace->id));
+ $data = DB::select(sprintf('select id, status, cents, created_at, \'invoice\' as type, user_id from users_invoices where workspace_id = "%s" order by created_at desc;', $workspace->id));
$data = array_map(function($item) {
$array = (array) $item;
$array['dollars'] = self::toDollars($array['cents']);
@@ -292,4 +293,102 @@ public static function refundInvoice($invoice)
$invoice->status = PaymentStatus::REFUNDED;
$invoice->save();
}
+
+ public static function getUpgradeData($subscription, $planKey, $workspace) {
+ $newPlan = ServicePlan::where('key_name', $planKey)->first();
+ $oldPlan = ServicePlan::find($subscription->current_plan_id);
+
+ $now = new \DateTime();
+
+ // 2. Determine start date cleanly without a ternary
+ $last_billable_date = $subscription->last_billed_at;
+ if (!$last_billable_date) {
+ $last_billable_date = $subscription->created_at;
+ }
+
+ // 3. Proration Calculation using Timestamp Precision
+ // Calculate period end in real time based on billing flow and cycle
+ $customizations = CustomizationsKVStore::getRecord();
+ $billingFlow = $customizations['billing_flow'];
+
+ $isAnnualSubscription = false;
+ if ($subscription->billing_cycle === 'ANNUAL') {
+ $isAnnualSubscription = true;
+ }
+
+ if ($billingFlow === 'ANNUAL') {
+ if ($isAnnualSubscription) {
+ $periodEnd = (clone $now)->modify('first day of january next year')->setTime(0, 0, 0);
+ } else {
+ $periodEnd = (clone $now)->modify('first day of next month')->setTime(0, 0, 0);
+ }
+ } else {
+ // ANNIVERSARY billing flow
+ $anchorDate = new \DateTime($last_billable_date);
+ if ($isAnnualSubscription) {
+ $periodEnd = (clone $anchorDate)->modify('+1 year');
+ } else {
+ $periodEnd = (clone $anchorDate)->modify('+1 month');
+ }
+ }
+
+ $lastBilled = new \DateTime($last_billable_date);
+
+ $totalCycleSeconds = $periodEnd->getTimestamp() - $lastBilled->getTimestamp();
+ $remainingSeconds = $periodEnd->getTimestamp() - $now->getTimestamp();
+
+ if ($totalCycleSeconds <= 0) {
+ $totalCycleSeconds = 30 * 86400; // Default fallback to 30 days in seconds
+ }
+
+ // Explicit pricing logic without ternaries
+ $isAnnualSubscription = false;
+ if ($subscription->billing_cycle === 'ANNUAL') {
+ $isAnnualSubscription = true;
+ }
+
+ if ($isAnnualSubscription) {
+ $oldPrice = $oldPlan->annual_cost_cents;
+ $newPrice = $newPlan->annual_cost_cents;
+ } else {
+ $oldPrice = $oldPlan->monthly_cost_cents;
+ $newPrice = $newPlan->monthly_cost_cents;
+ }
+
+ \Log::info('Plan upgrade pricing calculation', [
+ 'workspace_id' => $workspace->id,
+ 'old_plan_id' => $oldPlan->id,
+ 'new_plan_id' => $newPlan->id,
+ 'is_annual' => $isAnnualSubscription,
+ 'old_price_cents' => $oldPrice,
+ 'new_price_cents' => $newPrice,
+ ]);
+
+ $priceDiff = $newPrice - $oldPrice;
+
+ $prorationCents = 0;
+ if ($priceDiff > 0 && $remainingSeconds > 0) {
+ $prorationCents = ($priceDiff * $remainingSeconds) / $totalCycleSeconds;
+ }
+
+ \Log::info('Proration calculation', [
+ 'workspace_id' => $workspace->id,
+ 'price_diff_cents' => $priceDiff,
+ 'remaining_seconds' => $remainingSeconds,
+ 'total_cycle_seconds' => $totalCycleSeconds,
+ 'proration_cents' => round($prorationCents),
+ ]);
+
+
+
+ $feesCents = (int) round($prorationCents);
+ return [
+ 'fees' => $feesCents,
+ 'fees_dollars' => MainHelper::toDollars($feesCents),
+ 'new_plan_cost_cents' => $newPrice,
+ 'new_plan_cost_dollars' => MainHelper::toDollars($newPrice),
+ 'new_plan' => $newPlan,
+ 'old_plan' => $oldPlan,
+ ];
+ }
}
diff --git a/app/app/Helpers/WorkspaceSuspensionHelper.php b/app/app/Helpers/WorkspaceSuspensionHelper.php
index 8c931f446..4d1375991 100644
--- a/app/app/Helpers/WorkspaceSuspensionHelper.php
+++ b/app/app/Helpers/WorkspaceSuspensionHelper.php
@@ -24,30 +24,19 @@ public static function getGlobalGracePeriod()
public static function getActiveSuspension($workspaceId)
{
- if (!Schema::hasTable('workspace_suspensions')) {
- return null;
- }
-
- $suspensions = WorkspaceSuspension::where('workspace_id', $workspaceId)
+ $suspension = WorkspaceSuspension::where('workspace_id', $workspaceId)
+ ->where('status', '=', WorkspaceSuspensionStatus::SUSPENDED)
->orderBy('suspended_at', 'desc')
- ->get();
+ ->first();
- foreach ($suspensions as $suspension) {
- if (WorkspaceSuspensionStatus::isActive($suspension->status)) {
- return $suspension;
- }
- }
-
- return null;
+ return $suspension;
}
public static function getGracePeriodExtension($workspaceId)
{
- if (Schema::hasColumn('workspaces', 'grace_period_extension')) {
- $workspace = Workspace::find($workspaceId);
- if ($workspace && $workspace->grace_period_extension !== null) {
- return (int) $workspace->grace_period_extension;
- }
+ $workspace = Workspace::find($workspaceId);
+ if ($workspace && $workspace->grace_period_extension !== null) {
+ return (int) $workspace->grace_period_extension;
}
$suspension = self::getActiveSuspension($workspaceId);
@@ -76,30 +65,32 @@ public static function saveGracePeriodExtension(Workspace $workspace, $value)
$extension = (int) $value;
}
- if (Schema::hasColumn('workspaces', 'grace_period_extension')) {
- $workspace->grace_period_extension = $extension;
- $workspace->save();
- }
+ $workspace->grace_period_extension = $extension;
+ $workspace->save();
if ($extension !== null && !$workspace->active) {
$workspace->active = true;
$workspace->save();
}
- if (!Schema::hasTable('workspace_suspensions')) {
- return $workspace;
- }
-
- $suspensions = WorkspaceSuspension::where('workspace_id', $workspace->id)->get();
+ $suspensions = WorkspaceSuspension::where('workspace_id', $workspace->id)
+ ->where('status', '!=', WorkspaceSuspensionStatus::LIFTED)
+ ->get();
if ($suspensions->isEmpty()) {
return $workspace;
}
foreach ($suspensions as $suspension) {
- $suspension->grace_period_extension = $extension;
if ($extension !== null) {
- $suspension->status = WorkspaceSuspensionStatus::NOT_SUSPENDED;
+ $suspension->grace_period_extension = $extension;
+
+ $now = new DateTime();
+ $gracePeriodEnd = (new DateTime($suspension->suspension_initiated_at))->modify("+{$extension} days");
+ if ($now < $gracePeriodEnd) {
+ $suspension->status = WorkspaceSuspensionStatus::INITIATED;
+ $suspension->suspended_at = null;
+ }
}
$suspension->save();
}
@@ -109,7 +100,7 @@ public static function saveGracePeriodExtension(Workspace $workspace, $value)
public static function suspendWorkspace(Workspace $workspace, $invoice = null, $daysPastDue = null, $threshold = null)
{
- if (!Schema::hasTable('workspace_suspensions')) {
+ if (!Schema::hasTable('workspaces_suspensions')) {
return null;
}
diff --git a/app/app/Http/Controllers/Admin/WorkspaceController.php b/app/app/Http/Controllers/Admin/WorkspaceController.php
index ff7449b70..29ca5d7ca 100755
--- a/app/app/Http/Controllers/Admin/WorkspaceController.php
+++ b/app/app/Http/Controllers/Admin/WorkspaceController.php
@@ -140,25 +140,14 @@ public function destroy(Workspace $workspace)
public function data()
{
$globalGracePeriod = WorkspaceSuspensionHelper::getGlobalGracePeriod();
- $realSuspensionStatus = WorkspaceSuspensionStatus::REAL_SUSPENSION;
- $activeSuspensionSelect = Schema::hasTable('workspace_suspensions')
- ? DB::raw("(select count(*) from workspace_suspensions ws_status where ws_status.workspace_id = workspaces.id and ws_status.status in ('" . $realSuspensionStatus . "', '1')) as active_suspension")
- : DB::raw('0 as active_suspension');
+ $activeSuspensionSelect = DB::raw("(select count(*) from workspaces_suspensions ws_status where ws_status.workspace_id = workspaces.id and ws_status.status = 'SUSPENDED') as active_suspension");
$gracePeriodSources = array();
- if (Schema::hasColumn('workspaces', 'grace_period_extension')) {
- $gracePeriodSources[] = 'workspaces.grace_period_extension';
- }
- if (Schema::hasTable('workspace_suspensions')) {
- $gracePeriodSources[] = "(select ws.grace_period_extension from workspace_suspensions ws where ws.workspace_id = workspaces.id and ws.status in ('" . $realSuspensionStatus . "', '1') and ws.grace_period_extension is not null order by ws.suspended_at desc limit 1)";
- }
- $gracePeriodSources[] = (int) $globalGracePeriod;
- $gracePeriodSelect = DB::raw('COALESCE(' . implode(', ', $gracePeriodSources) . ') as grace_period');
$workspaces = DB::table('workspaces')->select(array(
'workspaces.id',
'workspaces.name',
'workspaces.active',
- $gracePeriodSelect,
+ DB::raw('workspaces.grace_period_extension AS grace_period'),
'workspaces.created_at',
$activeSuspensionSelect
));
diff --git a/app/app/Http/Controllers/MergedController.php b/app/app/Http/Controllers/MergedController.php
index 723b25ecd..972794b5d 100755
--- a/app/app/Http/Controllers/MergedController.php
+++ b/app/app/Http/Controllers/MergedController.php
@@ -195,6 +195,29 @@ public function plans(Request $request)
return $this->response->array($plans);
}
+ public function getUpgradeFees(Request $request)
+ {
+ $planKey = $request->get('plan_key');
+ if (!$planKey) {
+ return $this->response->errorBadRequest("Plan key is required.");
+ }
+
+ $workspace = $this->getWorkspace($request);
+ $subscription = Subscription::where('workspace_id', $workspace->id)->first();
+
+ if (!$subscription) {
+ return $this->response->errorBadRequest("Invalid subscription or plan.");
+ }
+
+ $upgradeData = BillingDataHelper::getUpgradeData($subscription, $planKey, $workspace);
+ return $this->response->array([
+ 'fees' => $upgradeData['fees'],
+ 'fees_dollars' => $upgradeData['fees_dollars'],
+ 'new_plan_cost_cents' => $upgradeData['new_plan_cost_cents'],
+ 'new_plan_cost_dollars' => $upgradeData['new_plan_cost_dollars']
+ ]);
+ }
+
public function upgradePlan(Request $request)
{
$json = $request->json()->all();
@@ -223,97 +246,17 @@ public function upgradePlan(Request $request)
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 || !$oldPlan) {
- return $this->response->errorBadRequest("Invalid subscription or plan config.");
- }
-
- // 2. Determine start date cleanly without a ternary
- $last_billable_date = $subscription->last_billed_at;
- if (!$last_billable_date) {
- $last_billable_date = $subscription->created_at;
- }
-
- // 3. Proration Calculation using Timestamp Precision
- // Calculate period end in real time based on billing flow and cycle
+ $upgradeData = BillingDataHelper::getUpgradeData($subscription, $planKey, $workspace);
+ // 4. Determine Scheduled Effective Date without ternaries
+ $scheduledEffectiveDate = null;
$customizations = CustomizationsKVStore::getRecord();
- $billingFlow = $customizations['billing_flow'];
-
- $isAnnualSubscription = false;
- if ($subscription->billing_cycle === 'ANNUAL') {
- $isAnnualSubscription = true;
- }
- if ($billingFlow === 'ANNUAL') {
- if ($isAnnualSubscription) {
- $periodEnd = (clone $now)->modify('first day of january next year')->setTime(0, 0, 0);
- } else {
- $periodEnd = (clone $now)->modify('first day of next month')->setTime(0, 0, 0);
- }
- } else {
- // ANNIVERSARY billing flow
- $anchorDate = new \DateTime($last_billable_date);
- if ($isAnnualSubscription) {
- $periodEnd = (clone $anchorDate)->modify('+1 year');
- } else {
- $periodEnd = (clone $anchorDate)->modify('+1 month');
- }
- }
-
- $lastBilled = new \DateTime($last_billable_date);
-
- $totalCycleSeconds = $periodEnd->getTimestamp() - $lastBilled->getTimestamp();
- $remainingSeconds = $periodEnd->getTimestamp() - $now->getTimestamp();
-
- if ($totalCycleSeconds <= 0) {
- $totalCycleSeconds = 30 * 86400; // Default fallback to 30 days in seconds
- }
+ $billingFlow = $customizations['billing_flow'];
- // Explicit pricing logic without ternaries
$isAnnualSubscription = false;
if ($subscription->billing_cycle === 'ANNUAL') {
$isAnnualSubscription = true;
}
-
- if ($isAnnualSubscription) {
- $oldPrice = $oldPlan->annual_cost_cents;
- $newPrice = $newPlan->annual_cost_cents;
- } else {
- $oldPrice = $oldPlan->monthly_cost_cents;
- $newPrice = $newPlan->monthly_cost_cents;
- }
-
- \Log::info('Plan upgrade pricing calculation', [
- 'workspace_id' => $workspace->id,
- 'old_plan_id' => $oldPlan->id,
- 'new_plan_id' => $newPlan->id,
- 'is_annual' => $isAnnualSubscription,
- 'old_price_cents' => $oldPrice,
- 'new_price_cents' => $newPrice,
- ]);
-
- $priceDiff = $newPrice - $oldPrice;
-
- $prorationCents = 0;
- if ($priceDiff > 0 && $remainingSeconds > 0) {
- $prorationCents = ($priceDiff * $remainingSeconds) / $totalCycleSeconds;
- }
-
- \Log::info('Proration calculation', [
- 'workspace_id' => $workspace->id,
- 'price_diff_cents' => $priceDiff,
- 'remaining_seconds' => $remainingSeconds,
- 'total_cycle_seconds' => $totalCycleSeconds,
- 'proration_cents' => round($prorationCents),
- ]);
-
- // 4. Determine Scheduled Effective Date without ternaries
- $scheduledEffectiveDate = null;
- $customizations = CustomizationsKVStore::getRecord();
-
- $billingFlow = $customizations['billing_flow'];
if ($billingFlow === 'ANNUAL') {
if ($isAnnualSubscription) {
$modifier = 'first day of january next year';
@@ -336,14 +279,15 @@ public function upgradePlan(Request $request)
$scheduledEffectiveDate = (clone $anchorDateTime)->modify($modifier);
}
+ $newPlan = $upgradeData['new_plan'];
RabbitMQHelper::dispatchWorkspaceUpgrade(
$workspace->id,
$workspace->creator_id,
- (int) round($prorationCents),
+ $upgradeData['fees'],
$subscription->id,
$subscription->current_plan_id,
$newPlan->id,
- $scheduledEffectiveDate->format('Y-m-d H:i:s'),
+ $scheduledEffectiveDate->format('Y-m-d'),
$card->id,
$card->stripe_payment_method_id,
$card->issuer,
@@ -354,17 +298,14 @@ public function upgradePlan(Request $request)
$props = [
"billing_status" => "immediate_upgrade_pending_reconciliation",
"new_plan" => $planKey,
- "proration_applied" => (int) round($prorationCents)
+ "proration_applied" => $upgradeData['fees']
];
WorkspaceEvent::addEvent($workspace, 'PLAN_UPGRADED', $props);
- $data = ['user' => $user, 'plan' => $planKey];
- $subject = MainHelper::createEmailSubject(sprintf("Upgraded plan to %s", $planKey));
- EmailHelper::sendEmail($subject, $user->email, 'plan_upgraded', $data);
-
return $this->response->noContent();
}
+
public function dashboard(Request $request)
{
$dayCount = 7;
diff --git a/app/app/Http/routes.php b/app/app/Http/routes.php
index 44b352b63..aa35aba20 100755
--- a/app/app/Http/routes.php
+++ b/app/app/Http/routes.php
@@ -587,6 +587,7 @@
$api->get('dashboard', '\App\Http\Controllers\MergedController@dashboard');
$api->get('feed', '\App\Http\Controllers\MergedController@feed');
$api->post('upgradePlan', '\App\Http\Controllers\MergedController@upgradePlan');
+ $api->get('getUpgradeFees', '\App\Http\Controllers\MergedController@getUpgradeFees');
$api->get('plans', '\App\Http\Controllers\MergedController@plans');
$api->get('billing', '\App\Http\Controllers\MergedController@billing');
$api->get('billing/viewEstimatedCharges', '\App\Http\Controllers\BillingController@viewEstimatedCharges');
diff --git a/app/app/WorkspaceSuspension.php b/app/app/WorkspaceSuspension.php
index d9a04df3d..2f07a79c6 100644
--- a/app/app/WorkspaceSuspension.php
+++ b/app/app/WorkspaceSuspension.php
@@ -11,7 +11,7 @@ class WorkspaceSuspension extends Model
protected $dates = ['suspended_at'];
protected $guarded = array('id');
- protected $table = 'workspace_suspensions';
+ protected $table = 'workspaces_suspensions';
protected $casts = array(
'status' => 'boolean',
'grace_period_extension' => 'integer',
diff --git a/app/database/migrations/2026_05_22_000001_add_grace_period_extension_to_workspace_suspensions.php b/app/database/migrations/2026_05_22_000001_add_grace_period_extension_to_workspace_suspensions.php
index 414f9feab..fac0a8780 100644
--- a/app/database/migrations/2026_05_22_000001_add_grace_period_extension_to_workspace_suspensions.php
+++ b/app/database/migrations/2026_05_22_000001_add_grace_period_extension_to_workspace_suspensions.php
@@ -17,7 +17,7 @@ public function up()
$table->timestamp('suspended_at');
$table->integer('grace_period_extension')->nullable();
$table->string('reason');
- $table->boolean('status')->default(true);
+ $table->string('status')->default('INITIATED');
$table->foreign('workspace_id')->references('id')->on('workspaces')->onDelete('CASCADE');
$table->index(array('workspace_id', 'status'));
diff --git a/app/resources/views/emails/failed_upgrade.blade.php b/app/resources/views/emails/failed_upgrade.blade.php
new file mode 100755
index 000000000..0e3201f9c
--- /dev/null
+++ b/app/resources/views/emails/failed_upgrade.blade.php
@@ -0,0 +1,85 @@
+@extends('emails.layouts.alert_email')
+@section('title')
+Upgrade Failed
+@endsection
+@section('content')
+nice_name ?: $plan->name) : (isset($plan) ? $plan : 'your new plan');
+$currentPlanName = isset($currentPlan) && is_object($currentPlan) ? ($currentPlan->nice_name ?: $currentPlan->name) : (isset($currentPlan) ? $currentPlan : null);
+$newPlanName = isset($newPlan) && is_object($newPlan) ? ($newPlan->nice_name ?: $newPlan->name) : (isset($newPlan) ? $newPlan : $planName);
+$userName = isset($user) ? trim($user->getName()) : '';
+?>
+
+
+
+
+
+
+ | |
+
+
+
+
+ | |
+
+
+ | |
+
+
+
+
+
+ |
+
+ |
+
+ We couldn't upgrade your plan
+ There was an issue processing your payment.
+ |
+
+
+
+ Hello {{ $userName !== '' ? $userName : 'there' }},
+ This email is to notify you that we couldn't process the payment to upgrade your workspace plan. Your plan has not been upgraded.
+
+
+ @if ($currentPlanName)
+
+ | Current Plan |
+ {{ $currentPlanName }} |
+
+ @endif
+
+ | Requested Plan |
+ {{ $newPlanName }} |
+
+
+ | Upgrade Status |
+ Failed |
+
+
+ | What Happens Next |
+ Your services will remain on the current plan. Please update your payment information so we can process your upgrade. |
+
+
+
+ You can review your billing details and update your payment method from your account dashboard at any time. Thank you for choosing {{$site_name ?? \App\Helpers\MainHelper::getSiteName()}}.
+ |
+
+
+
+ |
+ |
+
+
+ | |
+
+
+
+ |
+
+@endsection