Skip to content

Commit c461578

Browse files
simonhampclaude
andcommitted
Stop creating Anystack licenses on subscription events
License creation via Anystack is no longer needed for any subscription plan. Remove the createLicense() method and all call sites in HandleInvoicePaidJob, including fallbacks in legacy renewal and subscription cycle handlers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 664123b commit c461578

File tree

3 files changed

+134
-42
lines changed

3 files changed

+134
-42
lines changed

app/Jobs/HandleInvoicePaidJob.php

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use App\Enums\PayoutStatus;
66
use App\Enums\Subscription;
7-
use App\Exceptions\InvalidStateException;
87
use App\Models\Cart;
98
use App\Models\CartItem;
109
use App\Models\License;
@@ -25,7 +24,6 @@
2524
use Illuminate\Queue\SerializesModels;
2625
use Illuminate\Support\Facades\Date;
2726
use Illuminate\Support\Facades\Log;
28-
use Illuminate\Support\Sleep;
2927
use Laravel\Cashier\Cashier;
3028
use Laravel\Cashier\SubscriptionItem;
3129
use Stripe\Invoice;
@@ -83,8 +81,6 @@ private function handleSubscriptionCreated(): void
8381
return;
8482
}
8583

86-
// Normal flow - create a new license
87-
$this->createLicense();
8884
$this->updateSubscriptionCompedStatus();
8985
}
9086

@@ -105,8 +101,6 @@ private function handleLegacyLicenseRenewal($subscription, string $licenseKey, s
105101
'user_id' => $user->id,
106102
'subscription_id' => $subscription->id,
107103
]);
108-
// Fallback to creating a new license
109-
$this->createLicense();
110104

111105
return;
112106
}
@@ -140,38 +134,6 @@ private function handleLegacyLicenseRenewal($subscription, string $licenseKey, s
140134
]);
141135
}
142136

