11<?php
22
3- namespace App \Jobs ;
3+ namespace App \Actions \ Stripe ;
44
55use App \Models \Subscription ;
6- use Illuminate \Bus \Queueable ;
7- use Illuminate \Contracts \Queue \ShouldBeEncrypted ;
8- use Illuminate \Contracts \Queue \ShouldQueue ;
9- use Illuminate \Foundation \Bus \Dispatchable ;
10- use Illuminate \Queue \InteractsWithQueue ;
11- use Illuminate \Queue \SerializesModels ;
6+ use Lorisleiva \Actions \Concerns \AsAction ;
127use Stripe \StripeClient ;
138
14- class SyncStripeSubscriptionsJob implements ShouldBeEncrypted, ShouldQueue
9+ class SyncStripeSubscriptions
1510{
16- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels ;
11+ use AsAction ;
1712
18- public int $ tries = 1 ;
13+ private const VALID_STRIPE_STATUSES = [ ' active ' , ' past_due ' ] ;
1914
20- public int $ timeout = 1800 ; // 30 minutes max
21-
22- public function __construct (public bool $ fix = false )
23- {
24- $ this ->onQueue ('high ' );
25- }
26-
27- public function handle (?\Closure $ onProgress = null ): array
15+ public function handle (bool $ fix = false , ?\Closure $ onProgress = null ): array
2816 {
2917 if (! isCloud () || ! isStripe ()) {
3018 return ['error ' => 'Not running on Cloud or Stripe not configured ' ];
@@ -34,7 +22,9 @@ public function handle(?\Closure $onProgress = null): array
3422 ->where ('stripe_invoice_paid ' , true )
3523 ->get ();
3624
37- $ stripe = app (StripeClient::class);
25+ $ stripe = app ()->bound (StripeClient::class)
26+ ? app (StripeClient::class)
27+ : new StripeClient (config ('subscription.stripe_api_key ' ));
3828
3929 // Bulk fetch all valid subscription IDs from Stripe (active + past_due)
4030 $ validStripeIds = $ this ->fetchValidStripeSubscriptionIds ($ stripe , $ onProgress );
@@ -43,13 +33,20 @@ public function handle(?\Closure $onProgress = null): array
4333 $ staleSubscriptions = $ subscriptions ->filter (
4434 fn (Subscription $ sub ) => ! in_array ($ sub ->stripe_subscription_id , $ validStripeIds )
4535 );
36+ $ staleSubscriptionCount = $ staleSubscriptions ->count ();
37+
38+ $ onProgress ?->__invoke('checking ' , 0 , $ staleSubscriptionCount );
4639
4740 // For each stale subscription, get the exact Stripe status and check for resubscriptions
4841 $ discrepancies = [];
4942 $ resubscribed = [];
5043 $ errors = [];
44+ $ fixedCount = 0 ;
45+ $ manualReviewCount = 0 ;
46+
47+ foreach ($ staleSubscriptions ->values () as $ index => $ subscription ) {
48+ $ onProgress ?->__invoke('checking ' , $ index + 1 , $ staleSubscriptionCount );
5149
52- foreach ($ staleSubscriptions as $ subscription ) {
5350 try {
5451 $ stripeSubscription = $ stripe ->subscriptions ->retrieve (
5552 $ subscription ->stripe_subscription_id
@@ -66,8 +63,18 @@ public function handle(?\Closure $onProgress = null): array
6663 continue ;
6764 }
6865
69- // Check if this user resubscribed under a different customer/subscription
66+ if (in_array ($ stripeStatus , self ::VALID_STRIPE_STATUSES , true )) {
67+ continue ;
68+ }
69+
7070 $ activeSub = $ this ->findActiveSubscriptionByEmail ($ stripe , $ stripeSubscription ->customer );
71+ $ validReplacement = Subscription::query ()
72+ ->where ('team_id ' , $ subscription ->team_id )
73+ ->where ('id ' , '!= ' , $ subscription ->id )
74+ ->where ('stripe_invoice_paid ' , true )
75+ ->whereIn ('stripe_subscription_id ' , $ validStripeIds )
76+ ->first ();
77+
7178 if ($ activeSub ) {
7279 $ resubscribed [] = [
7380 'subscription_id ' => $ subscription ->id ,
@@ -78,33 +85,69 @@ public function handle(?\Closure $onProgress = null): array
7885 'new_stripe_subscription_id ' => $ activeSub ['subscription_id ' ],
7986 'new_stripe_customer_id ' => $ activeSub ['customer_id ' ],
8087 'new_status ' => $ activeSub ['status ' ],
88+ 'linked_to_team ' => $ validReplacement ?->stripe_subscription_id === $ activeSub ['subscription_id ' ],
8189 ];
90+ }
8291
83- continue ;
92+ $ inactiveSubscription = null ;
93+ if (! $ validReplacement && ! $ activeSub ) {
94+ $ inactiveSubscription = Subscription::query ()
95+ ->where ('team_id ' , $ subscription ->team_id )
96+ ->where ('id ' , '!= ' , $ subscription ->id )
97+ ->where ('stripe_invoice_paid ' , false )
98+ ->first ();
8499 }
85100
101+ $ resolution = match (true ) {
102+ (bool ) $ validReplacement => 'delete_stale ' ,
103+ (bool ) $ activeSub => 'manual_review ' ,
104+ (bool ) $ inactiveSubscription => 'delete_stale ' ,
105+ default => 'end_subscription ' ,
106+ };
107+
86108 $ discrepancies [] = [
87109 'subscription_id ' => $ subscription ->id ,
88110 'team_id ' => $ subscription ->team_id ,
89111 'stripe_subscription_id ' => $ subscription ->stripe_subscription_id ,
90112 'stripe_status ' => $ stripeStatus ,
113+ 'resolution ' => $ resolution ,
91114 ];
92115
93- if ($ this ->fix ) {
94- $ subscription ->update ([
95- 'stripe_invoice_paid ' => false ,
96- 'stripe_past_due ' => false ,
97- ]);
116+ if ($ fix ) {
117+ $ team = $ subscription ->team ;
118+
119+ if ($ resolution === 'manual_review ' ) {
120+ $ manualReviewCount ++;
121+
122+ continue ;
123+ }
124+
125+ if ($ resolution === 'delete_stale ' ) {
126+ if (! $ validReplacement && $ inactiveSubscription && $ team ) {
127+ $ team ->subscriptionEnded ($ inactiveSubscription );
128+ }
129+
130+ $ subscription ->delete ();
131+ $ fixedCount ++;
132+
133+ continue ;
134+ }
98135
99- if ($ stripeStatus === 'canceled ' ) {
100- $ subscription ->team ?->subscriptionEnded();
136+ if ($ team ) {
137+ $ team ->subscriptionEnded ($ subscription );
138+ } else {
139+ $ subscription ->update ([
140+ 'stripe_invoice_paid ' => false ,
141+ 'stripe_past_due ' => false ,
142+ ]);
101143 }
144+ $ fixedCount ++;
102145 }
103146 }
104147
105- if ($ this -> fix && count ( $ discrepancies ) > 0 ) {
148+ if ($ fix && $ fixedCount > 0 ) {
106149 send_internal_notification (
107- ' SyncStripeSubscriptionsJob : Fixed ' . count ( $ discrepancies ). " discrepancies: \n" .
150+ " SyncStripeSubscriptions : Fixed { $ fixedCount } discrepancies: \n" .
108151 json_encode ($ discrepancies , JSON_PRETTY_PRINT )
109152 );
110153 }
@@ -114,7 +157,9 @@ public function handle(?\Closure $onProgress = null): array
114157 'discrepancies ' => $ discrepancies ,
115158 'resubscribed ' => $ resubscribed ,
116159 'errors ' => $ errors ,
117- 'fixed ' => $ this ->fix ,
160+ 'fixed ' => $ fix ,
161+ 'fixed_count ' => $ fixedCount ,
162+ 'manual_review_count ' => $ manualReviewCount ,
118163 ];
119164 }
120165
@@ -183,13 +228,13 @@ private function fetchValidStripeSubscriptionIds(StripeClient $stripe, ?\Closure
183228 $ validIds = [];
184229 $ fetched = 0 ;
185230
186- foreach ([ ' active ' , ' past_due ' ] as $ status ) {
231+ foreach (self :: VALID_STRIPE_STATUSES as $ status ) {
187232 foreach ($ stripe ->subscriptions ->all (['status ' => $ status , 'limit ' => 100 ])->autoPagingIterator () as $ sub ) {
188233 $ validIds [] = $ sub ->id ;
189234 $ fetched ++;
190235
191236 if ($ onProgress ) {
192- $ onProgress ($ fetched );
237+ $ onProgress (' fetching ' , $ fetched, null );
193238 }
194239 }
195240 }
0 commit comments