From 7b57dbabf624be1270271c1f54f2dc87081cfa70 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 2 Apr 2026 18:35:58 +0100 Subject: [PATCH 1/2] Remove automatic is_comped detection from invoice paid handler Stop auto-setting is_comped based on invoice total being zero. The comped status should only be set intentionally via admin tools. Renamed method to updateSubscriptionPricePaid to reflect its purpose. Co-Authored-By: Claude Opus 4.6 --- app/Jobs/HandleInvoicePaidJob.php | 13 +++-- .../Feature/Jobs/HandleInvoicePaidJobTest.php | 53 ++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/app/Jobs/HandleInvoicePaidJob.php b/app/Jobs/HandleInvoicePaidJob.php index dcdfb34d..9fccbade 100644 --- a/app/Jobs/HandleInvoicePaidJob.php +++ b/app/Jobs/HandleInvoicePaidJob.php @@ -61,7 +61,7 @@ private function handleSubscriptionUpdate(): void 'invoice_id' => $this->invoice->id, ]); - $this->updateSubscriptionCompedStatus(); + $this->updateSubscriptionPricePaid(); } private function handleSubscriptionCreated(): void @@ -76,12 +76,12 @@ private function handleSubscriptionCreated(): void if ($isRenewal && $licenseKey && $licenseId) { $this->handleLegacyLicenseRenewal($subscription, $licenseKey, $licenseId); - $this->updateSubscriptionCompedStatus(); + $this->updateSubscriptionPricePaid(); return; } - $this->updateSubscriptionCompedStatus(); + $this->updateSubscriptionPricePaid(); } private function handleLegacyLicenseRenewal($subscription, string $licenseKey, string $licenseId): void @@ -169,7 +169,7 @@ private function handleSubscriptionRenewal(): void 'invoice_id' => $this->invoice->id, ]); - $this->updateSubscriptionCompedStatus(); + $this->updateSubscriptionPricePaid(); } private function handleManualInvoice(): void @@ -591,9 +591,9 @@ private function createBundlePluginLicense(User $user, Plugin $plugin, PluginBun } /** - * Mark the local Cashier subscription as comped if the invoice total is zero. + * Update the price paid on the local Cashier subscription from the invoice total. */ - private function updateSubscriptionCompedStatus(): void + private function updateSubscriptionPricePaid(): void { if (! $this->invoice->subscription) { return; @@ -605,7 +605,6 @@ private function updateSubscriptionCompedStatus(): void $invoiceTotal = $this->invoice->total ?? 0; $subscription->update([ - 'is_comped' => $invoiceTotal <= 0, 'price_paid' => max(0, $invoiceTotal), ]); } diff --git a/tests/Feature/Jobs/HandleInvoicePaidJobTest.php b/tests/Feature/Jobs/HandleInvoicePaidJobTest.php index 02241039..6bc2c3ea 100644 --- a/tests/Feature/Jobs/HandleInvoicePaidJobTest.php +++ b/tests/Feature/Jobs/HandleInvoicePaidJobTest.php @@ -66,6 +66,56 @@ public function it_does_not_create_license_for_any_subscription(string $planKey) Bus::assertNotDispatched(CreateAnystackLicenseJob::class); } + #[Test] + public function it_does_not_auto_set_is_comped_when_invoice_total_is_zero(): void + { + Bus::fake(); + + $user = User::factory()->create([ + 'stripe_id' => 'cus_test123', + ]); + + $priceId = 'price_test_mini'; + config(['subscriptions.plans.mini.stripe_price_id' => $priceId]); + + $subscription = \Laravel\Cashier\Subscription::factory() + ->for($user, 'user') + ->create([ + 'stripe_id' => 'sub_test123', + 'stripe_status' => 'active', + 'stripe_price' => $priceId, + 'quantity' => 1, + 'is_comped' => false, + ]); + + SubscriptionItem::factory() + ->for($subscription, 'subscription') + ->create([ + 'stripe_id' => 'si_test123', + 'stripe_price' => $priceId, + 'quantity' => 1, + ]); + + $this->mockStripeSubscriptionRetrieve('sub_test123'); + + $invoice = $this->createStripeInvoice( + customerId: 'cus_test123', + subscriptionId: 'sub_test123', + billingReason: Invoice::BILLING_REASON_SUBSCRIPTION_CREATE, + priceId: $priceId, + subscriptionItemId: 'si_test123', + total: 0, + ); + + $job = new HandleInvoicePaidJob($invoice); + $job->handle(); + + $subscription->refresh(); + + $this->assertFalse((bool) $subscription->is_comped); + $this->assertEquals(0, $subscription->price_paid); + } + public static function subscriptionPlanProvider(): array { return [ @@ -81,6 +131,7 @@ private function createStripeInvoice( string $billingReason, string $priceId, string $subscriptionItemId, + int $total = 25000, ): Invoice { return Invoice::constructFrom([ 'id' => 'in_test_'.uniqid(), @@ -88,7 +139,7 @@ private function createStripeInvoice( 'customer' => $customerId, 'subscription' => $subscriptionId, 'billing_reason' => $billingReason, - 'total' => 25000, + 'total' => $total, 'currency' => 'usd', 'payment_intent' => 'pi_test_'.uniqid(), 'metadata' => [], From 414b7813c1f49c7aa21ebf38a3ad61b7c3840f9c Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 2 Apr 2026 18:46:07 +0100 Subject: [PATCH 2/2] Fix dashboard showing Active badge for canceled subscriptions Check subscription active() status instead of just existence, so canceled/ended subscriptions correctly show "No active subscription". Co-Authored-By: Claude Opus 4.6 --- .../livewire/customer/dashboard.blade.php | 2 +- tests/Feature/DashboardLayoutTest.php | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/customer/dashboard.blade.php b/resources/views/livewire/customer/dashboard.blade.php index 2d9716fd..6928deba 100644 --- a/resources/views/livewire/customer/dashboard.blade.php +++ b/resources/views/livewire/customer/dashboard.blade.php @@ -82,7 +82,7 @@ /> {{-- Subscription Card --}} - @if($this->activeSubscription) + @if($this->activeSubscription?->active()) assertSee("Timmy D'Hooghe"); } + // ======================================== + // Subscription Card Tests + // ======================================== + + public function test_dashboard_shows_no_active_subscription_when_subscription_is_canceled(): void + { + $user = User::factory()->create(); + Subscription::factory()->for($user)->create([ + 'stripe_price' => self::MAX_PRICE_ID, + 'stripe_status' => 'canceled', + 'ends_at' => now()->subDay(), + ]); + + Livewire::actingAs($user) + ->test(Dashboard::class) + ->assertOk() + ->assertSee('No active subscription') + ->assertDontSee('badge="Active"'); + } + // ======================================== // Sidebar Team Item Tests // ========================================