2024-11-22 13:42:10 +00:00
< ? php
namespace App\Jobs ;
use App\Models\Subscription ;
use App\Models\Team ;
2026-03-10 13:05:05 +00:00
use Illuminate\Contracts\Queue\ShouldBeEncrypted ;
2024-11-22 13:42:10 +00:00
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Queue\Queueable ;
use Illuminate\Support\Str ;
2026-03-10 13:05:05 +00:00
class StripeProcessJob implements ShouldBeEncrypted , ShouldQueue
2024-11-22 13:42:10 +00:00
{
use Queueable ;
public $type ;
public $webhook ;
public $tries = 3 ;
public function __construct ( public $event )
{
$this -> onQueue ( 'high' );
}
public function handle () : void
{
2024-11-25 10:28:08 +00:00
try {
$excludedPlans = config ( 'subscription.stripe_excluded_plans' );
2024-11-22 13:42:10 +00:00
2024-11-25 10:28:08 +00:00
$type = data_get ( $this -> event , 'type' );
$this -> type = $type ;
$data = data_get ( $this -> event , 'data.object' );
switch ( $type ) {
case 'radar.early_fraud_warning.created' :
2025-01-07 14:31:43 +00:00
$stripe = new \Stripe\StripeClient ( config ( 'subscription.stripe_api_key' ));
2024-11-25 10:28:08 +00:00
$id = data_get ( $data , 'id' );
$charge = data_get ( $data , 'charge' );
if ( $charge ) {
2025-01-07 14:31:43 +00:00
$stripe -> refunds -> create ([ 'charge' => $charge ]);
2024-11-25 10:28:08 +00:00
}
$pi = data_get ( $data , 'payment_intent' );
2025-01-07 14:31:43 +00:00
$piData = $stripe -> paymentIntents -> retrieve ( $pi , []);
2024-11-25 10:28:08 +00:00
$customerId = data_get ( $piData , 'customer' );
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2024-11-22 13:42:10 +00:00
if ( $subscription ) {
2024-11-25 10:28:08 +00:00
$subscriptionId = data_get ( $subscription , 'stripe_subscription_id' );
2025-01-07 14:31:43 +00:00
$stripe -> subscriptions -> cancel ( $subscriptionId , []);
2024-11-25 10:28:08 +00:00
$subscription -> update ([
'stripe_invoice_paid' => false ,
]);
send_internal_notification ( " Early fraud warning created Refunded, subscription canceled. Charge: { $charge } , id: { $id } , pi: { $pi } " );
} else {
send_internal_notification ( " Early fraud warning: subscription not found. Charge: { $charge } , id: { $id } , pi: { $pi } " );
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " Early fraud warning: subscription not found. Charge: { $charge } , id: { $id } , pi: { $pi } " );
2024-11-25 10:28:08 +00:00
}
2024-11-22 13:42:10 +00:00
break ;
2024-11-25 10:28:08 +00:00
case 'checkout.session.completed' :
$clientReferenceId = data_get ( $data , 'client_reference_id' );
if ( is_null ( $clientReferenceId )) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Checkout session completed without client reference id.');
2024-11-25 10:28:08 +00:00
break ;
}
$userId = Str :: before ( $clientReferenceId , ':' );
$teamId = Str :: after ( $clientReferenceId , ':' );
$subscriptionId = data_get ( $data , 'subscription' );
$customerId = data_get ( $data , 'customer' );
2025-01-07 14:31:43 +00:00
$team = Team :: find ( $teamId );
2024-11-25 10:28:08 +00:00
$found = $team -> members -> where ( 'id' , $userId ) -> first ();
if ( ! $found -> isAdmin ()) {
2025-09-13 17:35:32 +00:00
// send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " User { $userId } is not an admin or owner of team { $team -> id } , customerid: { $customerId } , subscriptionid: { $subscriptionId } . " );
2024-11-25 10:28:08 +00:00
}
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'team_id' , $teamId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( $subscription ) {
2025-03-01 11:43:21 +00:00
// send_internal_notification('Old subscription activated for team: '.$teamId);
2024-11-25 10:28:08 +00:00
$subscription -> update ([
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
'stripe_invoice_paid' => true ,
2025-03-01 11:43:21 +00:00
'stripe_past_due' => false ,
2024-11-25 10:28:08 +00:00
]);
} else {
2025-03-01 11:43:21 +00:00
// send_internal_notification('New subscription for team: '.$teamId);
2025-01-07 14:31:43 +00:00
Subscription :: create ([
2024-11-22 13:42:10 +00:00
'team_id' => $teamId ,
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
2024-11-25 10:28:08 +00:00
'stripe_invoice_paid' => true ,
2025-03-01 11:43:21 +00:00
'stripe_past_due' => false ,
2024-11-25 10:28:08 +00:00
]);
}
break ;
case 'invoice.paid' :
$customerId = data_get ( $data , 'customer' );
2025-09-23 09:00:38 +00:00
$invoiceAmount = data_get ( $data , 'amount_paid' , 0 );
$subscriptionId = data_get ( $data , 'subscription' );
2024-11-25 10:28:08 +00:00
$planId = data_get ( $data , 'lines.data.0.plan.id' );
if ( Str :: contains ( $excludedPlans , $planId )) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Subscription excluded.');
2024-11-25 10:28:08 +00:00
break ;
}
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2025-09-23 09:00:38 +00:00
if ( ! $subscription ) {
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No subscription found for customer: { $customerId } " );
2024-11-25 10:28:08 +00:00
}
2025-09-23 09:00:38 +00:00
if ( $subscription -> stripe_subscription_id ) {
try {
$stripe = new \Stripe\StripeClient ( config ( 'subscription.stripe_api_key' ));
$stripeSubscription = $stripe -> subscriptions -> retrieve (
$subscription -> stripe_subscription_id
);
switch ( $stripeSubscription -> status ) {
case 'active' :
$subscription -> update ([
'stripe_invoice_paid' => true ,
'stripe_past_due' => false ,
]);
break ;
case 'past_due' :
$subscription -> update ([
'stripe_invoice_paid' => true ,
'stripe_past_due' => true ,
]);
break ;
case 'canceled' :
case 'incomplete_expired' :
case 'unpaid' :
send_internal_notification (
" Invoice paid for { $stripeSubscription -> status } subscription. " .
" Customer: { $customerId } , Amount: \$ { $invoiceAmount } "
);
break ;
default :
VerifyStripeSubscriptionStatusJob :: dispatch ( $subscription )
-> delay ( now () -> addSeconds ( 20 ));
break ;
}
} catch ( \Exception $e ) {
VerifyStripeSubscriptionStatusJob :: dispatch ( $subscription )
-> delay ( now () -> addSeconds ( 20 ));
send_internal_notification (
'Failed to verify subscription status in invoice.paid: ' . $e -> getMessage ()
);
}
} else {
VerifyStripeSubscriptionStatusJob :: dispatch ( $subscription )
-> delay ( now () -> addSeconds ( 20 ));
}
2024-11-25 10:28:08 +00:00
break ;
case 'invoice.payment_failed' :
$customerId = data_get ( $data , 'customer' );
2025-09-13 17:35:32 +00:00
$invoiceId = data_get ( $data , 'id' );
$paymentIntentId = data_get ( $data , 'payment_intent' );
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( ! $subscription ) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No subscription found for customer: { $customerId } " );
2024-11-25 10:28:08 +00:00
}
2024-11-22 13:42:10 +00:00
$team = data_get ( $subscription , 'team' );
2024-11-25 10:28:08 +00:00
if ( ! $team ) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No team found in Coolify for customer: { $customerId } " );
2024-11-25 10:28:08 +00:00
}
2025-09-13 17:35:32 +00:00
// Verify payment status with Stripe API before sending failure notification
if ( $paymentIntentId ) {
try {
$stripe = new \Stripe\StripeClient ( config ( 'subscription.stripe_api_key' ));
$paymentIntent = $stripe -> paymentIntents -> retrieve ( $paymentIntentId );
if ( in_array ( $paymentIntent -> status , [ 'processing' , 'succeeded' , 'requires_action' , 'requires_confirmation' ])) {
break ;
}
if ( ! $subscription -> stripe_invoice_paid && $subscription -> created_at -> diffInMinutes ( now ()) < 5 ) {
SubscriptionInvoiceFailedJob :: dispatch ( $team ) -> delay ( now () -> addSeconds ( 60 ));
break ;
}
} catch ( \Exception $e ) {
}
}
2024-11-25 10:28:08 +00:00
if ( ! $subscription -> stripe_invoice_paid ) {
SubscriptionInvoiceFailedJob :: dispatch ( $team );
2025-03-01 11:43:21 +00:00
// send_internal_notification('Invoice payment failed: '.$customerId);
2024-11-25 10:28:08 +00:00
}
break ;
case 'payment_intent.payment_failed' :
$customerId = data_get ( $data , 'customer' );
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( ! $subscription ) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No subscription found in Coolify for customer: { $customerId } " );
2024-11-25 10:28:08 +00:00
}
if ( $subscription -> stripe_invoice_paid ) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
2024-11-25 10:28:08 +00:00
return ;
}
2025-03-01 11:43:21 +00:00
// send_internal_notification('Subscription payment failed for customer: '.$customerId);
2024-11-25 10:28:08 +00:00
break ;
case 'customer.subscription.created' :
$customerId = data_get ( $data , 'customer' );
$subscriptionId = data_get ( $data , 'id' );
$teamId = data_get ( $data , 'metadata.team_id' );
$userId = data_get ( $data , 'metadata.user_id' );
if ( ! $teamId || ! $userId ) {
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( $subscription ) {
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " Subscription already exists for customer: { $customerId } " );
2024-11-25 10:28:08 +00:00
}
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( 'No team id or user id found' );
2024-11-25 10:28:08 +00:00
}
2025-01-07 14:31:43 +00:00
$team = Team :: find ( $teamId );
2024-11-25 10:28:08 +00:00
$found = $team -> members -> where ( 'id' , $userId ) -> first ();
if ( ! $found -> isAdmin ()) {
2025-09-13 17:35:32 +00:00
// send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " User { $userId } is not an admin or owner of team { $team -> id } , customerid: { $customerId } . " );
2024-11-25 10:28:08 +00:00
}
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'team_id' , $teamId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( $subscription ) {
2025-03-01 11:43:21 +00:00
// send_internal_notification("Subscription already exists for team: {$teamId}");
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " Subscription already exists for team: { $teamId } " );
} else {
Subscription :: create ([
'team_id' => $teamId ,
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
'stripe_invoice_paid' => false ,
]);
2024-11-22 13:42:10 +00:00
}
2024-11-25 10:28:08 +00:00
case 'customer.subscription.updated' :
$teamId = data_get ( $data , 'metadata.team_id' );
$userId = data_get ( $data , 'metadata.user_id' );
$customerId = data_get ( $data , 'customer' );
$status = data_get ( $data , 'status' );
2025-01-07 14:31:43 +00:00
$subscriptionId = data_get ( $data , 'items.data.0.subscription' ) ? ? data_get ( $data , 'id' );
$planId = data_get ( $data , 'items.data.0.plan.id' ) ? ? data_get ( $data , 'plan.id' );
2024-11-25 10:28:08 +00:00
if ( Str :: contains ( $excludedPlans , $planId )) {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Subscription excluded.');
2024-11-25 10:28:08 +00:00
break ;
}
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
2024-11-25 10:28:08 +00:00
if ( ! $subscription ) {
if ( $status === 'incomplete_expired' ) {
2025-03-01 11:43:21 +00:00
// send_internal_notification('Subscription incomplete expired');
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( 'Subscription incomplete expired' );
2024-11-25 10:28:08 +00:00
}
if ( $teamId ) {
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: create ([
2024-11-25 10:28:08 +00:00
'team_id' => $teamId ,
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
'stripe_invoice_paid' => false ,
]);
} else {
2025-09-13 17:35:32 +00:00
// send_internal_notification('No subscription and team id found');
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( 'No subscription and team id found' );
2024-11-25 10:28:08 +00:00
}
}
$cancelAtPeriodEnd = data_get ( $data , 'cancel_at_period_end' );
$feedback = data_get ( $data , 'cancellation_details.feedback' );
$comment = data_get ( $data , 'cancellation_details.comment' );
$lookup_key = data_get ( $data , 'items.data.0.price.lookup_key' );
if ( str ( $lookup_key ) -> contains ( 'dynamic' )) {
$quantity = data_get ( $data , 'items.data.0.quantity' , 2 );
$team = data_get ( $subscription , 'team' );
if ( $team ) {
$team -> update ([
'custom_server_limit' => $quantity ,
]);
}
ServerLimitCheckJob :: dispatch ( $team );
}
2024-11-22 13:42:10 +00:00
$subscription -> update ([
2024-11-25 10:28:08 +00:00
'stripe_feedback' => $feedback ,
'stripe_comment' => $comment ,
'stripe_plan_id' => $planId ,
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd ,
2024-11-22 13:42:10 +00:00
]);
2025-03-01 11:43:21 +00:00
if ( $status === 'paused' || $status === 'incomplete_expired' ) {
if ( $subscription -> stripe_subscription_id === $subscriptionId ) {
$subscription -> update ([
'stripe_invoice_paid' => false ,
]);
}
}
if ( $status === 'past_due' ) {
if ( $subscription -> stripe_subscription_id === $subscriptionId ) {
$subscription -> update ([
'stripe_past_due' => true ,
]);
2025-09-13 17:35:32 +00:00
// send_internal_notification('Past Due: '.$customerId.'Subscription ID: '.$subscriptionId);
2025-03-01 11:43:21 +00:00
}
}
if ( $status === 'unpaid' ) {
2025-01-07 14:31:43 +00:00
if ( $subscription -> stripe_subscription_id === $subscriptionId ) {
$subscription -> update ([
'stripe_invoice_paid' => false ,
]);
2025-09-13 17:35:32 +00:00
// send_internal_notification('Unpaid: '.$customerId.'Subscription ID: '.$subscriptionId);
2025-03-01 11:43:21 +00:00
}
$team = data_get ( $subscription , 'team' );
if ( $team ) {
$team -> subscriptionEnded ();
} else {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Subscription unpaid but no team found in Coolify for customer: '.$customerId);
2025-03-01 11:43:21 +00:00
throw new \RuntimeException ( " No team found in Coolify for customer: { $customerId } " );
2025-01-07 14:31:43 +00:00
}
2024-12-13 07:18:08 +00:00
}
2025-01-07 14:31:43 +00:00
if ( $status === 'active' ) {
if ( $subscription -> stripe_subscription_id === $subscriptionId ) {
$subscription -> update ([
2025-03-01 11:43:21 +00:00
'stripe_past_due' => false ,
2025-01-07 14:31:43 +00:00
'stripe_invoice_paid' => true ,
]);
}
2024-11-25 10:28:08 +00:00
}
if ( $feedback ) {
$reason = " Cancellation feedback for { $customerId } : ' " . $feedback . " ' " ;
if ( $comment ) {
$reason .= ' with comment: \'' . $comment . " ' " ;
}
}
2024-12-13 07:18:08 +00:00
2024-11-25 10:28:08 +00:00
break ;
case 'customer.subscription.deleted' :
$customerId = data_get ( $data , 'customer' );
2024-12-13 07:18:08 +00:00
$subscriptionId = data_get ( $data , 'id' );
2025-01-07 14:31:43 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> where ( 'stripe_subscription_id' , $subscriptionId ) -> first ();
2024-12-13 07:18:08 +00:00
if ( $subscription ) {
$team = data_get ( $subscription , 'team' );
if ( $team ) {
$team -> subscriptionEnded ();
} else {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Subscription deleted but no team found in Coolify for customer: '.$customerId);
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No team found in Coolify for customer: { $customerId } " );
2024-12-13 07:18:08 +00:00
}
} else {
2025-09-13 17:35:32 +00:00
// send_internal_notification('Subscription deleted but no subscription found in Coolify for customer: '.$customerId);
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " No subscription found in Coolify for customer: { $customerId } " );
2024-12-13 07:18:08 +00:00
}
2024-11-25 10:28:08 +00:00
break ;
default :
2025-01-07 14:31:43 +00:00
throw new \RuntimeException ( " Unhandled event type: { $type } " );
2024-11-25 10:28:08 +00:00
}
2025-01-07 14:31:43 +00:00
} catch ( \Exception $e ) {
2024-11-25 10:28:08 +00:00
send_internal_notification ( 'StripeProcessJob error: ' . $e -> getMessage ());
2024-11-22 13:42:10 +00:00
}
}
}