seed(RolesAndPermissionsSeeder::class); $this->topups = app(CreditTopupService::class); $this->wallet = app(CreditWalletService::class); }); test('the bonus pack staffel is configured as agreed', function () { expect($this->topups->packs())->toBe([ ['key' => 'p10', 'price_cents' => 1000, 'credits' => 10], ['key' => 'p25', 'price_cents' => 2500, 'credits' => 27], ['key' => 'p50', 'price_cents' => 5000, 'credits' => 55], ['key' => 'p100', 'price_cents' => 10000, 'credits' => 115], ]); }); test('starting a topup creates a pending record without crediting yet', function () { $user = User::factory()->create(); $topup = $this->topups->startTopup($user, 'p25'); expect($topup->status)->toBe(SinglePurchaseStatus::Pending); expect($topup->credits)->toBe(27); expect($topup->price_cents)->toBe(2500); expect($this->wallet->balance($user))->toBe(0); }); test('an unknown pack is rejected', function () { $user = User::factory()->create(); expect(fn () => $this->topups->startTopup($user, 'nope')) ->toThrow(InvalidArgumentException::class); }); test('fulfilling a topup credits the wallet exactly once', function () { $user = User::factory()->create(); $topup = $this->topups->startTopup($user, 'p50'); $this->topups->fulfill($topup, 'cs_test_1', 'pi_test_1'); $this->topups->fulfill($topup->fresh(), 'cs_test_1', 'pi_test_1'); // Replay expect($this->wallet->balance($user))->toBe(55); expect($topup->fresh()->status)->toBe(SinglePurchaseStatus::Paid); expect($topup->fresh()->credited_at)->not->toBeNull(); expect($user->creditTransactions()->count())->toBe(1); }); test('the checkout webhook credits the wallet from session metadata', function () { $user = User::factory()->create(); $topup = $this->topups->startTopup($user, 'p100'); $event = new WebhookReceived([ 'type' => 'checkout.session.completed', 'data' => ['object' => [ 'id' => 'cs_test_hook', 'payment_intent' => 'pi_test_hook', 'metadata' => ['credit_topup_id' => (string) $topup->id], ]], ]); app(ProcessStripeWebhook::class)->handle($event); expect($this->wallet->balance($user))->toBe(115); expect($topup->fresh()->credited_at)->not->toBeNull(); }); test('the topup checkout route redirects to the profile without a billing address', function () { $user = User::factory()->create(['is_active' => true, 'email_verified_at' => now()]); $user->assignRole('customer'); $this->actingAs($user) ->get(route('me.checkout.credit-topup', ['pack' => 'p25'])) ->assertRedirect(route('me.profile')); }); test('the topup checkout route starts a stripe checkout with a complete billing address', function () { $user = User::factory()->create(['is_active' => true, 'email_verified_at' => now()]); $user->assignRole('customer'); BillingAddress::factory()->create(['user_id' => $user->id]); $checkout = Mockery::mock(Checkout::class); $checkout->shouldReceive('toResponse')->andReturn(new RedirectResponse('https://checkout.stripe.com/c/pay/test')); $mock = Mockery::mock(StripeCheckoutService::class); $mock->shouldReceive('forCreditTopup')->once()->andReturn($checkout); app()->instance(StripeCheckoutService::class, $mock); $this->actingAs($user) ->get(route('me.checkout.credit-topup', ['pack' => 'p25'])) ->assertRedirect('https://checkout.stripe.com/c/pay/test'); expect($user->creditTopups()->where('pack_key', 'p25')->exists())->toBeTrue(); });