mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
feat(subscription): display next billing date and billing interval
Add current_period_end to refund eligibility checks and display next billing date and billing interval in the subscription overview. Refactor the plan overview layout to show subscription status more prominently.
This commit is contained in:
@@ -19,7 +19,7 @@ class RefundSubscription
|
|||||||
/**
|
/**
|
||||||
* Check if the team's subscription is eligible for a refund.
|
* Check if the team's subscription is eligible for a refund.
|
||||||
*
|
*
|
||||||
* @return array{eligible: bool, days_remaining: int, reason: string}
|
* @return array{eligible: bool, days_remaining: int, reason: string, current_period_end: int|null}
|
||||||
*/
|
*/
|
||||||
public function checkEligibility(Team $team): array
|
public function checkEligibility(Team $team): array
|
||||||
{
|
{
|
||||||
@@ -43,8 +43,10 @@ class RefundSubscription
|
|||||||
return $this->ineligible('Subscription not found in Stripe.');
|
return $this->ineligible('Subscription not found in Stripe.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$currentPeriodEnd = $stripeSubscription->current_period_end;
|
||||||
|
|
||||||
if (! in_array($stripeSubscription->status, ['active', 'trialing'])) {
|
if (! in_array($stripeSubscription->status, ['active', 'trialing'])) {
|
||||||
return $this->ineligible("Subscription status is '{$stripeSubscription->status}'.");
|
return $this->ineligible("Subscription status is '{$stripeSubscription->status}'.", $currentPeriodEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
$startDate = \Carbon\Carbon::createFromTimestamp($stripeSubscription->start_date);
|
$startDate = \Carbon\Carbon::createFromTimestamp($stripeSubscription->start_date);
|
||||||
@@ -52,13 +54,14 @@ class RefundSubscription
|
|||||||
$daysRemaining = self::REFUND_WINDOW_DAYS - $daysSinceStart;
|
$daysRemaining = self::REFUND_WINDOW_DAYS - $daysSinceStart;
|
||||||
|
|
||||||
if ($daysRemaining <= 0) {
|
if ($daysRemaining <= 0) {
|
||||||
return $this->ineligible('The 30-day refund window has expired.');
|
return $this->ineligible('The 30-day refund window has expired.', $currentPeriodEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'eligible' => true,
|
'eligible' => true,
|
||||||
'days_remaining' => $daysRemaining,
|
'days_remaining' => $daysRemaining,
|
||||||
'reason' => 'Eligible for refund.',
|
'reason' => 'Eligible for refund.',
|
||||||
|
'current_period_end' => $currentPeriodEnd,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +131,15 @@ class RefundSubscription
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{eligible: bool, days_remaining: int, reason: string}
|
* @return array{eligible: bool, days_remaining: int, reason: string, current_period_end: int|null}
|
||||||
*/
|
*/
|
||||||
private function ineligible(string $reason): array
|
private function ineligible(string $reason, ?int $currentPeriodEnd = null): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'eligible' => false,
|
'eligible' => false,
|
||||||
'days_remaining' => 0,
|
'days_remaining' => 0,
|
||||||
'reason' => $reason,
|
'reason' => $reason,
|
||||||
|
'current_period_end' => $currentPeriodEnd,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Actions\Stripe\RefundSubscription;
|
|||||||
use App\Actions\Stripe\ResumeSubscription;
|
use App\Actions\Stripe\ResumeSubscription;
|
||||||
use App\Actions\Stripe\UpdateSubscriptionQuantity;
|
use App\Actions\Stripe\UpdateSubscriptionQuantity;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Stripe\StripeClient;
|
use Stripe\StripeClient;
|
||||||
@@ -31,10 +32,15 @@ class Actions extends Component
|
|||||||
|
|
||||||
public bool $refundAlreadyUsed = false;
|
public bool $refundAlreadyUsed = false;
|
||||||
|
|
||||||
|
public string $billingInterval = 'monthly';
|
||||||
|
|
||||||
|
public ?string $nextBillingDate = null;
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->server_limits = Team::serverLimit();
|
$this->server_limits = Team::serverLimit();
|
||||||
$this->quantity = (int) $this->server_limits;
|
$this->quantity = (int) $this->server_limits;
|
||||||
|
$this->billingInterval = currentTeam()->subscription?->billingInterval() ?? 'monthly';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadPricePreview(int $quantity): void
|
public function loadPricePreview(int $quantity): void
|
||||||
@@ -198,6 +204,10 @@ class Actions extends Component
|
|||||||
$result = (new RefundSubscription)->checkEligibility(currentTeam());
|
$result = (new RefundSubscription)->checkEligibility(currentTeam());
|
||||||
$this->isRefundEligible = $result['eligible'];
|
$this->isRefundEligible = $result['eligible'];
|
||||||
$this->refundDaysRemaining = $result['days_remaining'];
|
$this->refundDaysRemaining = $result['days_remaining'];
|
||||||
|
|
||||||
|
if ($result['current_period_end']) {
|
||||||
|
$this->nextBillingDate = Carbon::createFromTimestamp($result['current_period_end'])->format('M j, Y');
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::warning('Refund eligibility check failed: '.$e->getMessage());
|
\Log::warning('Refund eligibility check failed: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "cp $JEAN_ROOT_PATH/.env . && mkdir -p .claude && cp $JEAN_ROOT_PATH/.claude/settings.local.json .claude/settings.local.json",
|
"setup": "cp $JEAN_ROOT_PATH/.env . && mkdir -p .claude && cp $JEAN_ROOT_PATH/.claude/settings.local.json .claude/settings.local.json",
|
||||||
|
"teardown": null,
|
||||||
"run": "docker rm -f coolify coolify-minio-init coolify-realtime coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down"
|
"run": "docker rm -f coolify coolify-minio-init coolify-realtime coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down"
|
||||||
}
|
},
|
||||||
}
|
"ports": [
|
||||||
|
{
|
||||||
|
"port": 8000,
|
||||||
|
"label": "Coolify UI"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,44 +35,44 @@
|
|||||||
}" @success.window="preview = null; showModal = false; qty = $wire.server_limits"
|
}" @success.window="preview = null; showModal = false; qty = $wire.server_limits"
|
||||||
@keydown.escape.window="if (showModal) { closeAdjust(); }" class="-mt-2">
|
@keydown.escape.window="if (showModal) { closeAdjust(); }" class="-mt-2">
|
||||||
<h3 class="pb-2">Plan Overview</h3>
|
<h3 class="pb-2">Plan Overview</h3>
|
||||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-3">
|
<div class="space-y-2">
|
||||||
{{-- Current Plan Card --}}
|
<div class="text-sm">
|
||||||
<div class="p-5 rounded border dark:bg-coolgray-100 bg-white border-neutral-200 dark:border-coolgray-400">
|
<span class="text-neutral-500">Plan:</span>
|
||||||
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1">Current Plan</div>
|
<span class="dark:text-warning font-medium">
|
||||||
<div class="text-xl font-bold dark:text-warning">
|
|
||||||
@if (data_get(currentTeam(), 'subscription')->type() == 'dynamic')
|
@if (data_get(currentTeam(), 'subscription')->type() == 'dynamic')
|
||||||
Pay-as-you-go
|
Pay-as-you-go
|
||||||
@else
|
@else
|
||||||
{{ data_get(currentTeam(), 'subscription')->type() }}
|
{{ data_get(currentTeam(), 'subscription')->type() }}
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</span>
|
||||||
<div class="pt-2 text-sm">
|
<span class="text-neutral-500">· {{ $billingInterval === 'yearly' ? 'Yearly' : 'Monthly' }}</span>
|
||||||
|
<span class="text-neutral-500">·</span>
|
||||||
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
|
<span class="text-red-500 font-medium">Cancelling at end of period</span>
|
||||||
|
@else
|
||||||
|
<span class="text-green-500 font-medium">Active</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="text-sm flex items-center gap-2 flex-wrap">
|
||||||
|
<span>
|
||||||
|
<span class="text-neutral-500">Active servers:</span>
|
||||||
|
<span class="font-medium {{ currentTeam()->serverOverflow() ? 'text-red-500' : 'dark:text-white' }}">{{ currentTeam()->servers->count() }}</span>
|
||||||
|
<span class="text-neutral-500">/</span>
|
||||||
|
<span class="font-medium dark:text-white" x-text="current"></span>
|
||||||
|
<span class="text-neutral-500">paid</span>
|
||||||
|
</span>
|
||||||
|
<x-forms.button isHighlighted @click="openAdjust()">Adjust</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-neutral-500">
|
||||||
|
@if ($refundCheckLoading)
|
||||||
|
<x-loading text="Loading..." />
|
||||||
|
@elseif ($nextBillingDate)
|
||||||
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
<span class="text-red-500 font-medium">Cancelling at end of period</span>
|
Cancels on <span class="dark:text-white font-medium">{{ $nextBillingDate }}</span>
|
||||||
@else
|
@else
|
||||||
<span class="text-green-500 font-medium">Active</span>
|
Next billing <span class="dark:text-white font-medium">{{ $nextBillingDate }}</span>
|
||||||
<span class="text-neutral-500"> · Invoice
|
|
||||||
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}</span>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
@endif
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Paid Servers Card --}}
|
|
||||||
<div class="p-5 rounded border dark:bg-coolgray-100 bg-white border-neutral-200 dark:border-coolgray-400 cursor-pointer hover:border-warning/50 transition-colors"
|
|
||||||
@click="openAdjust()">
|
|
||||||
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1">Paid Servers</div>
|
|
||||||
<div class="text-xl font-bold dark:text-white" x-text="current"></div>
|
|
||||||
<div class="pt-2 text-sm text-neutral-500">Click to adjust</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Active Servers Card --}}
|
|
||||||
<div
|
|
||||||
class="p-5 rounded border dark:bg-coolgray-100 bg-white border-neutral-200 dark:border-coolgray-400 {{ currentTeam()->serverOverflow() ? 'border-red-500 dark:border-red-500' : '' }}">
|
|
||||||
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1">Active Servers</div>
|
|
||||||
<div class="text-xl font-bold {{ currentTeam()->serverOverflow() ? 'text-red-500' : 'dark:text-white' }}">
|
|
||||||
{{ currentTeam()->servers->count() }}
|
|
||||||
</div>
|
|
||||||
<div class="pt-2 text-sm text-neutral-500">Currently running</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -99,9 +99,9 @@
|
|||||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
class="relative w-full border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] max-h-[calc(100vh-2rem)] bg-neutral-100 border-neutral-400 dark:bg-base dark:border-coolgray-300 flex flex-col">
|
class="relative w-full border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] max-h-[calc(100vh-2rem)] bg-neutral-100 border-neutral-400 dark:bg-base dark:border-coolgray-300 flex flex-col">
|
||||||
<div class="flex justify-between items-center py-6 px-7 shrink-0">
|
<div class="flex justify-between items-center py-6 px-7 shrink-0">
|
||||||
<h3 class="pr-8 text-2xl font-bold">Adjust Server Limit</h3>
|
<h3 class="text-2xl font-bold">Adjust Server Limit</h3>
|
||||||
<button @click="closeAdjust()"
|
<button @click="closeAdjust()"
|
||||||
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
class="flex justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none"
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
@@ -144,7 +144,12 @@
|
|||||||
<p class="text-xs text-neutral-500 pt-1">Charged immediately to your payment method.</p>
|
<p class="text-xs text-neutral-500 pt-1">Charged immediately to your payment method.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1.5">Next billing cycle</div>
|
<div class="text-xs font-bold text-neutral-500 uppercase tracking-wide pb-1.5">
|
||||||
|
Next billing cycle
|
||||||
|
@if ($nextBillingDate)
|
||||||
|
<span class="normal-case font-normal">· {{ $nextBillingDate }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<div class="flex justify-between gap-6 text-sm">
|
<div class="flex justify-between gap-6 text-sm">
|
||||||
<span class="text-neutral-500" x-text="preview?.quantity + ' servers × ' + fmt(preview?.unit_price)"></span>
|
<span class="text-neutral-500" x-text="preview?.quantity + ' servers × ' + fmt(preview?.unit_price)"></span>
|
||||||
@@ -155,7 +160,7 @@
|
|||||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_tax)"></span>
|
<span class="dark:text-white" x-text="fmt(preview?.recurring_tax)"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between gap-6 text-sm font-bold pt-1.5 border-t dark:border-coolgray-400 border-neutral-200">
|
<div class="flex justify-between gap-6 text-sm font-bold pt-1.5 border-t dark:border-coolgray-400 border-neutral-200">
|
||||||
<span class="dark:text-white">Total / month</span>
|
<span class="dark:text-white">Total / {{ $billingInterval === 'yearly' ? 'year' : 'month' }}</span>
|
||||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_total)"></span>
|
<span class="dark:text-white" x-text="fmt(preview?.recurring_total)"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,7 +180,7 @@
|
|||||||
warningMessage="This will update your subscription and charge the prorated amount to your payment method."
|
warningMessage="This will update your subscription and charge the prorated amount to your payment method."
|
||||||
step2ButtonText="Confirm & Pay">
|
step2ButtonText="Confirm & Pay">
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<x-forms.button @click="$wire.set('quantity', qty)">
|
<x-forms.button class="w-full" @click="$wire.set('quantity', qty)">
|
||||||
Update Server Limit
|
Update Server Limit
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
@@ -194,11 +199,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{-- Billing, Refund & Cancellation --}}
|
{{-- Manage Subscription --}}
|
||||||
<section>
|
<section>
|
||||||
<h3 class="pb-2">Manage Subscription</h3>
|
<h3 class="pb-2">Manage Subscription</h3>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
{{-- Billing --}}
|
|
||||||
<x-forms.button class="gap-2" wire:click='stripeCustomerPortal'>
|
<x-forms.button class="gap-2" wire:click='stripeCustomerPortal'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor">
|
stroke-width="1.5" stroke="currentColor">
|
||||||
@@ -207,8 +211,13 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Manage Billing on Stripe
|
Manage Billing on Stripe
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{{-- Resume or Cancel --}}
|
{{-- Cancel Subscription --}}
|
||||||
|
<section>
|
||||||
|
<h3 class="pb-2">Cancel Subscription</h3>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
<x-forms.button wire:click="resumeSubscription">Resume Subscription</x-forms.button>
|
<x-forms.button wire:click="resumeSubscription">Resume Subscription</x-forms.button>
|
||||||
@else
|
@else
|
||||||
@@ -231,10 +240,18 @@
|
|||||||
confirmationLabel="Enter your team name to confirm"
|
confirmationLabel="Enter your team name to confirm"
|
||||||
shortConfirmationLabel="Team Name" step2ButtonText="Permanently Cancel" />
|
shortConfirmationLabel="Team Name" step2ButtonText="Permanently Cancel" />
|
||||||
@endif
|
@endif
|
||||||
|
</div>
|
||||||
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
|
<p class="mt-2 text-sm text-neutral-500">Your subscription is set to cancel at the end of the billing period.</p>
|
||||||
|
@endif
|
||||||
|
</section>
|
||||||
|
|
||||||
{{-- Refund --}}
|
{{-- Refund --}}
|
||||||
|
<section>
|
||||||
|
<h3 class="pb-2">Refund</h3>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
@if ($refundCheckLoading)
|
@if ($refundCheckLoading)
|
||||||
<x-loading text="Checking refund..." />
|
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
||||||
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
<x-modal-confirmation title="Request Full Refund?" buttonTitle="Request Full Refund"
|
<x-modal-confirmation title="Request Full Refund?" buttonTitle="Request Full Refund"
|
||||||
isErrorButton submitAction="refundSubscription"
|
isErrorButton submitAction="refundSubscription"
|
||||||
@@ -245,18 +262,21 @@
|
|||||||
]" confirmationText="{{ currentTeam()->name }}"
|
]" confirmationText="{{ currentTeam()->name }}"
|
||||||
confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name"
|
confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name"
|
||||||
step2ButtonText="Confirm Refund & Cancel" />
|
step2ButtonText="Confirm Refund & Cancel" />
|
||||||
|
@else
|
||||||
|
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-neutral-500">
|
||||||
{{-- Contextual notes --}}
|
@if ($refundCheckLoading)
|
||||||
@if ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
Checking refund eligibility...
|
||||||
<p class="mt-2 text-sm text-neutral-500">Eligible for a full refund — <strong class="dark:text-warning">{{ $refundDaysRemaining }}</strong> days remaining.</p>
|
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
@elseif ($refundAlreadyUsed)
|
Eligible for a full refund — <strong class="dark:text-warning">{{ $refundDaysRemaining }}</strong> days remaining.
|
||||||
<p class="mt-2 text-sm text-neutral-500">Refund already processed. Each team is eligible for one refund only.</p>
|
@elseif ($refundAlreadyUsed)
|
||||||
@endif
|
Refund already processed. Each team is eligible for one refund only.
|
||||||
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
@else
|
||||||
<p class="mt-2 text-sm text-neutral-500">Your subscription is set to cancel at the end of the billing period.</p>
|
Not eligible for a refund.
|
||||||
@endif
|
@endif
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="text-sm text-neutral-500">
|
<div class="text-sm text-neutral-500">
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ beforeEach(function () {
|
|||||||
|
|
||||||
describe('checkEligibility', function () {
|
describe('checkEligibility', function () {
|
||||||
test('returns eligible when subscription is within 30 days', function () {
|
test('returns eligible when subscription is within 30 days', function () {
|
||||||
|
$periodEnd = now()->addDays(20)->timestamp;
|
||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(10)->timestamp,
|
'start_date' => now()->subDays(10)->timestamp,
|
||||||
|
'current_period_end' => $periodEnd,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -58,12 +60,15 @@ describe('checkEligibility', function () {
|
|||||||
|
|
||||||
expect($result['eligible'])->toBeTrue();
|
expect($result['eligible'])->toBeTrue();
|
||||||
expect($result['days_remaining'])->toBe(20);
|
expect($result['days_remaining'])->toBe(20);
|
||||||
|
expect($result['current_period_end'])->toBe($periodEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns ineligible when subscription is past 30 days', function () {
|
test('returns ineligible when subscription is past 30 days', function () {
|
||||||
|
$periodEnd = now()->addDays(25)->timestamp;
|
||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(35)->timestamp,
|
'start_date' => now()->subDays(35)->timestamp,
|
||||||
|
'current_period_end' => $periodEnd,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -77,12 +82,15 @@ describe('checkEligibility', function () {
|
|||||||
expect($result['eligible'])->toBeFalse();
|
expect($result['eligible'])->toBeFalse();
|
||||||
expect($result['days_remaining'])->toBe(0);
|
expect($result['days_remaining'])->toBe(0);
|
||||||
expect($result['reason'])->toContain('30-day refund window has expired');
|
expect($result['reason'])->toContain('30-day refund window has expired');
|
||||||
|
expect($result['current_period_end'])->toBe($periodEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns ineligible when subscription is not active', function () {
|
test('returns ineligible when subscription is not active', function () {
|
||||||
|
$periodEnd = now()->addDays(25)->timestamp;
|
||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'canceled',
|
'status' => 'canceled',
|
||||||
'start_date' => now()->subDays(5)->timestamp,
|
'start_date' => now()->subDays(5)->timestamp,
|
||||||
|
'current_period_end' => $periodEnd,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -94,6 +102,7 @@ describe('checkEligibility', function () {
|
|||||||
$result = $action->checkEligibility($this->team);
|
$result = $action->checkEligibility($this->team);
|
||||||
|
|
||||||
expect($result['eligible'])->toBeFalse();
|
expect($result['eligible'])->toBeFalse();
|
||||||
|
expect($result['current_period_end'])->toBe($periodEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns ineligible when no subscription exists', function () {
|
test('returns ineligible when no subscription exists', function () {
|
||||||
@@ -104,6 +113,7 @@ describe('checkEligibility', function () {
|
|||||||
|
|
||||||
expect($result['eligible'])->toBeFalse();
|
expect($result['eligible'])->toBeFalse();
|
||||||
expect($result['reason'])->toContain('No active subscription');
|
expect($result['reason'])->toContain('No active subscription');
|
||||||
|
expect($result['current_period_end'])->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns ineligible when invoice is not paid', function () {
|
test('returns ineligible when invoice is not paid', function () {
|
||||||
@@ -114,6 +124,7 @@ describe('checkEligibility', function () {
|
|||||||
|
|
||||||
expect($result['eligible'])->toBeFalse();
|
expect($result['eligible'])->toBeFalse();
|
||||||
expect($result['reason'])->toContain('not paid');
|
expect($result['reason'])->toContain('not paid');
|
||||||
|
expect($result['current_period_end'])->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns ineligible when team has already been refunded', function () {
|
test('returns ineligible when team has already been refunded', function () {
|
||||||
@@ -145,6 +156,7 @@ describe('execute', function () {
|
|||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(10)->timestamp,
|
'start_date' => now()->subDays(10)->timestamp,
|
||||||
|
'current_period_end' => now()->addDays(20)->timestamp,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -205,6 +217,7 @@ describe('execute', function () {
|
|||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(10)->timestamp,
|
'start_date' => now()->subDays(10)->timestamp,
|
||||||
|
'current_period_end' => now()->addDays(20)->timestamp,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -229,6 +242,7 @@ describe('execute', function () {
|
|||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(10)->timestamp,
|
'start_date' => now()->subDays(10)->timestamp,
|
||||||
|
'current_period_end' => now()->addDays(20)->timestamp,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
@@ -255,6 +269,7 @@ describe('execute', function () {
|
|||||||
$stripeSubscription = (object) [
|
$stripeSubscription = (object) [
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'start_date' => now()->subDays(35)->timestamp,
|
'start_date' => now()->subDays(35)->timestamp,
|
||||||
|
'current_period_end' => now()->addDays(25)->timestamp,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->mockSubscriptions
|
$this->mockSubscriptions
|
||||||
|
|||||||
Reference in New Issue
Block a user