'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(); });