2026-05-14 23:23:35 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Enums\Plan\Slug;
|
|
|
|
|
use App\Models\Account;
|
|
|
|
|
use App\Models\Plan;
|
|
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Database\Seeders\PlanSeeder;
|
|
|
|
|
|
|
|
|
|
beforeEach(function () {
|
2026-05-21 13:02:38 +00:00
|
|
|
config(['trypost.billing.require_card_for_trial' => true]);
|
2026-05-14 23:23:35 +00:00
|
|
|
$this->seed(PlanSeeder::class);
|
|
|
|
|
Carbon::setTestNow('2026-05-14 12:00:00');
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-14 18:46:17 +00:00
|
|
|
test('isPastDue returns false without a subscription', function () {
|
|
|
|
|
config(['trypost.self_hosted' => false]);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => null]);
|
|
|
|
|
|
|
|
|
|
expect($account->isPastDue())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('isPastDue returns true for a past_due subscription', function () {
|
|
|
|
|
config(['trypost.self_hosted' => false]);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'past_due',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->isPastDue())->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('isPastDue returns false for an active subscription', function () {
|
|
|
|
|
config(['trypost.self_hosted' => false]);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'active',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->isPastDue())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('isPastDue returns false when self-hosted', function () {
|
|
|
|
|
config(['trypost.self_hosted' => true]);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'past_due',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->isPastDue())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-21 12:49:44 +00:00
|
|
|
test('isOnTrial ignores generic trial when there is no subscription', function () {
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => now()->addDays(7)]);
|
2026-05-14 23:23:35 +00:00
|
|
|
|
|
|
|
|
expect($account->isOnTrial())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-21 13:02:38 +00:00
|
|
|
test('isOnTrial includes generic trial when card is not required', function () {
|
|
|
|
|
config(['trypost.billing.require_card_for_trial' => false]);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => now()->addDays(7)]);
|
|
|
|
|
|
|
|
|
|
expect($account->isOnTrial())->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 23:23:35 +00:00
|
|
|
test('isOnTrial returns false for account without trial or subscription', function () {
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => null]);
|
|
|
|
|
|
|
|
|
|
expect($account->isOnTrial())->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('isOnTrial returns true via subscription trial when account has no generic trial', function () {
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'trialing',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
'trial_ends_at' => now()->addDays(5),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->isOnTrial())->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('activeTrialEndsAt returns null when not on any trial', function () {
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => null]);
|
|
|
|
|
|
|
|
|
|
expect($account->activeTrialEndsAt())->toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('activeTrialEndsAt returns generic trial date when only generic is active', function () {
|
|
|
|
|
$endsAt = now()->addDays(7);
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => $endsAt]);
|
|
|
|
|
|
2026-05-21 12:49:44 +00:00
|
|
|
expect($account->activeTrialEndsAt())->toBeNull();
|
2026-05-14 23:23:35 +00:00
|
|
|
});
|
|
|
|
|
|
2026-05-21 13:02:38 +00:00
|
|
|
test('activeTrialEndsAt returns generic trial date when card is not required', function () {
|
|
|
|
|
config(['trypost.billing.require_card_for_trial' => false]);
|
|
|
|
|
|
|
|
|
|
$endsAt = now()->addDays(7);
|
|
|
|
|
$account = Account::factory()->create(['trial_ends_at' => $endsAt]);
|
|
|
|
|
|
|
|
|
|
expect($account->activeTrialEndsAt()?->toDateTimeString())
|
|
|
|
|
->toBe($endsAt->toDateTimeString());
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 23:23:35 +00:00
|
|
|
test('activeTrialEndsAt returns subscription date when only subscription trial is active', function () {
|
|
|
|
|
$subscriptionEndsAt = now()->addDays(5);
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'trialing',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
'trial_ends_at' => $subscriptionEndsAt,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->activeTrialEndsAt()?->toDateTimeString())
|
|
|
|
|
->toBe($subscriptionEndsAt->toDateTimeString());
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-21 12:49:44 +00:00
|
|
|
test('activeTrialEndsAt returns subscription date when both generic and subscription trials are present', function () {
|
2026-05-14 23:23:35 +00:00
|
|
|
$genericEndsAt = now()->addDays(7);
|
|
|
|
|
$subscriptionEndsAt = now()->addDays(14);
|
|
|
|
|
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => $genericEndsAt,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
|
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'trialing',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
'trial_ends_at' => $subscriptionEndsAt,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->activeTrialEndsAt()?->toDateTimeString())
|
|
|
|
|
->toBe($subscriptionEndsAt->toDateTimeString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('activeTrialEndsAt returns null for paying customer post-trial', function () {
|
|
|
|
|
$account = Account::factory()->create([
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
'stripe_id' => 'cus_test_'.fake()->uuid(),
|
2026-06-22 18:31:32 +00:00
|
|
|
'plan_id' => Plan::where('slug', Slug::Workspace)->value('id'),
|
2026-05-14 23:23:35 +00:00
|
|
|
]);
|
|
|
|
|
$account->subscriptions()->create([
|
|
|
|
|
'type' => Account::SUBSCRIPTION_NAME,
|
|
|
|
|
'stripe_id' => 'sub_test_'.fake()->uuid(),
|
|
|
|
|
'stripe_status' => 'active',
|
|
|
|
|
'stripe_price' => 'price_123',
|
|
|
|
|
'trial_ends_at' => null,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect($account->activeTrialEndsAt())->toBeNull();
|
|
|
|
|
expect($account->isOnTrial())->toBeFalse();
|
|
|
|
|
});
|