Certified/tests/Feature/Billing/SubscriptionSyncTest.php
2026-08-09 16:33:53 +02:00

92 lines
3.3 KiB
PHP

<?php
use App\Enums\SubscriptionPlan;
use App\Enums\SubscriptionStatus;
use App\Models\Subscription;
use App\Models\SubscriptionInvoice;
use App\Models\User;
use App\Notifications\SubscriptionDowngradedNotification;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Notification;
beforeEach(function () {
config([
'services.moneybird.api_token' => 'test-token',
'services.moneybird.administration_id' => '123456',
'services.moneybird.grace_days' => 14,
]);
});
test('an overdue awaiting-payment subscription is marked past due with a grace period', function () {
$user = User::factory()->create();
$subscription = Subscription::factory()->for($user)->starter()->create([
'status' => SubscriptionStatus::AwaitingPayment,
]);
SubscriptionInvoice::factory()->for($subscription)->create([
'state' => 'open',
'due_date' => today()->subDay(),
]);
$this->artisan('app:sync-subscriptions')->assertSuccessful();
$subscription->refresh();
expect($subscription->status)->toBe(SubscriptionStatus::PastDue)
->and($subscription->grace_ends_at?->toDateString())->toBe(today()->addDays(14)->toDateString());
});
test('a past-due subscription is downgraded to free once its grace period lapses', function () {
Notification::fake();
Http::fake([
'*/recurring_sales_invoices/recurring-1.json' => Http::response('', 204),
]);
$user = User::factory()->create();
$subscription = Subscription::factory()->for($user)->starter()->pastDue()->create([
'moneybird_recurring_invoice_id' => 'recurring-1',
]);
$this->artisan('app:sync-subscriptions')->assertSuccessful();
expect($subscription->refresh()->plan)->toBe(SubscriptionPlan::Free)
->and($subscription->status)->toBe(SubscriptionStatus::Active)
->and($subscription->moneybird_recurring_invoice_id)->toBeNull();
Http::assertSent(fn ($request) => str_contains((string) $request->url(), '/recurring_sales_invoices/recurring-1.json')
&& $request->method() === 'DELETE');
Notification::assertSentTo($user, SubscriptionDowngradedNotification::class);
});
test('a cancelled subscription is downgraded to free once the current period ends', function () {
Notification::fake();
Http::fake([
'*/recurring_sales_invoices/*' => Http::response('', 404),
]);
$user = User::factory()->create();
$subscription = Subscription::factory()->for($user)->starter()->create([
'cancel_at_period_end' => true,
'current_period_ends_at' => today()->subDay(),
]);
$this->artisan('app:sync-subscriptions')->assertSuccessful();
expect($subscription->refresh()->plan)->toBe(SubscriptionPlan::Free)
->and($subscription->cancel_at_period_end)->toBeFalse();
Notification::assertSentTo($user, SubscriptionDowngradedNotification::class);
});
test('subscriptions in good standing are left untouched', function () {
Notification::fake();
$user = User::factory()->create();
$subscription = Subscription::factory()->for($user)->starter()->create();
$this->artisan('app:sync-subscriptions')->assertSuccessful();
expect($subscription->refresh()->status)->toBe(SubscriptionStatus::Active)
->and($subscription->plan)->toBe(SubscriptionPlan::Starter);
Notification::assertNothingSent();
});