Skip to content

Commit 06f60c9

Browse files
authored
v4.0.0-beta.469 (#9007)
2 parents 89aecc2 + 8be2267 commit 06f60c9

File tree

95 files changed

+5223
-1197
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+5223
-1197
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Thank you so much!
5959

6060
* [MVPS](https://www.mvps.net?ref=coolify.io) - Cheap VPS servers at the highest possible quality
6161
* [SerpAPI](https://serpapi.com?ref=coolify.io) - Google Search API — Scrape Google and other search engines from our fast, easy, and complete API
62+
* [ScreenshotOne](https://screenshotone.com?ref=coolify.io) - Screenshot API for devs
6263
*
6364

6465
### Big Sponsors
@@ -77,6 +78,7 @@ Thank you so much!
7778
* [Convex](https://convex.link/coolify.io) - Open-source reactive database for web app developers
7879
* [CubePath](https://cubepath.com/?ref=coolify.io) - Dedicated Servers & Instant Deploy
7980
* [Darweb](https://darweb.nl/?ref=coolify.io) - 3D CPQ solutions for ecommerce design
81+
* [Dataforest Cloud](https://cloud.dataforest.net/en?ref=coolify.io) - Deploy cloud servers as seeds independently in seconds. Enterprise hardware, premium network, 100% made in Germany.
8082
* [Formbricks](https://formbricks.com?ref=coolify.io) - The open source feedback platform
8183
* [GoldenVM](https://billing.goldenvm.com?ref=coolify.io) - Premium virtual machine hosting solutions
8284
* [Greptile](https://www.greptile.com?ref=coolify.io) - The AI Code Reviewer

app/Actions/Stripe/RefundSubscription.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function __construct(?StripeClient $stripe = null)
1919
/**
2020
* Check if the team's subscription is eligible for a refund.
2121
*
22-
* @return array{eligible: bool, days_remaining: int, reason: string}
22+
* @return array{eligible: bool, days_remaining: int, reason: string, current_period_end: int|null}
2323
*/
2424
public function checkEligibility(Team $team): array
2525
{
@@ -43,22 +43,25 @@ public function checkEligibility(Team $team): array
4343
return $this->ineligible('Subscription not found in Stripe.');
4444
}
4545

46+
$currentPeriodEnd = $stripeSubscription->current_period_end;
47+
4648
if (! in_array($stripeSubscription->status, ['active', 'trialing'])) {
47-
return $this->ineligible("Subscription status is '{$stripeSubscription->status}'.");
49+
return $this->ineligible("Subscription status is '{$stripeSubscription->status}'.", $currentPeriodEnd);
4850
}
4951

5052
$startDate = \Carbon\Carbon::createFromTimestamp($stripeSubscription->start_date);
5153
$daysSinceStart = (int) $startDate->diffInDays(now());
5254
$daysRemaining = self::REFUND_WINDOW_DAYS - $daysSinceStart;
5355

5456
if ($daysRemaining <= 0) {
55-
return $this->ineligible('The 30-day refund window has expired.');
57+
return $this->ineligible('The 30-day refund window has expired.', $currentPeriodEnd);
5658
}
5759

5860
return [
5961
'eligible' => true,
6062
'days_remaining' => $daysRemaining,
6163
'reason' => 'Eligible for refund.',
64+
'current_period_end' => $currentPeriodEnd,
6265
];
6366
}
6467

@@ -99,16 +102,27 @@ public function execute(Team $team): array
99102
'payment_intent' => $paymentIntentId,
100103
]);
101104

102-
$this->stripe->subscriptions->cancel($subscription->stripe_subscription_id);
105+
// Record refund immediately so it cannot be retried if cancel fails
106+
$subscription->update([
107+
'stripe_refunded_at' => now(),
108+
'stripe_feedback' => 'Refund requested by user',
109+
'stripe_comment' => 'Full refund processed within 30-day window at '.now()->toDateTimeString(),
110+
]);
111+
112+
try {
113+
$this->stripe->subscriptions->cancel($subscription->stripe_subscription_id);
114+
} catch (\Exception $e) {
115+
\Log::critical("Refund succeeded but subscription cancel failed for team {$team->id}: ".$e->getMessage());
116+
send_internal_notification(
117+
"CRITICAL: Refund succeeded but cancel failed for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Manual intervention required."
118+
);
119+
}
103120

104121
$subscription->update([
105122
'stripe_cancel_at_period_end' => false,
106123
'stripe_invoice_paid' => false,
107124
'stripe_trial_already_ended' => false,
108125
'stripe_past_due' => false,
109-
'stripe_feedback' => 'Refund requested by user',
110-
'stripe_comment' => 'Full refund processed within 30-day window at '.now()->toDateTimeString(),
111-
'stripe_refunded_at' => now(),
112126
]);
113127

114128
$team->subscriptionEnded();
@@ -128,14 +142,15 @@ public function execute(Team $team): array
128142
}
129143

130144
/**
131-
* @return array{eligible: bool, days_remaining: int, reason: string}
145+
* @return array{eligible: bool, days_remaining: int, reason: string, current_period_end: int|null}
132146
*/
133-
private function ineligible(string $reason): array
147+
private function ineligible(string $reason, ?int $currentPeriodEnd = null): array
134148
{
135149
return [
136150
'eligible' => false,
137151
'days_remaining' => 0,
138152
'reason' => $reason,
153+
'current_period_end' => $currentPeriodEnd,
139154
];
140155
}
141156
}

app/Actions/Stripe/UpdateSubscriptionQuantity.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,19 @@ public function execute(Team $team, int $quantity): array
153153
\Log::warning("Subscription {$subscription->stripe_subscription_id} quantity updated but invoice not paid (status: {$latestInvoice->status}) for team {$team->name}. Reverting to {$previousQuantity}.");
154154

155155
// Revert subscription quantity on Stripe
156-
$this->stripe->subscriptions->update($subscription->stripe_subscription_id, [
157-
'items' => [
158-
['id' => $item->id, 'quantity' => $previousQuantity],
159-
],
160-
'proration_behavior' => 'none',
161-
]);
156+
try {
157+
$this->stripe->subscriptions->update($subscription->stripe_subscription_id, [
158+
'items' => [
159+
['id' => $item->id, 'quantity' => $previousQuantity],
160+
],
161+
'proration_behavior' => 'none',
162+
]);
163+
} catch (\Exception $revertException) {
164+
\Log::critical("Failed to revert Stripe quantity for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Stripe may have quantity {$quantity} but local is {$previousQuantity}. Error: ".$revertException->getMessage());
165+
send_internal_notification(
166+
"CRITICAL: Stripe quantity revert failed for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Manual reconciliation required."
167+
);
168+
}
162169

163170
// Void the unpaid invoice
164171
if ($latestInvoice->id) {

app/Helpers/SshMultiplexingHelper.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Support\Facades\Hash;
99
use Illuminate\Support\Facades\Log;
1010
use Illuminate\Support\Facades\Process;
11+
use Illuminate\Support\Facades\Storage;
1112

1213
class SshMultiplexingHelper
1314
{
@@ -209,12 +210,37 @@ private static function isMultiplexingEnabled(): bool
209210
private static function validateSshKey(PrivateKey $privateKey): void
210211
{
211212
$keyLocation = $privateKey->getKeyLocation();
212-
$checkKeyCommand = "ls $keyLocation 2>/dev/null";
213-
$keyCheckProcess = Process::run($checkKeyCommand);
213+
$filename = "ssh_key@{$privateKey->uuid}";
214+
$disk = Storage::disk('ssh-keys');
214215

215-
if ($keyCheckProcess->exitCode() !== 0) {
216+
$needsRewrite = false;
217+
218+
if (! $disk->exists($filename)) {
219+
$needsRewrite = true;
220+
} else {
221+
$diskContent = $disk->get($filename);
222+
if ($diskContent !== $privateKey->private_key) {
223+
Log::warning('SSH key file content does not match database, resyncing', [
224+
'key_uuid' => $privateKey->uuid,
225+
]);
226+
$needsRewrite = true;
227+
}
228+
}
229+
230+
if ($needsRewrite) {
216231
$privateKey->storeInFileSystem();
217232
}
233+
234+
// Ensure correct permissions (SSH requires 0600)
235+
if (file_exists($keyLocation)) {
236+
$currentPerms = fileperms($keyLocation) & 0777;
237+
if ($currentPerms !== 0600 && ! chmod($keyLocation, 0600)) {
238+
Log::warning('Failed to set SSH key file permissions to 0600', [
239+
'key_uuid' => $privateKey->uuid,
240+
'path' => $keyLocation,
241+
]);
242+
}
243+
}
218244
}
219245

220246
private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string

0 commit comments

Comments
 (0)