2023-05-09 09:33:50 +00:00
< ? php
2024-01-26 17:46:50 +00:00
use App\Enums\ProcessStatus ;
use App\Jobs\ApplicationPullRequestUpdateJob ;
2024-02-20 19:17:04 +00:00
use App\Jobs\GithubAppPermissionJob ;
2024-02-26 09:25:21 +00:00
use App\Jobs\ServerLimitCheckJob ;
2024-01-24 10:28:01 +00:00
use App\Jobs\SubscriptionInvoiceFailedJob ;
2023-09-12 09:19:21 +00:00
use App\Jobs\SubscriptionTrialEndedJob ;
use App\Jobs\SubscriptionTrialEndsSoonJob ;
2023-05-09 12:42:10 +00:00
use App\Models\Application ;
2023-05-31 09:24:02 +00:00
use App\Models\ApplicationPreview ;
2023-05-09 09:33:50 +00:00
use App\Models\GithubApp ;
2023-08-08 09:51:36 +00:00
use App\Models\PrivateKey ;
2023-07-13 20:03:27 +00:00
use App\Models\Subscription ;
2023-08-08 09:51:36 +00:00
use App\Models\Team ;
2023-08-15 12:11:38 +00:00
use App\Models\Waitlist ;
2023-08-08 09:51:36 +00:00
use App\Models\Webhook ;
2023-05-09 09:33:50 +00:00
use Illuminate\Support\Facades\Http ;
use Illuminate\Support\Facades\Route ;
2023-11-03 13:51:29 +00:00
use Illuminate\Support\Sleep ;
2023-05-09 12:42:10 +00:00
use Illuminate\Support\Str ;
use Visus\Cuid2\Cuid2 ;
2023-05-09 09:33:50 +00:00
Route :: get ( '/source/github/redirect' , function () {
try {
$code = request () -> get ( 'code' );
$state = request () -> get ( 'state' );
$github_app = GithubApp :: where ( 'uuid' , $state ) -> firstOrFail ();
$api_url = data_get ( $github_app , 'api_url' );
$data = Http :: withBody ( null ) -> accept ( 'application/vnd.github+json' ) -> post ( " $api_url /app-manifests/ $code /conversions " ) -> throw () -> json ();
$id = data_get ( $data , 'id' );
$slug = data_get ( $data , 'slug' );
$client_id = data_get ( $data , 'client_id' );
$client_secret = data_get ( $data , 'client_secret' );
$private_key = data_get ( $data , 'pem' );
$webhook_secret = data_get ( $data , 'webhook_secret' );
$private_key = PrivateKey :: create ([
'name' => $slug ,
'private_key' => $private_key ,
2023-06-19 08:58:00 +00:00
'team_id' => $github_app -> team_id ,
'is_git_related' => true ,
2023-05-09 09:33:50 +00:00
]);
2023-06-19 07:14:09 +00:00
$github_app -> name = $slug ;
2023-05-09 09:33:50 +00:00
$github_app -> app_id = $id ;
$github_app -> client_id = $client_id ;
$github_app -> client_secret = $client_secret ;
$github_app -> webhook_secret = $webhook_secret ;
$github_app -> private_key_id = $private_key -> id ;
$github_app -> save ();
return redirect () -> route ( 'source.github.show' , [ 'github_app_uuid' => $github_app -> uuid ]);
2023-08-08 09:51:36 +00:00
} catch ( Exception $e ) {
2023-09-15 13:34:25 +00:00
return handleError ( $e );
2023-05-09 09:33:50 +00:00
}
});
Route :: get ( '/source/github/install' , function () {
try {
$installation_id = request () -> get ( 'installation_id' );
$source = request () -> get ( 'source' );
$setup_action = request () -> get ( 'setup_action' );
2024-02-20 19:17:04 +00:00
ray ( request ());
2023-05-09 09:33:50 +00:00
$github_app = GithubApp :: where ( 'uuid' , $source ) -> firstOrFail ();
if ( $setup_action === 'install' ) {
$github_app -> installation_id = $installation_id ;
$github_app -> save ();
}
return redirect () -> route ( 'source.github.show' , [ 'github_app_uuid' => $github_app -> uuid ]);
2023-08-08 09:51:36 +00:00
} catch ( Exception $e ) {
2023-09-15 13:34:25 +00:00
return handleError ( $e );
2023-05-09 09:33:50 +00:00
}
});
2023-11-14 12:26:14 +00:00
Route :: post ( '/source/gitlab/events/manual' , function () {
try {
2023-12-15 13:17:53 +00:00
$return_payloads = collect ([]);
2023-11-14 12:26:14 +00:00
$payload = request () -> collect ();
$headers = request () -> headers -> all ();
$x_gitlab_token = data_get ( $headers , 'x-gitlab-token.0' );
$x_gitlab_event = data_get ( $payload , 'object_kind' );
if ( $x_gitlab_event === 'push' ) {
$branch = data_get ( $payload , 'ref' );
$full_name = data_get ( $payload , 'project.path_with_namespace' );
if ( Str :: isMatch ( '/refs\/heads\/*/' , $branch )) {
$branch = Str :: after ( $branch , 'refs/heads/' );
}
if ( ! $branch ) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'status' => 'failed' ,
'message' => 'Nothing to do. No branch found in the request.' ,
]);
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
}
ray ( 'Manual Webhook GitLab Push Event with branch: ' . $branch );
}
if ( $x_gitlab_event === 'merge_request' ) {
$action = data_get ( $payload , 'object_attributes.action' );
$branch = data_get ( $payload , 'object_attributes.source_branch' );
$base_branch = data_get ( $payload , 'object_attributes.target_branch' );
$full_name = data_get ( $payload , 'project.path_with_namespace' );
$pull_request_id = data_get ( $payload , 'object_attributes.iid' );
$pull_request_html_url = data_get ( $payload , 'object_attributes.url' );
if ( ! $branch ) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'status' => 'failed' ,
'message' => 'Nothing to do. No branch found in the request.' ,
]);
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
}
ray ( 'Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id );
}
2023-11-14 13:07:48 +00:00
$applications = Application :: where ( 'git_repository' , 'like' , " % $full_name % " );
2023-11-14 12:26:14 +00:00
if ( $x_gitlab_event === 'push' ) {
$applications = $applications -> where ( 'git_branch' , $branch ) -> get ();
if ( $applications -> isEmpty ()) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'status' => 'failed' ,
'message' => " Nothing to do. No applications found with deploy key set, branch is ' $branch ' and Git Repository name has $full_name . " ,
]);
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
}
}
if ( $x_gitlab_event === 'merge_request' ) {
$applications = $applications -> where ( 'git_branch' , $base_branch ) -> get ();
if ( $applications -> isEmpty ()) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'status' => 'failed' ,
'message' => " Nothing to do. No applications found with branch ' $base_branch '. " ,
]);
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
}
}
foreach ( $applications as $application ) {
$webhook_secret = data_get ( $application , 'manual_webhook_secret_gitlab' );
if ( $webhook_secret !== $x_gitlab_token ) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Invalid token.' ,
]);
2023-11-14 12:26:14 +00:00
ray ( 'Invalid signature' );
continue ;
}
$isFunctional = $application -> destination -> server -> isFunctional ();
if ( ! $isFunctional ) {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Server is not functional' ,
]);
2023-11-14 12:26:14 +00:00
ray ( 'Server is not functional: ' . $application -> destination -> server -> name );
continue ;
}
if ( $x_gitlab_event === 'push' ) {
if ( $application -> isDeployable ()) {
ray ( 'Deploying ' . $application -> name . ' with branch ' . $branch );
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-11-14 12:26:14 +00:00
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
is_webhook : true
);
} else {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Deployments disabled' ,
]);
2023-11-14 12:26:14 +00:00
ray ( 'Deployments disabled for ' . $application -> name );
}
}
if ( $x_gitlab_event === 'merge_request' ) {
2024-01-15 13:29:54 +00:00
if ( $action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update' ) {
2023-11-14 12:26:14 +00:00
if ( $application -> isPRDeployable ()) {
$deployment_uuid = new Cuid2 ( 7 );
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( ! $found ) {
ApplicationPreview :: create ([
'git_type' => 'gitlab' ,
'application_id' => $application -> id ,
'pull_request_id' => $pull_request_id ,
'pull_request_html_url' => $pull_request_html_url ,
]);
}
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-11-14 12:26:14 +00:00
pull_request_id : $pull_request_id ,
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
is_webhook : true ,
git_type : 'gitlab'
);
ray ( 'Deploying preview for ' . $application -> name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id );
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview Deployment queued' ,
]);
2023-11-14 12:26:14 +00:00
} else {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Preview deployments disabled' ,
]);
2023-11-14 12:26:14 +00:00
ray ( 'Preview deployments disabled for ' . $application -> name );
}
2024-01-15 13:29:54 +00:00
} else if ( $action === 'closed' || $action === 'close' ) {
2023-11-14 12:26:14 +00:00
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( $found ) {
$found -> delete ();
$container_name = generateApplicationContainerName ( $application , $pull_request_id );
// ray('Stopping container: ' . $container_name);
instant_remote_process ([ " docker rm -f $container_name " ], $application -> destination -> server );
2023-12-15 13:19:29 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview Deployment closed' ,
]);
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
}
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'No Preview Deployment found' ,
]);
2023-12-15 13:09:14 +00:00
} else {
2023-12-15 13:17:53 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'No action found. Contact us for debugging.' ,
]);
2023-11-14 12:26:14 +00:00
}
}
}
2023-12-15 13:17:53 +00:00
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
} catch ( Exception $e ) {
ray ( $e -> getMessage ());
return handleError ( $e );
}
});
2024-01-23 10:34:25 +00:00
Route :: post ( '/source/bitbucket/events/manual' , function () {
try {
$return_payloads = collect ([]);
$payload = request () -> collect ();
$headers = request () -> headers -> all ();
2024-01-29 09:43:18 +00:00
$x_bitbucket_token = data_get ( $headers , 'x-hub-signature.0' , " " );
$x_bitbucket_event = data_get ( $headers , 'x-event-key.0' , " " );
$handled_events = collect ([ 'repo:push' , 'pullrequest:created' , 'pullrequest:rejected' , 'pullrequest:fulfilled' ]);
if ( ! $handled_events -> contains ( $x_bitbucket_event )) {
return response ([
'status' => 'failed' ,
'message' => 'Nothing to do. Event not handled.' ,
]);
}
2024-01-23 10:34:25 +00:00
if ( $x_bitbucket_event === 'repo:push' ) {
$branch = data_get ( $payload , 'push.changes.0.new.name' );
2024-01-29 09:43:18 +00:00
$full_name = data_get ( $payload , 'repository.full_name' );
2024-01-23 10:34:25 +00:00
if ( ! $branch ) {
2024-01-29 09:43:18 +00:00
return response ([
2024-01-23 10:34:25 +00:00
'status' => 'failed' ,
'message' => 'Nothing to do. No branch found in the request.' ,
]);
}
2024-01-29 09:43:18 +00:00
ray ( 'Manual webhook bitbucket push event with branch: ' . $branch );
2024-01-23 10:34:25 +00:00
}
2024-01-29 09:43:18 +00:00
if ( $x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled' ) {
$branch = data_get ( $payload , 'pullrequest.destination.branch.name' );
$base_branch = data_get ( $payload , 'pullrequest.source.branch.name' );
$full_name = data_get ( $payload , 'repository.full_name' );
$pull_request_id = data_get ( $payload , 'pullrequest.id' );
$pull_request_html_url = data_get ( $payload , 'pullrequest.links.html.href' );
$commit = data_get ( $payload , 'pullrequest.source.commit.hash' );
}
$applications = Application :: where ( 'git_repository' , 'like' , " % $full_name % " );
$applications = $applications -> where ( 'git_branch' , $branch ) -> get ();
if ( $applications -> isEmpty ()) {
return response ([
'status' => 'failed' ,
'message' => " Nothing to do. No applications found with deploy key set, branch is ' $branch ' and Git Repository name has $full_name . " ,
]);
}
foreach ( $applications as $application ) {
2024-01-23 10:34:25 +00:00
$webhook_secret = data_get ( $application , 'manual_webhook_secret_bitbucket' );
$payload = request () -> getContent ();
list ( $algo , $hash ) = explode ( '=' , $x_bitbucket_token , 2 );
$payloadHash = hash_hmac ( $algo , $payload , $webhook_secret );
2024-01-29 09:43:18 +00:00
if ( ! hash_equals ( $hash , $payloadHash ) && ! isDev ()) {
2024-01-23 10:34:25 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Invalid token.' ,
]);
ray ( 'Invalid signature' );
continue ;
}
$isFunctional = $application -> destination -> server -> isFunctional ();
if ( ! $isFunctional ) {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
2024-01-29 09:43:18 +00:00
'message' => 'Server is not functional.' ,
2024-01-23 10:34:25 +00:00
]);
ray ( 'Server is not functional: ' . $application -> destination -> server -> name );
continue ;
}
if ( $x_bitbucket_event === 'repo:push' ) {
2024-01-29 15:31:10 +00:00
if ( $application -> isDeployable ()) {
2024-01-23 10:34:25 +00:00
ray ( 'Deploying ' . $application -> name . ' with branch ' . $branch );
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
2024-01-29 09:43:18 +00:00
application : $application ,
2024-01-23 10:34:25 +00:00
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
is_webhook : true
);
2024-01-29 09:43:18 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment queued.' ,
]);
2024-01-23 10:34:25 +00:00
} else {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
2024-01-29 15:31:10 +00:00
'message' => 'Auto deployment disabled.' ,
2024-01-29 09:43:18 +00:00
]);
}
}
if ( $x_bitbucket_event === 'pullrequest:created' ) {
if ( $application -> isPRDeployable ()) {
ray ( 'Deploying preview for ' . $application -> name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id );
$deployment_uuid = new Cuid2 ( 7 );
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( ! $found ) {
ApplicationPreview :: create ([
'git_type' => 'bitbucket' ,
'application_id' => $application -> id ,
'pull_request_id' => $pull_request_id ,
'pull_request_html_url' => $pull_request_html_url ,
]);
}
queue_application_deployment (
application : $application ,
pull_request_id : $pull_request_id ,
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
commit : $commit ,
is_webhook : true ,
git_type : 'bitbucket'
);
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment queued.' ,
]);
} else {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Preview deployments disabled.' ,
]);
}
}
if ( $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled' ) {
ray ( 'Pull request rejected' );
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( $found ) {
$found -> delete ();
$container_name = generateApplicationContainerName ( $application , $pull_request_id );
instant_remote_process ([ " docker rm -f $container_name " ], $application -> destination -> server );
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment closed.' ,
]);
} else {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'No preview deployment found.' ,
2024-01-23 10:34:25 +00:00
]);
}
}
}
2024-01-29 09:43:18 +00:00
ray ( $return_payloads );
2024-01-23 10:34:25 +00:00
return response ( $return_payloads );
} catch ( Exception $e ) {
2024-01-29 09:43:18 +00:00
ray ( $e );
2024-01-23 10:34:25 +00:00
return handleError ( $e );
}
});
2023-11-14 12:26:14 +00:00
Route :: post ( '/source/github/events/manual' , function () {
try {
2024-01-29 10:23:04 +00:00
$return_payloads = collect ([]);
2023-11-14 12:26:14 +00:00
$x_github_event = Str :: lower ( request () -> header ( 'X-GitHub-Event' ));
$x_hub_signature_256 = Str :: after ( request () -> header ( 'X-Hub-Signature-256' ), 'sha256=' );
$content_type = request () -> header ( 'Content-Type' );
$payload = request () -> collect ();
if ( $x_github_event === 'ping' ) {
// Just pong
return response ( 'pong' );
}
if ( $content_type !== 'application/json' ) {
$payload = json_decode ( data_get ( $payload , 'payload' ), true );
}
if ( $x_github_event === 'push' ) {
$branch = data_get ( $payload , 'ref' );
$full_name = data_get ( $payload , 'repository.full_name' );
if ( Str :: isMatch ( '/refs\/heads\/*/' , $branch )) {
$branch = Str :: after ( $branch , 'refs/heads/' );
}
ray ( 'Manual Webhook GitHub Push Event with branch: ' . $branch );
}
if ( $x_github_event === 'pull_request' ) {
$action = data_get ( $payload , 'action' );
$full_name = data_get ( $payload , 'repository.full_name' );
$pull_request_id = data_get ( $payload , 'number' );
$pull_request_html_url = data_get ( $payload , 'pull_request.html_url' );
$branch = data_get ( $payload , 'pull_request.head.ref' );
$base_branch = data_get ( $payload , 'pull_request.base.ref' );
ray ( 'Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id );
}
if ( ! $branch ) {
return response ( 'Nothing to do. No branch found in the request.' );
}
2023-11-14 13:07:48 +00:00
$applications = Application :: where ( 'git_repository' , 'like' , " % $full_name % " );
2023-11-14 12:26:14 +00:00
if ( $x_github_event === 'push' ) {
$applications = $applications -> where ( 'git_branch' , $branch ) -> get ();
if ( $applications -> isEmpty ()) {
return response ( " Nothing to do. No applications found with deploy key set, branch is ' $branch ' and Git Repository name has $full_name . " );
}
}
if ( $x_github_event === 'pull_request' ) {
$applications = $applications -> where ( 'git_branch' , $base_branch ) -> get ();
if ( $applications -> isEmpty ()) {
return response ( " Nothing to do. No applications found with branch ' $base_branch '. " );
}
}
foreach ( $applications as $application ) {
$webhook_secret = data_get ( $application , 'manual_webhook_secret_github' );
$hmac = hash_hmac ( 'sha256' , request () -> getContent (), $webhook_secret );
2024-01-29 10:23:04 +00:00
if ( ! hash_equals ( $x_hub_signature_256 , $hmac ) && ! isDev ()) {
2023-11-14 12:26:14 +00:00
ray ( 'Invalid signature' );
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Invalid token.' ,
]);
2023-11-14 12:26:14 +00:00
continue ;
}
$isFunctional = $application -> destination -> server -> isFunctional ();
if ( ! $isFunctional ) {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Server is not functional.' ,
]);
2023-11-14 12:26:14 +00:00
continue ;
}
if ( $x_github_event === 'push' ) {
if ( $application -> isDeployable ()) {
ray ( 'Deploying ' . $application -> name . ' with branch ' . $branch );
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-11-14 12:26:14 +00:00
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
2024-01-29 10:23:04 +00:00
is_webhook : true ,
2023-11-14 12:26:14 +00:00
);
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Deployment queued.' ,
]);
2023-11-14 12:26:14 +00:00
} else {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Deployments disabled.' ,
]);
2023-11-14 12:26:14 +00:00
}
}
if ( $x_github_event === 'pull_request' ) {
if ( $action === 'opened' || $action === 'synchronize' || $action === 'reopened' ) {
if ( $application -> isPRDeployable ()) {
$deployment_uuid = new Cuid2 ( 7 );
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( ! $found ) {
ApplicationPreview :: create ([
'git_type' => 'github' ,
'application_id' => $application -> id ,
'pull_request_id' => $pull_request_id ,
'pull_request_html_url' => $pull_request_html_url ,
]);
}
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-11-14 12:26:14 +00:00
pull_request_id : $pull_request_id ,
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
is_webhook : true ,
git_type : 'github'
);
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment queued.' ,
]);
2023-11-14 12:26:14 +00:00
} else {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Preview deployments disabled.' ,
]);
2023-11-14 12:26:14 +00:00
}
}
if ( $action === 'closed' ) {
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( $found ) {
$found -> delete ();
$container_name = generateApplicationContainerName ( $application , $pull_request_id );
// ray('Stopping container: ' . $container_name);
instant_remote_process ([ " docker rm -f $container_name " ], $application -> destination -> server );
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment closed.' ,
]);
} else {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'No preview deployment found.' ,
]);
2023-11-14 12:26:14 +00:00
}
}
}
}
2024-01-29 10:23:04 +00:00
ray ( $return_payloads );
return response ( $return_payloads );
2023-11-14 12:26:14 +00:00
} catch ( Exception $e ) {
ray ( $e -> getMessage ());
return handleError ( $e );
}
});
2023-05-09 12:42:10 +00:00
Route :: post ( '/source/github/events' , function () {
try {
2024-01-29 10:23:04 +00:00
$return_payloads = collect ([]);
2023-11-06 09:22:46 +00:00
$id = null ;
2023-05-09 12:42:10 +00:00
$x_github_delivery = request () -> header ( 'X-GitHub-Delivery' );
$x_github_event = Str :: lower ( request () -> header ( 'X-GitHub-Event' ));
$x_github_hook_installation_target_id = request () -> header ( 'X-GitHub-Hook-Installation-Target-Id' );
2023-06-13 13:37:55 +00:00
$x_hub_signature_256 = Str :: after ( request () -> header ( 'X-Hub-Signature-256' ), 'sha256=' );
2023-05-09 12:42:10 +00:00
$payload = request () -> collect ();
if ( $x_github_event === 'ping' ) {
// Just pong
return response ( 'pong' );
}
2023-11-22 15:40:49 +00:00
$github_app = GithubApp :: where ( 'app_id' , $x_github_hook_installation_target_id ) -> first ();
if ( is_null ( $github_app )) {
return response ( 'Nothing to do. No GitHub App found.' );
}
2023-06-13 13:37:55 +00:00
$webhook_secret = data_get ( $github_app , 'webhook_secret' );
$hmac = hash_hmac ( 'sha256' , request () -> getContent (), $webhook_secret );
if ( config ( 'app.env' ) !== 'local' ) {
if ( ! hash_equals ( $x_hub_signature_256 , $hmac )) {
2024-01-29 10:23:04 +00:00
return response ( 'Invalid signature.' );
2023-06-13 13:37:55 +00:00
}
}
2024-02-20 19:17:04 +00:00
if ( $x_github_event === 'installation' || $x_github_event === 'installation_repositories' ) {
// Installation handled by setup redirect url. Repositories queried on-demand.
$action = data_get ( $payload , 'action' );
if ( $action === 'new_permissions_accepted' ) {
GithubAppPermissionJob :: dispatch ( $github_app );
}
return response ( 'cool' );
}
2023-05-09 12:42:10 +00:00
if ( $x_github_event === 'push' ) {
$id = data_get ( $payload , 'repository.id' );
$branch = data_get ( $payload , 'ref' );
if ( Str :: isMatch ( '/refs\/heads\/*/' , $branch )) {
$branch = Str :: after ( $branch , 'refs/heads/' );
}
2023-05-31 09:24:02 +00:00
ray ( 'Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch );
2023-05-09 12:42:10 +00:00
}
if ( $x_github_event === 'pull_request' ) {
2023-05-31 09:24:02 +00:00
$action = data_get ( $payload , 'action' );
$id = data_get ( $payload , 'repository.id' );
$pull_request_id = data_get ( $payload , 'number' );
$pull_request_html_url = data_get ( $payload , 'pull_request.html_url' );
$branch = data_get ( $payload , 'pull_request.head.ref' );
$base_branch = data_get ( $payload , 'pull_request.base.ref' );
ray ( 'Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id );
2023-05-09 12:42:10 +00:00
}
if ( ! $id || ! $branch ) {
2023-05-31 09:24:02 +00:00
return response ( 'Nothing to do. No id or branch found.' );
}
2023-06-13 13:01:11 +00:00
$applications = Application :: where ( 'repository_project_id' , $id ) -> whereRelation ( 'source' , 'is_public' , false );
2023-05-31 09:24:02 +00:00
if ( $x_github_event === 'push' ) {
$applications = $applications -> where ( 'git_branch' , $branch ) -> get ();
2023-09-30 18:47:07 +00:00
if ( $applications -> isEmpty ()) {
return response ( " Nothing to do. No applications found with branch ' $branch '. " );
}
2023-05-31 09:24:02 +00:00
}
if ( $x_github_event === 'pull_request' ) {
$applications = $applications -> where ( 'git_branch' , $base_branch ) -> get ();
2023-09-30 18:47:07 +00:00
if ( $applications -> isEmpty ()) {
return response ( " Nothing to do. No applications found with branch ' $base_branch '. " );
}
2023-05-31 09:24:02 +00:00
}
2023-09-30 18:47:07 +00:00
2023-05-09 12:42:10 +00:00
foreach ( $applications as $application ) {
2023-09-12 11:10:39 +00:00
$isFunctional = $application -> destination -> server -> isFunctional ();
if ( ! $isFunctional ) {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Server is not functional.' ,
]);
2023-09-12 11:10:39 +00:00
continue ;
}
2023-05-31 09:24:02 +00:00
if ( $x_github_event === 'push' ) {
if ( $application -> isDeployable ()) {
ray ( 'Deploying ' . $application -> name . ' with branch ' . $branch );
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-05-31 09:24:02 +00:00
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
is_webhook : true
);
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Deployment queued.' ,
]);
2023-05-31 09:24:02 +00:00
} else {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Deployments disabled.' ,
]);
2023-05-31 09:24:02 +00:00
}
}
if ( $x_github_event === 'pull_request' ) {
2023-06-13 09:45:33 +00:00
if ( $action === 'opened' || $action === 'synchronize' || $action === 'reopened' ) {
2023-05-31 09:24:02 +00:00
if ( $application -> isPRDeployable ()) {
$deployment_uuid = new Cuid2 ( 7 );
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( ! $found ) {
ApplicationPreview :: create ([
2023-11-14 12:26:14 +00:00
'git_type' => 'github' ,
2023-05-31 09:24:02 +00:00
'application_id' => $application -> id ,
'pull_request_id' => $pull_request_id ,
2023-08-15 12:11:38 +00:00
'pull_request_html_url' => $pull_request_html_url ,
2023-05-31 09:24:02 +00:00
]);
}
queue_application_deployment (
2024-01-27 17:44:40 +00:00
application : $application ,
2023-05-31 09:24:02 +00:00
pull_request_id : $pull_request_id ,
deployment_uuid : $deployment_uuid ,
force_rebuild : false ,
2023-11-14 12:26:14 +00:00
is_webhook : true ,
git_type : 'github'
2023-05-31 09:24:02 +00:00
);
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment queued.' ,
]);
2023-05-31 09:24:02 +00:00
} else {
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'Preview deployments disabled.' ,
]);
2023-05-31 09:24:02 +00:00
}
}
2023-11-14 12:26:14 +00:00
if ( $action === 'closed' || $action === 'close' ) {
2023-05-31 09:24:02 +00:00
$found = ApplicationPreview :: where ( 'application_id' , $application -> id ) -> where ( 'pull_request_id' , $pull_request_id ) -> first ();
if ( $found ) {
2024-01-26 17:46:50 +00:00
ApplicationPullRequestUpdateJob :: dispatchSync ( application : $application , preview : $found , status : ProcessStatus :: CLOSED );
2023-05-31 09:24:02 +00:00
$found -> delete ();
2023-10-27 08:26:35 +00:00
$container_name = generateApplicationContainerName ( $application , $pull_request_id );
2023-10-01 10:02:44 +00:00
// ray('Stopping container: ' . $container_name);
instant_remote_process ([ " docker rm -f $container_name " ], $application -> destination -> server );
2024-01-29 10:23:04 +00:00
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'success' ,
'message' => 'Preview deployment closed.' ,
]);
} else {
$return_payloads -> push ([
'application' => $application -> name ,
'status' => 'failed' ,
'message' => 'No preview deployment found.' ,
]);
2023-05-31 09:24:02 +00:00
}
}
2023-05-10 09:43:49 +00:00
}
2023-05-09 12:42:10 +00:00
}
2024-01-29 10:23:04 +00:00
ray ( $return_payloads );
return response ( $return_payloads );
2023-08-08 09:51:36 +00:00
} catch ( Exception $e ) {
2023-09-14 13:52:04 +00:00
ray ( $e -> getMessage ());
2023-09-15 13:34:25 +00:00
return handleError ( $e );
2023-05-09 12:42:10 +00:00
}
});
2023-08-15 14:28:38 +00:00
Route :: get ( '/waitlist/confirm' , function () {
$email = request () -> get ( 'email' );
$confirmation_code = request () -> get ( 'confirmation_code' );
ray ( $email , $confirmation_code );
try {
$found = Waitlist :: where ( 'uuid' , $confirmation_code ) -> where ( 'email' , $email ) -> first ();
2023-09-11 10:16:26 +00:00
if ( $found ) {
if ( ! $found -> verified ) {
if ( $found -> created_at > now () -> subMinutes ( config ( 'constants.waitlist.expiration' ))) {
$found -> verified = true ;
$found -> save ();
send_internal_notification ( 'Waitlist confirmed: ' . $email );
return 'Thank you for confirming your email address. We will notify you when you are next in line.' ;
} else {
2023-09-11 10:31:31 +00:00
$found -> delete ();
send_internal_notification ( 'Waitlist expired: ' . $email );
2023-09-11 10:16:26 +00:00
return 'Your confirmation code has expired. Please sign up again.' ;
}
}
2023-08-15 12:11:38 +00:00
}
2023-08-15 14:28:38 +00:00
return redirect () -> route ( 'dashboard' );
2023-09-11 10:31:31 +00:00
} catch ( Exception $e ) {
send_internal_notification ( 'Waitlist confirmation failed: ' . $e -> getMessage ());
ray ( $e -> getMessage ());
2023-08-15 14:28:38 +00:00
return redirect () -> route ( 'dashboard' );
}
}) -> name ( 'webhooks.waitlist.confirm' );
Route :: get ( '/waitlist/cancel' , function () {
$email = request () -> get ( 'email' );
$confirmation_code = request () -> get ( 'confirmation_code' );
try {
$found = Waitlist :: where ( 'uuid' , $confirmation_code ) -> where ( 'email' , $email ) -> first ();
if ( $found && ! $found -> verified ) {
$found -> delete ();
2023-08-16 14:03:30 +00:00
send_internal_notification ( 'Waitlist cancelled: ' . $email );
2023-08-15 14:28:38 +00:00
return 'Your email address has been removed from the waitlist.' ;
2023-08-15 12:11:38 +00:00
}
2023-08-15 14:28:38 +00:00
return redirect () -> route ( 'dashboard' );
2023-09-11 10:31:31 +00:00
} catch ( Exception $e ) {
send_internal_notification ( 'Waitlist cancellation failed: ' . $e -> getMessage ());
ray ( $e -> getMessage ());
2023-08-15 14:28:38 +00:00
return redirect () -> route ( 'dashboard' );
}
}) -> name ( 'webhooks.waitlist.cancel' );
2023-08-24 14:14:09 +00:00
Route :: post ( '/payments/stripe/events' , function () {
try {
$webhookSecret = config ( 'subscription.stripe_webhook_secret' );
$signature = request () -> header ( 'Stripe-Signature' );
2023-11-10 14:36:02 +00:00
$excludedPlans = config ( 'subscription.stripe_excluded_plans' );
2023-08-24 14:14:09 +00:00
$event = \Stripe\Webhook :: constructEvent (
request () -> getContent (),
$signature ,
$webhookSecret
);
$webhook = Webhook :: create ([
'type' => 'stripe' ,
'payload' => request () -> getContent ()
]);
$type = data_get ( $event , 'type' );
$data = data_get ( $event , 'data.object' );
switch ( $type ) {
case 'checkout.session.completed' :
$clientReferenceId = data_get ( $data , 'client_reference_id' );
2023-11-10 14:36:02 +00:00
if ( is_null ( $clientReferenceId )) {
send_internal_notification ( 'Checkout session completed without client reference id.' );
break ;
}
2023-08-24 14:14:09 +00:00
$userId = Str :: before ( $clientReferenceId , ':' );
$teamId = Str :: after ( $clientReferenceId , ':' );
$subscriptionId = data_get ( $data , 'subscription' );
$customerId = data_get ( $data , 'customer' );
$team = Team :: find ( $teamId );
$found = $team -> members -> where ( 'id' , $userId ) -> first ();
if ( ! $found -> isAdmin ()) {
2024-02-06 10:11:26 +00:00
send_internal_notification ( " User { $userId } is not an admin or owner of team { $team -> id } , customerid: { $customerId } , subscriptionid: { $subscriptionId } . " );
throw new Exception ( " User { $userId } is not an admin or owner of team { $team -> id } , customerid: { $customerId } , subscriptionid: { $subscriptionId } . " );
2023-08-24 14:14:09 +00:00
}
$subscription = Subscription :: where ( 'team_id' , $teamId ) -> first ();
if ( $subscription ) {
2023-09-06 10:07:34 +00:00
send_internal_notification ( 'Old subscription activated for team: ' . $teamId );
2023-08-24 14:14:09 +00:00
$subscription -> update ([
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
'stripe_invoice_paid' => true ,
]);
} else {
2023-09-06 10:07:34 +00:00
send_internal_notification ( 'New subscription for team: ' . $teamId );
2023-08-24 14:14:09 +00:00
Subscription :: create ([
'team_id' => $teamId ,
'stripe_subscription_id' => $subscriptionId ,
'stripe_customer_id' => $customerId ,
'stripe_invoice_paid' => true ,
]);
}
break ;
case 'invoice.paid' :
2023-09-06 10:07:34 +00:00
$customerId = data_get ( $data , 'customer' );
2023-11-10 14:41:44 +00:00
$planId = data_get ( $data , 'lines.data.0.plan.id' );
if ( Str :: contains ( $excludedPlans , $planId )) {
send_internal_notification ( 'Subscription excluded.' );
break ;
}
2023-11-03 13:51:29 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
if ( ! $subscription ) {
2023-11-06 12:30:37 +00:00
Sleep :: for ( 5 ) -> seconds ();
2023-11-03 13:51:29 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> firstOrFail ();
}
2023-10-27 08:30:15 +00:00
$subscription -> update ([
'stripe_invoice_paid' => true ,
]);
2023-08-24 14:14:09 +00:00
break ;
2024-01-24 10:28:01 +00:00
case 'invoice.payment_failed' :
$customerId = data_get ( $data , 'customer' );
2024-02-06 10:11:26 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
if ( ! $subscription ) {
send_internal_notification ( 'invoice.payment_failed failed but no subscription found in Coolify for customer: ' . $customerId );
return response ( 'No subscription found in Coolify.' );
}
2024-01-24 10:28:01 +00:00
$team = data_get ( $subscription , 'team' );
if ( ! $team ) {
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'invoice.payment_failed failed but no team found in Coolify for customer: ' . $customerId );
return response ( 'No team found in Coolify.' );
2024-01-24 10:28:01 +00:00
}
2024-02-04 11:23:00 +00:00
if ( ! $subscription -> stripe_invoice_paid ) {
SubscriptionInvoiceFailedJob :: dispatch ( $team );
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'Invoice payment failed: ' . $customerId );
2024-02-04 11:23:00 +00:00
} else {
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'Invoice payment failed but already paid: ' . $customerId );
2024-02-04 11:23:00 +00:00
}
2024-01-24 10:28:01 +00:00
break ;
2023-10-27 08:26:35 +00:00
case 'payment_intent.payment_failed' :
$customerId = data_get ( $data , 'customer' );
2024-02-06 10:11:26 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
if ( ! $subscription ) {
send_internal_notification ( 'payment_intent.payment_failed, no subscription found in Coolify for customer: ' . $customerId );
return response ( 'No subscription found in Coolify.' );
}
if ( $subscription -> stripe_invoice_paid ) {
send_internal_notification ( 'payment_intent.payment_failed but invoice is active for customer: ' . $customerId );
return ;
}
send_internal_notification ( 'Subscription payment failed for customer: ' . $customerId );
2023-10-27 08:26:35 +00:00
break ;
2023-08-24 14:14:09 +00:00
case 'customer.subscription.updated' :
2023-09-06 10:07:34 +00:00
$customerId = data_get ( $data , 'customer' );
2023-09-12 09:19:21 +00:00
$status = data_get ( $data , 'status' );
2023-08-24 14:14:09 +00:00
$subscriptionId = data_get ( $data , 'items.data.0.subscription' );
2023-08-30 16:23:55 +00:00
$planId = data_get ( $data , 'items.data.0.plan.id' );
2023-11-10 14:36:02 +00:00
if ( Str :: contains ( $excludedPlans , $planId )) {
2023-11-10 14:41:44 +00:00
send_internal_notification ( 'Subscription excluded.' );
2023-11-10 14:36:02 +00:00
break ;
}
2024-01-24 10:28:01 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
if ( ! $subscription ) {
Sleep :: for ( 5 ) -> seconds ();
2024-02-12 10:48:28 +00:00
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> first ();
}
if ( ! $subscription ) {
send_internal_notification ( 'No subscription found for: ' . $customerId );
return response ( " No subscription found " , 400 );
2024-01-24 10:28:01 +00:00
}
2023-11-10 14:41:44 +00:00
$trialEndedAlready = data_get ( $subscription , 'stripe_trial_already_ended' );
2023-08-24 14:14:09 +00:00
$cancelAtPeriodEnd = data_get ( $data , 'cancel_at_period_end' );
2023-09-06 10:07:34 +00:00
$alreadyCancelAtPeriodEnd = data_get ( $subscription , 'stripe_cancel_at_period_end' );
$feedback = data_get ( $data , 'cancellation_details.feedback' );
$comment = data_get ( $data , 'cancellation_details.comment' );
2024-02-25 13:00:35 +00:00
$lookup_key = data_get ( $data , 'items.data.0.price.lookup_key' );
if ( str ( $lookup_key ) -> contains ( 'ultimate' )) {
$quantity = data_get ( $data , 'items.data.0.quantity' , 10 );
$team = data_get ( $subscription , 'team' );
$team -> update ([
'custom_server_limit' => $quantity ,
]);
2024-02-26 09:25:21 +00:00
ServerLimitCheckJob :: dispatch ( $team );
2024-02-25 13:00:35 +00:00
}
2023-08-24 14:14:09 +00:00
$subscription -> update ([
2023-09-06 10:07:34 +00:00
'stripe_feedback' => $feedback ,
'stripe_comment' => $comment ,
2023-08-30 16:23:55 +00:00
'stripe_plan_id' => $planId ,
2023-08-24 14:14:09 +00:00
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd ,
]);
2023-10-27 08:17:13 +00:00
if ( $status === 'paused' || $status === 'incomplete_expired' ) {
2023-09-12 09:19:21 +00:00
$subscription -> update ([
'stripe_invoice_paid' => false ,
]);
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'Subscription paused or incomplete for customer: ' . $customerId );
2023-09-12 09:19:21 +00:00
}
// Trial ended but subscribed, reactive servers
if ( $trialEndedAlready && $status === 'active' ) {
$team = data_get ( $subscription , 'team' );
$team -> trialEndedButSubscribed ();
}
2023-09-06 10:07:34 +00:00
if ( $feedback ) {
2024-02-06 10:11:26 +00:00
$reason = " Cancellation feedback for { $customerId } : ' " . $feedback . " ' " ;
2023-09-06 10:07:34 +00:00
if ( $comment ) {
2023-09-11 10:16:26 +00:00
$reason .= ' with comment: \'' . $comment . " ' " ;
2023-09-06 10:07:34 +00:00
}
send_internal_notification ( $reason );
}
if ( $alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd ) {
if ( $cancelAtPeriodEnd ) {
2023-10-01 16:14:24 +00:00
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
2023-09-06 10:07:34 +00:00
} else {
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'customer.subscription.updated for customer: ' . $customerId );
2023-09-06 10:07:34 +00:00
}
}
2023-08-24 14:14:09 +00:00
break ;
case 'customer.subscription.deleted' :
2023-09-12 09:19:21 +00:00
// End subscription
2023-09-06 10:07:34 +00:00
$customerId = data_get ( $data , 'customer' );
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> firstOrFail ();
2023-09-12 09:19:21 +00:00
$team = data_get ( $subscription , 'team' );
$team -> trialEnded ();
2023-08-24 14:14:09 +00:00
$subscription -> update ([
2023-08-30 16:23:55 +00:00
'stripe_subscription_id' => null ,
2023-09-11 10:16:26 +00:00
'stripe_plan_id' => null ,
2023-08-24 14:14:09 +00:00
'stripe_cancel_at_period_end' => false ,
'stripe_invoice_paid' => false ,
2023-09-12 09:19:21 +00:00
'stripe_trial_already_ended' => true ,
2023-08-24 14:14:09 +00:00
]);
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'customer.subscription.deleted for customer: ' . $customerId );
2023-09-12 09:19:21 +00:00
break ;
case 'customer.subscription.trial_will_end' :
2024-02-06 10:11:26 +00:00
// Not used for now
2023-09-12 09:19:21 +00:00
$customerId = data_get ( $data , 'customer' );
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> firstOrFail ();
$team = data_get ( $subscription , 'team' );
if ( ! $team ) {
throw new Exception ( 'No team found for subscription: ' . $subscription -> id );
}
SubscriptionTrialEndsSoonJob :: dispatch ( $team );
break ;
case 'customer.subscription.paused' :
$customerId = data_get ( $data , 'customer' );
$subscription = Subscription :: where ( 'stripe_customer_id' , $customerId ) -> firstOrFail ();
$team = data_get ( $subscription , 'team' );
if ( ! $team ) {
throw new Exception ( 'No team found for subscription: ' . $subscription -> id );
}
$team -> trialEnded ();
$subscription -> update ([
'stripe_trial_already_ended' => true ,
'stripe_invoice_paid' => false ,
]);
SubscriptionTrialEndedJob :: dispatch ( $team );
2024-02-06 10:11:26 +00:00
send_internal_notification ( 'Subscription paused for customer: ' . $customerId );
2023-08-24 14:14:09 +00:00
break ;
default :
// Unhandled event type
}
} catch ( Exception $e ) {
2023-12-11 07:32:42 +00:00
if ( $type !== 'payment_intent.payment_failed' ) {
send_internal_notification ( " Subscription webhook ( $type ) failed: " . $e -> getMessage ());
}
2023-08-24 14:14:09 +00:00
$webhook -> update ([
'status' => 'failed' ,
'failure_reason' => $e -> getMessage (),
]);
return response ( $e -> getMessage (), 400 );
}
});
2024-01-24 10:28:01 +00:00
// Route::post('/payments/paddle/events', function () {
// try {
// $payload = request()->all();
// $signature = request()->header('Paddle-Signature');
// $ts = Str::of($signature)->after('ts=')->before(';');
// $h1 = Str::of($signature)->after('h1=');
// $signedPayload = $ts->value . ':' . request()->getContent();
// $verify = hash_hmac('sha256', $signedPayload, config('subscription.paddle_webhook_secret'));
// if (!hash_equals($verify, $h1->value)) {
// return response('Invalid signature.', 400);
// }
// $eventType = data_get($payload, 'event_type');
// $webhook = Webhook::create([
// 'type' => 'paddle',
// 'payload' => $payload,
// ]);
// // TODO - Handle events
// switch ($eventType) {
// case 'subscription.activated':
// }
// ray('Subscription event: ' . $eventType);
// $webhook->update([
// 'status' => 'success',
// ]);
// } catch (Exception $e) {
// ray($e->getMessage());
// send_internal_notification('Subscription webhook failed: ' . $e->getMessage());
// $webhook->update([
// 'status' => 'failed',
// 'failure_reason' => $e->getMessage(),
// ]);
// } finally {
// return response('OK');
// }
// });
// Route::post('/payments/lemon/events', function () {
// try {
// $secret = config('subscription.lemon_squeezy_webhook_secret');
// $payload = request()->collect();
// $hash = hash_hmac('sha256', $payload, $secret);
// $signature = request()->header('X-Signature');
2023-07-13 13:07:42 +00:00
2024-01-24 10:28:01 +00:00
// if (!hash_equals($hash, $signature)) {
// return response('Invalid signature.', 400);
// }
2023-07-13 13:07:42 +00:00
2024-01-24 10:28:01 +00:00
// $webhook = Webhook::create([
// 'type' => 'lemonsqueezy',
// 'payload' => $payload,
// ]);
// $event = data_get($payload, 'meta.event_name');
// ray('Subscription event: ' . $event);
// $email = data_get($payload, 'data.attributes.user_email');
// $team_id = data_get($payload, 'meta.custom_data.team_id');
// if (is_null($team_id) || empty($team_id)) {
// throw new Exception('No team_id found in webhook payload.');
// }
// $subscription_id = data_get($payload, 'data.id');
// $order_id = data_get($payload, 'data.attributes.order_id');
// $product_id = data_get($payload, 'data.attributes.product_id');
// $variant_id = data_get($payload, 'data.attributes.variant_id');
// $variant_name = data_get($payload, 'data.attributes.variant_name');
// $customer_id = data_get($payload, 'data.attributes.customer_id');
// $status = data_get($payload, 'data.attributes.status');
// $trial_ends_at = data_get($payload, 'data.attributes.trial_ends_at');
// $renews_at = data_get($payload, 'data.attributes.renews_at');
// $ends_at = data_get($payload, 'data.attributes.ends_at');
// $update_payment_method = data_get($payload, 'data.attributes.urls.update_payment_method');
// $team = Team::find($team_id);
// $found = $team->members->where('email', $email)->first();
// if (!$found->isAdmin()) {
// throw new Exception("User {$email} is not an admin or owner of team {$team->id}.");
// }
// switch ($event) {
// case 'subscription_created':
// case 'subscription_updated':
// case 'subscription_resumed':
// case 'subscription_unpaused':
// send_internal_notification("LemonSqueezy Event (`$event`): `" . $email . '` with status `' . $status . '`, tier: `' . $variant_name . '`');
// $subscription = Subscription::updateOrCreate([
// 'team_id' => $team_id,
// ], [
// 'lemon_subscription_id' => $subscription_id,
// 'lemon_customer_id' => $customer_id,
// 'lemon_order_id' => $order_id,
// 'lemon_product_id' => $product_id,
// 'lemon_variant_id' => $variant_id,
// 'lemon_status' => $status,
// 'lemon_variant_name' => $variant_name,
// 'lemon_trial_ends_at' => $trial_ends_at,
// 'lemon_renews_at' => $renews_at,
// 'lemon_ends_at' => $ends_at,
// 'lemon_update_payment_menthod_url' => $update_payment_method,
// ]);
// break;
// case 'subscription_cancelled':
// case 'subscription_paused':
// case 'subscription_expired':
// $subscription = Subscription::where('team_id', $team_id)->where('lemon_order_id', $order_id)->first();
// if ($subscription) {
// send_internal_notification("LemonSqueezy Event (`$event`): " . $subscription_id . ' for team ' . $team_id . ' with status ' . $status);
// $subscription->update([
// 'lemon_status' => $status,
// 'lemon_trial_ends_at' => $trial_ends_at,
// 'lemon_renews_at' => $renews_at,
// 'lemon_ends_at' => $ends_at,
// 'lemon_update_payment_menthod_url' => $update_payment_method,
// ]);
// }
// break;
// }
2023-08-16 14:03:30 +00:00
2024-01-24 10:28:01 +00:00
// $webhook->update([
// 'status' => 'success',
// ]);
// } catch (Exception $e) {
// ray($e->getMessage());
// send_internal_notification('Subscription webhook failed: ' . $e->getMessage());
// $webhook->update([
// 'status' => 'failed',
// 'failure_reason' => $e->getMessage(),
// ]);
// } finally {
// return response('OK');
// }
// });