Merge remote-tracking branch 'origin/next' into api-sensitive-data-scrubber

This commit is contained in:
Andras Bacsai
2026-06-15 12:30:15 +02:00
187 changed files with 4319 additions and 952 deletions
+5 -4
View File
@@ -5,6 +5,7 @@ namespace App\Actions\Stripe;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Support\Collection;
use Stripe\Exception\InvalidRequestException;
use Stripe\StripeClient;
class CancelSubscription
@@ -21,7 +22,7 @@ class CancelSubscription
$this->isDryRun = $isDryRun;
if (! $isDryRun && isCloud()) {
$this->stripe = new StripeClient(config('subscription.stripe_api_key'));
$this->stripe = app(StripeClient::class);
}
}
@@ -64,7 +65,7 @@ class CancelSubscription
];
}
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$subscriptions = $this->getSubscriptionsPreview();
$verified = collect();
@@ -88,7 +89,7 @@ class CancelSubscription
'reason' => "Status in Stripe: {$stripeSubscription->status}",
]);
}
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
// Subscription doesn't exist in Stripe
$notFound->push([
'subscription' => $subscription,
@@ -181,7 +182,7 @@ class CancelSubscription
return false;
}
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$stripe->subscriptions->cancel($subscriptionId, []);
// Update local record if exists
@@ -3,6 +3,7 @@
namespace App\Actions\Stripe;
use App\Models\Team;
use Stripe\Exception\InvalidRequestException;
use Stripe\StripeClient;
class CancelSubscriptionAtPeriodEnd
@@ -11,7 +12,7 @@ class CancelSubscriptionAtPeriodEnd
public function __construct(?StripeClient $stripe = null)
{
$this->stripe = $stripe ?? new StripeClient(config('subscription.stripe_api_key'));
$this->stripe = $stripe ?? app(StripeClient::class);
}
/**
@@ -47,7 +48,7 @@ class CancelSubscriptionAtPeriodEnd
\Log::info("Subscription {$subscription->stripe_subscription_id} set to cancel at period end for team {$team->name}");
return ['success' => true, 'error' => null];
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
\Log::error("Stripe cancel at period end error for team {$team->id}: ".$e->getMessage());
return ['success' => false, 'error' => 'Stripe error: '.$e->getMessage()];
+6 -4
View File
@@ -3,6 +3,8 @@
namespace App\Actions\Stripe;
use App\Models\Team;
use Carbon\Carbon;
use Stripe\Exception\InvalidRequestException;
use Stripe\StripeClient;
class RefundSubscription
@@ -13,7 +15,7 @@ class RefundSubscription
public function __construct(?StripeClient $stripe = null)
{
$this->stripe = $stripe ?? new StripeClient(config('subscription.stripe_api_key'));
$this->stripe = $stripe ?? app(StripeClient::class);
}
/**
@@ -39,7 +41,7 @@ class RefundSubscription
try {
$stripeSubscription = $this->stripe->subscriptions->retrieve($subscription->stripe_subscription_id);
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
return $this->ineligible('Subscription not found in Stripe.');
}
@@ -49,7 +51,7 @@ class RefundSubscription
return $this->ineligible("Subscription status is '{$stripeSubscription->status}'.", $currentPeriodEnd);
}
$startDate = \Carbon\Carbon::createFromTimestamp($stripeSubscription->start_date);
$startDate = Carbon::createFromTimestamp($stripeSubscription->start_date);
$daysSinceStart = (int) $startDate->diffInDays(now());
$daysRemaining = self::REFUND_WINDOW_DAYS - $daysSinceStart;
@@ -130,7 +132,7 @@ class RefundSubscription
\Log::info("Refunded and cancelled subscription {$subscription->stripe_subscription_id} for team {$team->name}");
return ['success' => true, 'error' => null];
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
\Log::error("Stripe refund error for team {$team->id}: ".$e->getMessage());
return ['success' => false, 'error' => 'Stripe error: '.$e->getMessage()];
+3 -2
View File
@@ -3,6 +3,7 @@
namespace App\Actions\Stripe;
use App\Models\Team;
use Stripe\Exception\InvalidRequestException;
use Stripe\StripeClient;
class ResumeSubscription
@@ -11,7 +12,7 @@ class ResumeSubscription
public function __construct(?StripeClient $stripe = null)
{
$this->stripe = $stripe ?? new StripeClient(config('subscription.stripe_api_key'));
$this->stripe = $stripe ?? app(StripeClient::class);
}
/**
@@ -43,7 +44,7 @@ class ResumeSubscription
\Log::info("Subscription {$subscription->stripe_subscription_id} resumed for team {$team->name}");
return ['success' => true, 'error' => null];
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
\Log::error("Stripe resume subscription error for team {$team->id}: ".$e->getMessage());
return ['success' => false, 'error' => 'Stripe error: '.$e->getMessage()];
@@ -17,7 +17,7 @@ class UpdateSubscriptionQuantity
public function __construct(?StripeClient $stripe = null)
{
$this->stripe = $stripe ?? new StripeClient(config('subscription.stripe_api_key'));
$this->stripe = $stripe ?? app(StripeClient::class);
}
/**
@@ -4,6 +4,8 @@ namespace App\Console\Commands\Cloud;
use App\Models\Team;
use Illuminate\Console\Command;
use Stripe\Exception\InvalidRequestException;
use Stripe\StripeClient;
class CloudFixSubscription extends Command
{
@@ -31,7 +33,7 @@ class CloudFixSubscription extends Command
*/
public function handle()
{
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
if ($this->option('verify-all')) {
return $this->verifyAllActiveSubscriptions($stripe);
@@ -111,7 +113,7 @@ class CloudFixSubscription extends Command
/**
* Fix canceled subscriptions in the database
*/
private function fixCanceledSubscriptions(\Stripe\StripeClient $stripe)
private function fixCanceledSubscriptions(StripeClient $stripe)
{
$isDryRun = $this->option('dry-run');
$checkOne = $this->option('one');
@@ -220,7 +222,7 @@ class CloudFixSubscription extends Command
break;
}
}
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
if ($e->getStripeCode() === 'resource_missing') {
$toFixCount++;
@@ -326,7 +328,7 @@ class CloudFixSubscription extends Command
/**
* Verify all active subscriptions against Stripe API
*/
private function verifyAllActiveSubscriptions(\Stripe\StripeClient $stripe)
private function verifyAllActiveSubscriptions(StripeClient $stripe)
{
$isDryRun = $this->option('dry-run');
$shouldFix = $this->option('fix-verified');
@@ -570,7 +572,7 @@ class CloudFixSubscription extends Command
break;
}
} catch (\Stripe\Exception\InvalidRequestException $e) {
} catch (InvalidRequestException $e) {
$this->error(' → Error: '.$e->getMessage());
if ($e->getStripeCode() === 'resource_missing' || $e->getHttpStatus() === 404) {
@@ -730,7 +732,7 @@ class CloudFixSubscription extends Command
/**
* Search for subscriptions by customer ID
*/
private function searchSubscriptionsByCustomer(\Stripe\StripeClient $stripe, $customerId, $requireActive = false)
private function searchSubscriptionsByCustomer(StripeClient $stripe, $customerId, $requireActive = false)
{
try {
$subscriptions = $stripe->subscriptions->all([
@@ -770,7 +772,7 @@ class CloudFixSubscription extends Command
/**
* Search for subscriptions by team member emails
*/
private function searchSubscriptionsByEmails(\Stripe\StripeClient $stripe, $emails)
private function searchSubscriptionsByEmails(StripeClient $stripe, $emails)
{
$this->line(' → Searching by team member emails...');
+17 -7
View File
@@ -14,6 +14,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;
use Visus\Cuid2\Cuid2;
class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
@@ -71,16 +72,25 @@ class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
->first();
if ($found) {
ApplicationPullRequestUpdateJob::dispatchSync(
application: $application,
preview: $found,
status: ProcessStatus::CLOSED
);
CleanupPreviewDeployment::run($application, $this->pullRequestId, $found);
try {
$this->dispatchPullRequestClosedUpdate($application, $found);
} catch (Throwable $e) {
report($e);
} finally {
CleanupPreviewDeployment::run($application, $this->pullRequestId, $found);
}
}
}
protected function dispatchPullRequestClosedUpdate(Application $application, ApplicationPreview $preview): void
{
ApplicationPullRequestUpdateJob::dispatchSync(
application: $application,
preview: $preview,
status: ProcessStatus::CLOSED
);
}
private function handleOpenAction(Application $application, ?GithubApp $githubApp): void
{
if (! $application->isPRDeployable()) {
+3 -3
View File
@@ -36,7 +36,7 @@ class StripeProcessJob implements ShouldBeEncrypted, ShouldQueue
$data = data_get($this->event, 'data.object');
switch ($type) {
case 'radar.early_fraud_warning.created':
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$id = data_get($data, 'id');
$charge = data_get($data, 'charge');
if ($charge) {
@@ -100,7 +100,7 @@ class StripeProcessJob implements ShouldBeEncrypted, ShouldQueue
if ($subscription->stripe_subscription_id) {
try {
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$stripeSubscription = $stripe->subscriptions->retrieve(
$subscription->stripe_subscription_id
);
@@ -166,7 +166,7 @@ class StripeProcessJob implements ShouldBeEncrypted, ShouldQueue
// Verify payment status with Stripe API before sending failure notification
if ($paymentIntentId) {
try {
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$paymentIntent = $stripe->paymentIntents->retrieve($paymentIntentId);
if (in_array($paymentIntent->status, ['processing', 'succeeded', 'requires_action', 'requires_confirmation'])) {
+2 -1
View File
@@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stripe\StripeClient;
class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -27,7 +28,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
$subscription = $this->team->subscription;
if ($subscription && $subscription->stripe_customer_id) {
try {
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
if ($subscription->stripe_subscription_id) {
$stripeSubscription = $stripe->subscriptions->retrieve($subscription->stripe_subscription_id);
+4 -3
View File
@@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stripe\StripeClient;
class SyncStripeSubscriptionsJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -33,7 +34,7 @@ class SyncStripeSubscriptionsJob implements ShouldBeEncrypted, ShouldQueue
->where('stripe_invoice_paid', true)
->get();
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
// Bulk fetch all valid subscription IDs from Stripe (active + past_due)
$validStripeIds = $this->fetchValidStripeSubscriptionIds($stripe, $onProgress);
@@ -123,7 +124,7 @@ class SyncStripeSubscriptionsJob implements ShouldBeEncrypted, ShouldQueue
*
* @return array{email: string, customer_id: string, subscription_id: string, status: string}|null
*/
private function findActiveSubscriptionByEmail(\Stripe\StripeClient $stripe, string $customerId): ?array
private function findActiveSubscriptionByEmail(StripeClient $stripe, string $customerId): ?array
{
try {
$customer = $stripe->customers->retrieve($customerId);
@@ -177,7 +178,7 @@ class SyncStripeSubscriptionsJob implements ShouldBeEncrypted, ShouldQueue
*
* @return array<string>
*/
private function fetchValidStripeSubscriptionIds(\Stripe\StripeClient $stripe, ?\Closure $onProgress = null): array
private function fetchValidStripeSubscriptionIds(StripeClient $stripe, ?\Closure $onProgress = null): array
{
$validIds = [];
$fetched = 0;
@@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stripe\StripeClient;
class VerifyStripeSubscriptionStatusJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -29,7 +30,7 @@ class VerifyStripeSubscriptionStatusJob implements ShouldBeEncrypted, ShouldQueu
if (! $this->subscription->stripe_subscription_id &&
$this->subscription->stripe_customer_id) {
try {
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$subscriptions = $stripe->subscriptions->all([
'customer' => $this->subscription->stripe_customer_id,
'limit' => 1,
@@ -50,7 +51,7 @@ class VerifyStripeSubscriptionStatusJob implements ShouldBeEncrypted, ShouldQueu
}
try {
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$stripeSubscription = $stripe->subscriptions->retrieve(
$this->subscription->stripe_subscription_id
);
+21 -3
View File
@@ -32,6 +32,8 @@ class Actions extends Component
public bool $refundAlreadyUsed = false;
public bool $refundLatestPayment = false;
public string $billingInterval = 'monthly';
public ?string $nextBillingDate = null;
@@ -100,7 +102,7 @@ class Actions extends Component
return 'Invalid password.';
}
$result = (new RefundSubscription)->execute(currentTeam());
$result = app(RefundSubscription::class)->execute(currentTeam());
if ($result['success']) {
$this->dispatch('success', 'Subscription refunded successfully.');
@@ -114,12 +116,28 @@ class Actions extends Component
return true;
}
public function cancelImmediately(string $password): bool|string
public function cancelImmediately(string $password, array $selectedActions = []): bool|string
{
if (! shouldSkipPasswordConfirmation() && ! Hash::check($password, auth()->user()->password)) {
return 'Invalid password.';
}
if (in_array('refundLatestPayment', $selectedActions, true)) {
// Eligibility is re-validated server-side inside RefundSubscription::execute()
$result = app(RefundSubscription::class)->execute(currentTeam());
if ($result['success']) {
$this->dispatch('success', 'Subscription refunded and cancelled successfully.');
$this->redirect(route('subscription.index'), navigate: true);
return true;
}
$this->dispatch('error', 'Something went wrong with the refund. Please <a href="'.config('constants.urls.contact').'" target="_blank" class="underline">contact us</a>.');
return true;
}
$team = currentTeam();
$subscription = $team->subscription;
@@ -130,7 +148,7 @@ class Actions extends Component
}
try {
$stripe = new StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$stripe->subscriptions->cancel($subscription->stripe_subscription_id);
$subscription->update([
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Livewire\Subscription;
use App\Models\InstanceSettings;
use App\Providers\RouteServiceProvider;
use Livewire\Component;
use Stripe\StripeClient;
class Index extends Component
{
@@ -52,7 +53,7 @@ class Index extends Component
{
try {
$subscription = currentTeam()->subscription;
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$stripe = app(StripeClient::class);
$customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id);
if ($customer) {
$subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]);
+10 -1
View File
@@ -637,7 +637,7 @@ class Service extends BaseModel
}
$fields->put('Unleash', $data->toArray());
break;
case $image->contains('grafana'):
case $this->isGrafanaImage($image->toString()):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
$data = $data->merge([
@@ -1391,6 +1391,15 @@ class Service extends BaseModel
return $fields;
}
private function isGrafanaImage(string $image): bool
{
return in_array($image, [
'grafana/grafana',
'grafana/grafana-oss',
'grafana/grafana-enterprise',
], true);
}
public function saveExtraFields($fields)
{
foreach ($fields as $field) {
+3
View File
@@ -11,6 +11,7 @@ use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Laravel\Sanctum\Sanctum;
use Laravel\Telescope\TelescopeServiceProvider;
use Stripe\StripeClient;
class AppServiceProvider extends ServiceProvider
{
@@ -19,6 +20,8 @@ class AppServiceProvider extends ServiceProvider
if (App::isLocal()) {
$this->app->register(TelescopeServiceProvider::class);
}
$this->app->bind(StripeClient::class, fn () => new StripeClient(config('subscription.stripe_api_key')));
}
public function boot(): void