143-
private function createLicense(): void
144-
{
145-
// Add some delay to allow all the Stripe events to come in
146-
Sleep::sleep(10);
147-
148-
// Assert the invoice line item is for a price_id that relates to a license plan.
149-
$plan = Subscription::fromStripePriceId($this->findPlanLineItem()->price->id);
150-
151-
// Assert the invoice line item relates to a subscription and has a subscription item id.
152-
if (blank($subscriptionItemId = $this->findPlanLineItem()->subscription_item)) {
153-
throw new UnexpectedValueException('Failed to retrieve the Stripe subscription item id from invoice lines.');
154-
}
155-
156-
// Assert we have a subscription item record for this subscription item id.
157-
$subscriptionItemModel = SubscriptionItem::query()->where('stripe_id', $subscriptionItemId)->firstOrFail();
158-
159-
// Assert we don't already have an existing license for this subscription item.
160-
if ($license = License::query()->whereBelongsTo($subscriptionItemModel)->first()) {
161-
throw new InvalidStateException("A license [{$license->id}] already exists for subscription item [{$subscriptionItemModel->id}].");
162-
}
163-
164-
$user = $this->billable();
165-
166-
dispatch(new CreateAnystackLicenseJob(
167-
$user,
168-
$plan,
169-
$subscriptionItemModel->id,
170-
$user->first_name,
171-
$user->last_name,
172-
));
173-
}
174-
175137
private function handleSubscriptionRenewal(): void
176138
{
177139
// Get the subscription item ID from the invoice line
@@ -186,9 +148,6 @@ private function handleSubscriptionRenewal(): void
186148
$license = License::query()->whereBelongsTo($subscriptionItemModel)->first();
187149

188150
if (! $license) {
189-
// No existing license found - this might be a new subscription, handle as create
190-
$this->createLicense();
191-
192151
return;
193152
}
194153

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
namespace Tests\Feature\Jobs;
4+
5+
use App\Jobs\CreateAnystackLicenseJob;
6+
use App\Jobs\HandleInvoicePaidJob;
7+
use App\Models\User;
8+
use Illuminate\Foundation\Testing\RefreshDatabase;
9+
use Illuminate\Support\Facades\Bus;
10+
use Laravel\Cashier\SubscriptionItem;
11+
use PHPUnit\Framework\Attributes\DataProvider;
12+
use PHPUnit\Framework\Attributes\Test;
13+
use Stripe\Invoice;
14+
use Stripe\Service\SubscriptionService;
15+
use Stripe\StripeClient;
16+
use Stripe\Subscription;
17+
use Tests\TestCase;
18+
19+
class HandleInvoicePaidJobTest extends TestCase
20+
{
21+
use RefreshDatabase;
22+
23+
#[Test]
24+
#[DataProvider('subscriptionPlanProvider')]
25+
public function it_does_not_create_license_for_any_subscription(string $planKey): void
26+
{
27+
Bus::fake();
28+
29+
$user = User::factory()->create([
30+
'stripe_id' => 'cus_test123',
31+
]);
32+
33+
$priceId = 'price_test_'.$planKey;
34+
config(["subscriptions.plans.{$planKey}.stripe_price_id" => $priceId]);
35+
36+
$subscription = \Laravel\Cashier\Subscription::factory()
37+
->for($user, 'user')
38+
->create([
39+
'stripe_id' => 'sub_test123',
40+
'stripe_status' => 'active',
41+
'stripe_price' => $priceId,
42+
'quantity' => 1,
43+
]);
44+
45+
SubscriptionItem::factory()
46+
->for($subscription, 'subscription')
47+
->create([
48+
'stripe_id' => 'si_test123',
49+
'stripe_price' => $priceId,
50+
'quantity' => 1,
51+
]);
52+
53+
$this->mockStripeSubscriptionRetrieve('sub_test123');
54+
55+
$invoice = $this->createStripeInvoice(
56+
customerId: 'cus_test123',
57+
subscriptionId: 'sub_test123',
58+
billingReason: Invoice::BILLING_REASON_SUBSCRIPTION_CREATE,
59+
priceId: $priceId,
60+
subscriptionItemId: 'si_test123',
61+
);
62+
63+
$job = new HandleInvoicePaidJob($invoice);
64+
$job->handle();
65+
66+
Bus::assertNotDispatched(CreateAnystackLicenseJob::class);
67+
}
68+
69+
public static function subscriptionPlanProvider(): array
70+
{
71+
return [
72+
'mini' => ['mini'],
73+
'pro' => ['pro'],
74+
'max' => ['max'],
75+
];
76+
}
77+
78+
private function createStripeInvoice(
79+
string $customerId,
80+
string $subscriptionId,
81+
string $billingReason,
82+
string $priceId,
83+
string $subscriptionItemId,
84+
): Invoice {
85+
return Invoice::constructFrom([
86+
'id' => 'in_test_'.uniqid(),
87+
'object' => 'invoice',
88+
'customer' => $customerId,
89+
'subscription' => $subscriptionId,
90+
'billing_reason' => $billingReason,
91+
'total' => 25000,
92+
'currency' => 'usd',
93+
'payment_intent' => 'pi_test_'.uniqid(),
94+
'metadata' => [],
95+
'lines' => [
96+
'object' => 'list',
97+
'data' => [
98+
[
99+
'id' => 'il_test_'.uniqid(),
100+
'object' => 'line_item',
101+
'subscription_item' => $subscriptionItemId,
102+
'price' => [
103+
'id' => $priceId,
104+
'object' => 'price',
105+
'active' => true,
106+
'currency' => 'usd',
107+
'unit_amount' => 25000,
108+
],
109+
],
110+
],
111+
'has_more' => false,
112+
'total_count' => 1,
113+
],
114+
]);
115+
}
116+
117+
private function mockStripeSubscriptionRetrieve(string $subscriptionId): void
118+
{
119+
$mockSubscription = Subscription::constructFrom([
120+
'id' => $subscriptionId,
121+
'metadata' => [],
122+
'current_period_end' => now()->addYear()->timestamp,
123+
]);
124+
125+
$mockSubscriptionsService = $this->createMock(SubscriptionService::class);
126+
$mockSubscriptionsService->method('retrieve')->willReturn($mockSubscription);
127+
128+
$mockStripeClient = $this->createMock(StripeClient::class);
129+
$mockStripeClient->subscriptions = $mockSubscriptionsService;
130+
131+
$this->app->bind(StripeClient::class, fn () => $mockStripeClient);
132+
}
133+
}

0 commit comments

Comments
 (0)