seed(RolesAndPermissionsSeeder::class); }); function checkoutTestCustomer(): User { $user = User::factory()->create(); $user->assignRole('customer'); return $user; } /** * Stripe-Checkout-Stub: Der Controller gibt das Checkout-Objekt zurück, * der Router ruft toResponse() — wir leiten auf eine Fake-Stripe-URL um. */ function fakeStripeCheckout(): Checkout { $checkout = Mockery::mock(Checkout::class); $checkout->shouldReceive('toResponse') ->andReturn(new RedirectResponse('https://checkout.stripe.com/c/pay/test')); return $checkout; } test('guests are redirected to the login', function () { /** @var TestCase $this */ $plan = Plan::factory()->create(); $this->get(route('me.checkout.subscription', ['planSlug' => $plan->slug, 'interval' => 'monthly'])) ->assertRedirect(); }); test('an unknown plan or invalid interval responds 404', function () { /** @var TestCase $this */ $this->actingAs(checkoutTestCustomer()); $this->get('/admin/me/checkout/abo/gibts-nicht/monthly')->assertNotFound(); $plan = Plan::factory()->create(); $this->get("/admin/me/checkout/abo/{$plan->slug}/weekly")->assertNotFound(); }); test('a plan without a synced stripe price redirects back with a notice', function () { /** @var TestCase $this */ $plan = Plan::factory()->create(['stripe_price_id_monthly' => null]); $this->actingAs(checkoutTestCustomer()) ->get(route('me.checkout.subscription', ['planSlug' => $plan->slug, 'interval' => 'monthly'])) ->assertRedirect(route('me.bookings.index')) ->assertSessionHas('checkout-notice'); }); test('an already subscribed user is redirected instead of double-booking', function () { /** @var TestCase $this */ $user = checkoutTestCustomer(); $plan = Plan::factory()->create(['stripe_price_id_monthly' => 'price_test_m_1']); subscribeUserToPlan($user, $plan); $this->actingAs($user) ->get(route('me.checkout.subscription', ['planSlug' => $plan->slug, 'interval' => 'monthly'])) ->assertRedirect(route('me.bookings.index')) ->assertSessionHas('checkout-notice'); }); test('the subscription checkout hands plan and interval to stripe', function () { /** @var TestCase $this */ $user = checkoutTestCustomer(); $plan = Plan::factory()->create(['stripe_price_id_yearly' => 'price_test_y_1']); $this->mock(StripeCheckoutService::class, function ($mock) use ($plan) { $mock->shouldReceive('forSubscription') ->once() ->withArgs(fn (User $u, Plan $p, string $interval) => $p->is($plan) && $interval === 'yearly') ->andReturn(fakeStripeCheckout()); }); $this->actingAs($user) ->get(route('me.checkout.subscription', ['planSlug' => $plan->slug, 'interval' => 'yearly'])) ->assertRedirect('https://checkout.stripe.com/c/pay/test'); }); test('the single pm checkout is disabled without a configured stripe price', function () { /** @var TestCase $this */ config()->set('billing.single_pm_stripe_price_id', null); $this->actingAs(checkoutTestCustomer()) ->get(route('me.checkout.single-pm')) ->assertRedirect(route('me.bookings.index')) ->assertSessionHas('checkout-notice'); expect(SinglePurchase::count())->toBe(0); }); test('the single pm checkout creates a pending purchase and hands it to stripe', function () { /** @var TestCase $this */ config()->set('billing.single_pm_stripe_price_id', 'price_test_single_pm'); config()->set('billing.single_pm_price_cents', 1900); $user = checkoutTestCustomer(); $this->mock(StripeCheckoutService::class, function ($mock) use ($user) { $mock->shouldReceive('forSinglePurchase') ->once() ->withArgs(fn (User $u, SinglePurchase $p) => $u->is($user) && $p->exists) ->andReturn(fakeStripeCheckout()); }); $this->actingAs($user) ->get(route('me.checkout.single-pm')) ->assertRedirect('https://checkout.stripe.com/c/pay/test'); $purchase = SinglePurchase::sole(); expect($purchase->user_id)->toBe($user->id); expect($purchase->type)->toBe(SinglePurchaseType::SinglePm); expect($purchase->status)->toBe(SinglePurchaseStatus::Pending); expect($purchase->price_cents)->toBe(1900); });