Skip to content

Commit 8b17e8c

Browse files
authored
Merge pull request #130 from NativePHP/store-anystack-ids
Store anystack contact ids on users table
2 parents 465b58e + c7b0cf1 commit 8b17e8c

9 files changed

+122
-31
lines changed

app/Jobs/CreateAnystackLicenseJob.php

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Jobs;
44

55
use App\Enums\Subscription;
6+
use App\Models\User;
67
use App\Notifications\LicenseKeyGenerated;
78
use Illuminate\Bus\Queueable;
89
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -12,41 +13,44 @@
1213
use Illuminate\Queue\SerializesModels;
1314
use Illuminate\Support\Facades\Cache;
1415
use Illuminate\Support\Facades\Http;
15-
use Illuminate\Support\Facades\Notification;
1616

1717
class CreateAnystackLicenseJob implements ShouldQueue
1818
{
1919
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
2020

2121
public function __construct(
22-
public string $email,
22+
public User $user,
2323
public Subscription $subscription,
2424
public ?string $firstName = null,
2525
public ?string $lastName = null,
2626
) {}
2727

2828
public function handle(): void
2929
{
30-
$contact = $this->createContact();
30+
if (! $this->user->anystack_contact_id) {
31+
$contact = $this->createContact();
3132

32-
$license = $this->createLicense($contact['id']);
33+
$this->user->anystack_contact_id = $contact['id'];
34+
$this->user->save();
35+
}
3336

34-
Cache::put($this->email.'.license_key', $license['key'], now()->addDay());
37+
$license = $this->createLicense($this->user->anystack_contact_id);
3538

36-
Notification::route('mail', $this->email)
37-
->notify(new LicenseKeyGenerated(
38-
$license['key'],
39-
$this->subscription,
40-
$this->firstName
41-
));
39+
Cache::put($this->user->email.'.license_key', $license['key'], now()->addDay());
40+
41+
$this->user->notify(new LicenseKeyGenerated(
42+
$license['key'],
43+
$this->subscription,
44+
$this->firstName
45+
));
4246
}
4347

4448
private function createContact(): array
4549
{
4650
$data = collect([
4751
'first_name' => $this->firstName,
4852
'last_name' => $this->lastName,
49-
'email' => $this->email,
53+
'email' => $this->user->email,
5054
])
5155
->filter()
5256
->all();

app/Jobs/CreateUserFromStripeCustomer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Illuminate\Queue\SerializesModels;
1111
use Illuminate\Support\Facades\Hash;
1212
use Illuminate\Support\Facades\Log;
13+
use Illuminate\Support\Facades\Validator;
1314
use Illuminate\Support\Str;
1415
use Laravel\Cashier\Cashier;
1516
use Stripe\Customer;
@@ -39,6 +40,10 @@ public function handle(): void
3940
return;
4041
}
4142

43+
Validator::validate(['email' => $this->customer->email], [
44+
'email' => 'required|email|max:255',
45+
]);
46+
4247
$user = new User;
4348
$user->name = $this->customer->name;
4449
$user->email = $this->customer->email;

app/Jobs/HandleCustomerSubscriptionCreatedJob.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Jobs;
44

5+
use App\Models\User;
56
use Illuminate\Bus\Queueable;
67
use Illuminate\Contracts\Queue\ShouldQueue;
78
use Illuminate\Foundation\Bus\Dispatchable;
@@ -27,9 +28,10 @@ public function handle(): void
2728
return;
2829
}
2930

31+
/** @var User $user */
3032
$user = Cashier::findBillable($stripeSubscription->customer);
3133

