endcustomer()->create([ 'max_photos' => 500, 'max_guests' => 200, 'gallery_days' => 60, ]); $event = Event::factory()->for($this->tenant)->create([ 'name' => ['de' => 'Gala', 'en' => 'Gala'], ]); $eventPackage = EventPackage::create([ 'event_id' => $event->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now()->subMonth(), 'used_photos' => 0, 'used_guests' => 0, 'gallery_expires_at' => now()->addDays(30), ]); $firstAddon = EventPackageAddon::create([ 'event_package_id' => $eventPackage->id, 'event_id' => $event->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_guests_50', 'quantity' => 1, 'extra_guests' => 50, 'status' => 'completed', 'amount' => 99.00, 'currency' => 'EUR', 'metadata' => ['label' => '+50 Gäste'], 'purchased_at' => now()->subDay(), 'receipt_payload' => ['receipt_url' => 'https://receipt.example/first'], ]); $secondAddon = EventPackageAddon::create([ 'event_package_id' => $eventPackage->id, 'event_id' => $event->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_photos_200', 'quantity' => 2, 'extra_photos' => 200, 'extra_guests' => 0, 'status' => 'pending', 'amount' => 149.00, 'currency' => 'EUR', 'metadata' => ['label' => '+200 Fotos'], 'purchased_at' => now(), 'receipt_payload' => ['receipt_url' => 'https://receipt.example/second'], ]); $otherTenant = Tenant::factory()->create(); $otherEvent = Event::factory()->for($otherTenant)->create(); $otherPackage = EventPackage::create([ 'event_id' => $otherEvent->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now()->subWeek(), 'used_photos' => 0, 'used_guests' => 0, 'gallery_expires_at' => now()->addDays(30), ]); EventPackageAddon::create([ 'event_package_id' => $otherPackage->id, 'event_id' => $otherEvent->id, 'tenant_id' => $otherTenant->id, 'addon_key' => 'extra_guests_999', 'quantity' => 1, 'extra_guests' => 999, 'status' => 'completed', 'amount' => 100.00, 'currency' => 'EUR', 'purchased_at' => now(), ]); $response = $this->authenticatedRequest('GET', '/api/v1/tenant/billing/addons'); $response->assertOk(); $response->assertJsonCount(2, 'data'); $response->assertJsonPath('meta.total', 2); $response->assertJsonPath('data.0.id', $secondAddon->id); $response->assertJsonPath('data.0.receipt_url', 'https://receipt.example/second'); $response->assertJsonPath('data.0.event.slug', $event->slug); $response->assertJsonPath('data.1.id', $firstAddon->id); } public function test_tenant_can_filter_addon_history_by_event_scope(): void { $package = Package::factory()->endcustomer()->create(); $firstEvent = Event::factory()->for($this->tenant)->create([ 'slug' => 'first-event', 'name' => ['de' => 'Erstes Event', 'en' => 'First Event'], ]); $secondEvent = Event::factory()->for($this->tenant)->create([ 'slug' => 'second-event', 'name' => ['de' => 'Zweites Event', 'en' => 'Second Event'], ]); $firstEventPackage = EventPackage::create([ 'event_id' => $firstEvent->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now()->subWeek(), 'used_photos' => 0, 'used_guests' => 0, 'gallery_expires_at' => now()->addDays(20), ]); $secondEventPackage = EventPackage::create([ 'event_id' => $secondEvent->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now()->subWeek(), 'used_photos' => 0, 'used_guests' => 0, 'gallery_expires_at' => now()->addDays(20), ]); $scopedAddon = EventPackageAddon::create([ 'event_package_id' => $firstEventPackage->id, 'event_id' => $firstEvent->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_photos_100', 'quantity' => 1, 'status' => 'completed', 'amount' => 29.00, 'currency' => 'EUR', 'purchased_at' => now(), ]); EventPackageAddon::create([ 'event_package_id' => $secondEventPackage->id, 'event_id' => $secondEvent->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_guests_100', 'quantity' => 1, 'status' => 'completed', 'amount' => 39.00, 'currency' => 'EUR', 'purchased_at' => now(), ]); $response = $this->authenticatedRequest('GET', "/api/v1/tenant/billing/addons?event_id={$firstEvent->id}"); $response->assertOk(); $response->assertJsonPath('meta.total', 1); $response->assertJsonPath('data.0.id', $scopedAddon->id); $response->assertJsonPath('meta.scope.type', 'event'); $response->assertJsonPath('meta.scope.event.id', $firstEvent->id); $response->assertJsonPath('meta.scope.event.slug', 'first-event'); } public function test_tenant_can_filter_addon_history_by_event_slug_and_status(): void { $package = Package::factory()->endcustomer()->create(); $event = Event::factory()->for($this->tenant)->create([ 'slug' => 'winter-ball', ]); $eventPackage = EventPackage::create([ 'event_id' => $event->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now()->subWeek(), 'used_photos' => 0, 'used_guests' => 0, 'gallery_expires_at' => now()->addDays(20), ]); EventPackageAddon::create([ 'event_package_id' => $eventPackage->id, 'event_id' => $event->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_photos_100', 'quantity' => 1, 'status' => 'pending', 'amount' => 19.00, 'currency' => 'EUR', 'purchased_at' => now()->subHour(), ]); $completedAddon = EventPackageAddon::create([ 'event_package_id' => $eventPackage->id, 'event_id' => $event->id, 'tenant_id' => $this->tenant->id, 'addon_key' => 'extra_guests_100', 'quantity' => 1, 'status' => 'completed', 'amount' => 29.00, 'currency' => 'EUR', 'purchased_at' => now(), ]); $response = $this->authenticatedRequest('GET', '/api/v1/tenant/billing/addons?event_slug=winter-ball&status=completed'); $response->assertOk(); $response->assertJsonPath('meta.total', 1); $response->assertJsonPath('data.0.id', $completedAddon->id); $response->assertJsonPath('data.0.status', 'completed'); $response->assertJsonPath('meta.scope.type', 'event'); $response->assertJsonPath('meta.scope.event.slug', 'winter-ball'); } public function test_tenant_gets_not_found_for_unknown_event_scope_filter(): void { $response = $this->authenticatedRequest('GET', '/api/v1/tenant/billing/addons?event_slug=missing-event'); $response->assertStatus(404); $response->assertJsonPath('message', 'Event scope not found.'); } }