32-
if (! $user || ! ($email = $user->email)) {
34+
if (! $user || ! $user->email) {
3335
$this->fail('Failed to find user from Stripe subscription customer.');
3436

3537
return;
@@ -42,7 +44,7 @@ public function handle(): void
4244
$lastName = $nameParts[1] ?? null;
4345

4446
dispatch(new CreateAnystackLicenseJob(
45-
$email,
47+
$user,
4648
$subscriptionPlan,
4749
$firstName,
4850
$lastName,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('users', function (Blueprint $table) {
15+
$table->string('anystack_contact_id')->nullable()->index();
16+
});
17+
}
18+
19+
/**
20+
* Reverse the migrations.
21+
*/
22+
public function down(): void
23+
{
24+
Schema::table('users', function (Blueprint $table) {
25+
$table->dropIndex([
26+
'anystack_contact_id',
27+
]);
28+
29+
$table->dropColumn([
30+
'anystack_contact_id',
31+
]);
32+
});
33+
}
34+
};

tests/Feature/Jobs/CreateAnystackLicenseJobTest.php

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

55
use App\Enums\Subscription;
66
use App\Jobs\CreateAnystackLicenseJob;
7+
use App\Models\User;
78
use App\Notifications\LicenseKeyGenerated;
89
use Illuminate\Foundation\Testing\RefreshDatabase;
910
use Illuminate\Support\Facades\Cache;
@@ -45,8 +46,13 @@ protected function setUp(): void
4546
/** @test */
4647
public function it_creates_contact_and_license_on_anystack()
4748
{
49+
$user = User::factory()->create([
50+
'email' => '[email protected]',
51+
'name' => 'John Doe',
52+
]);
53+
4854
$job = new CreateAnystackLicenseJob(
49-
55+
$user,
5056
Subscription::Max,
5157
'John',
5258
'Doe'
@@ -79,8 +85,13 @@ public function it_creates_contact_and_license_on_anystack()
7985
/** @test */
8086
public function it_stores_license_key_in_cache()
8187
{
88+
$user = User::factory()->create([
89+
'email' => '[email protected]',
90+
'name' => 'John Doe',
91+
]);
92+
8293
$job = new CreateAnystackLicenseJob(
83-
94+
$user,
8495
Subscription::Max,
8596
'John',
8697
'Doe'
@@ -94,20 +105,24 @@ public function it_stores_license_key_in_cache()
94105
/** @test */
95106
public function it_sends_license_key_notification()
96107
{
108+
$user = User::factory()->create([
109+
'email' => '[email protected]',
110+
'name' => 'John Doe',
111+
]);
112+
97113
$job = new CreateAnystackLicenseJob(
98-
114+
$user,
99115
Subscription::Max,
100116
'John',
101117
'Doe'
102118
);
103119

104120
$job->handle();
105121

106-
Notification::assertSentOnDemand(
107-
LicenseKeyGenerated::class,
122+
Notification::assertSentTo(
123+
$user,
108124
function (LicenseKeyGenerated $notification, array $channels, object $notifiable) {
109-
return $notifiable->routes['mail'] === '[email protected]' &&
110-
$notification->licenseKey === 'test-license-key-12345' &&
125+
return $notification->licenseKey === 'test-license-key-12345' &&
111126
$notification->subscription === Subscription::Max &&
112127
$notification->firstName === 'John';
113128
}
@@ -117,9 +132,14 @@ function (LicenseKeyGenerated $notification, array $channels, object $notifiable
117132
/** @test */
118133
public function it_handles_missing_name_components()
119134
{
135+
$user = User::factory()->create([
136+
'email' => '[email protected]',
137+
'name' => null,
138+
]);
139+
120140
// Create and run the job with missing name components
121141
$job = new CreateAnystackLicenseJob(
122-
142+
$user,
123143
Subscription::Max,
124144
);
125145

@@ -135,11 +155,10 @@ public function it_handles_missing_name_components()
135155
});
136156

137157
// Assert notification was sent with null firstName
138-
Notification::assertSentOnDemand(
139-
LicenseKeyGenerated::class,
158+
Notification::assertSentTo(
159+
$user,
140160
function (LicenseKeyGenerated $notification, array $channels, object $notifiable) {
141-
return $notifiable->routes['mail'] === '[email protected]' &&
142-
$notification->licenseKey === 'test-license-key-12345' &&
161+
return $notification->licenseKey === 'test-license-key-12345' &&
143162
$notification->subscription === Subscription::Max &&
144163
$notification->firstName === null;
145164
}

tests/Feature/Jobs/CreateUserFromStripeCustomerTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Models\User;
77
use Illuminate\Foundation\Testing\RefreshDatabase;
88
use Illuminate\Support\Facades\Hash;
9+
use Illuminate\Validation\ValidationException;
910
use Stripe\Customer;
1011
use Tests\TestCase;
1112

@@ -96,4 +97,22 @@ public function it_handles_a_null_name_in_stripe_customer()
9697
'stripe_id' => 'cus_noname123',
9798
]);
9899
}
100+
101+
/** @test */
102+
public function it_fails_when_customer_has_no_email()
103+
{
104+
$customer = Customer::constructFrom([
105+
'id' => 'cus_noemail123',
106+
'name' => 'No Email',
107+
'email' => '',
108+
]);
109+
110+
$job = new CreateUserFromStripeCustomer($customer);
111+
112+
$this->expectException(ValidationException::class);
113+
114+
$job->handle();
115+
116+
$this->assertDatabaseCount('users', 0);
117+
}
99118
}

tests/Feature/Jobs/HandleCustomerSubscriptionCreatedJobTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Jobs\CreateAnystackLicenseJob;
77
use App\Jobs\CreateUserFromStripeCustomer;
88
use App\Jobs\HandleCustomerSubscriptionCreatedJob;
9+
use App\Models\User;
910
use Illuminate\Foundation\Testing\RefreshDatabase;
1011
use Illuminate\Support\Facades\Bus;
1112
use Laravel\Cashier\Events\WebhookHandled;
@@ -38,7 +39,8 @@ public function it_dispatches_the_create_anystack_license_job_with_correct_data(
3839
$job->handle();
3940

4041
Bus::assertDispatched(CreateAnystackLicenseJob::class, function (CreateAnystackLicenseJob $job) {
41-
return $job->email === '[email protected]' &&
42+
return $job->user instanceof User &&
43+
$job->user->email === '[email protected]' &&
4244
$job->subscription === Subscription::Max &&
4345
$job->firstName === 'John' &&
4446
$job->lastName === 'Doe';
@@ -99,7 +101,11 @@ public function it_fails_when_customer_has_no_email()
99101

100102
$this->mockStripeClient($mockCustomer);
101103

102-
dispatch_sync(new CreateUserFromStripeCustomer($mockCustomer));
104+
User::factory()->create([
105+
'stripe_id' => 'cus_S9dhoV2rJK2Auy',
106+
'name' => 'John Doe',
107+
'email' => '',
108+
]);
103109

104110
Bus::fake();
105111

tests/Feature/Livewire/OrderSuccessTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ public function allLineItems()
153153
}
154154
};
155155

156-
$this->app->instance(StripeClient::class, $mockStripeClient);
156+
$this->app->bind(StripeClient::class, function ($app, $parameters) use ($mockStripeClient) {
157+
return $mockStripeClient;
158+
});
157159
}
158160
}

tests/Feature/StripePurchaseHandlingTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public function a_license_is_created_when_a_stripe_subscription_is_created()
9292

9393
$this->postJson('/stripe/webhook', $payload);
9494

95-
Bus::assertDispatched(CreateAnystackLicenseJob::class, function ($job) {
96-
return $job->email === '[email protected]' &&
95+
Bus::assertDispatched(CreateAnystackLicenseJob::class, function (CreateAnystackLicenseJob $job) {
96+
return $job->user->email === '[email protected]' &&
9797
$job->subscription === Subscription::Max &&
9898
$job->firstName === 'John' &&
9999
$job->lastName === 'Doe';

0 commit comments

Comments
 (0)