fixed event join token handling in the event admin. created new seeders with new tenants and package purchases. added new playwright test scenarios.
This commit is contained in:
@@ -7,18 +7,16 @@ use App\Models\Package;
|
||||
use App\Models\PackagePurchase;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\TenantPackage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Stripe\Stripe;
|
||||
use Stripe\PaymentIntent;
|
||||
use PayPal\PayPalClient;
|
||||
use PayPal\Environment\SandboxEnvironment;
|
||||
use PayPal\Environment\LiveEnvironment;
|
||||
use PayPal\Checkout\Orders\OrdersCreateRequest;
|
||||
use PayPal\Checkout\Orders\OrdersCaptureRequest;
|
||||
use PayPal\Checkout\Orders\OrdersCreateRequest;
|
||||
use PayPal\Environment\LiveEnvironment;
|
||||
use PayPal\Environment\SandboxEnvironment;
|
||||
use PayPal\PayPalClient;
|
||||
|
||||
class PackageController extends Controller
|
||||
{
|
||||
@@ -30,7 +28,16 @@ class PackageController extends Controller
|
||||
->get();
|
||||
|
||||
$packages->each(function ($package) {
|
||||
$package->features = json_decode($package->features ?? '[]', true);
|
||||
if (is_string($package->features)) {
|
||||
$decoded = json_decode($package->features, true);
|
||||
$package->features = is_array($decoded) ? $decoded : [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! is_array($package->features)) {
|
||||
$package->features = [];
|
||||
}
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
@@ -51,7 +58,7 @@ class PackageController extends Controller
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (!$tenant) {
|
||||
if (! $tenant) {
|
||||
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
||||
}
|
||||
|
||||
@@ -73,7 +80,7 @@ class PackageController extends Controller
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (!$tenant) {
|
||||
if (! $tenant) {
|
||||
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
||||
}
|
||||
|
||||
@@ -105,7 +112,7 @@ class PackageController extends Controller
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (!$tenant) {
|
||||
if (! $tenant) {
|
||||
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
||||
}
|
||||
|
||||
@@ -146,7 +153,7 @@ class PackageController extends Controller
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (!$tenant) {
|
||||
if (! $tenant) {
|
||||
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
||||
}
|
||||
|
||||
@@ -154,7 +161,7 @@ class PackageController extends Controller
|
||||
throw ValidationException::withMessages(['package' => 'Not a free package.']);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($request, $package, $tenant) {
|
||||
DB::transaction(function () use ($package, $tenant) {
|
||||
PackagePurchase::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
@@ -188,7 +195,7 @@ class PackageController extends Controller
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (!$tenant) {
|
||||
if (! $tenant) {
|
||||
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
||||
}
|
||||
|
||||
@@ -202,25 +209,25 @@ class PackageController extends Controller
|
||||
|
||||
$client = PayPalClient::client($environment);
|
||||
|
||||
$request = new OrdersCreateRequest();
|
||||
$request = new OrdersCreateRequest;
|
||||
$request->prefer('return=representation');
|
||||
$request->body = [
|
||||
"intent" => "CAPTURE",
|
||||
"purchase_units" => [[
|
||||
"amount" => [
|
||||
"currency_code" => "EUR",
|
||||
"value" => number_format($package->price, 2, '.', ''),
|
||||
'intent' => 'CAPTURE',
|
||||
'purchase_units' => [[
|
||||
'amount' => [
|
||||
'currency_code' => 'EUR',
|
||||
'value' => number_format($package->price, 2, '.', ''),
|
||||
],
|
||||
"description" => 'Fotospiel Package: ' . $package->name,
|
||||
"custom_id" => json_encode([
|
||||
'description' => 'Fotospiel Package: '.$package->name,
|
||||
'custom_id' => json_encode([
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
'user_id' => $tenant->user_id ?? null,
|
||||
]),
|
||||
]],
|
||||
"application_context" => [
|
||||
"shipping_preference" => "NO_SHIPPING",
|
||||
"user_action" => "PAY_NOW",
|
||||
'application_context' => [
|
||||
'shipping_preference' => 'NO_SHIPPING',
|
||||
'user_action' => 'PAY_NOW',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -232,7 +239,7 @@ class PackageController extends Controller
|
||||
'orderID' => $order->id,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('PayPal order creation error: ' . $e->getMessage());
|
||||
Log::error('PayPal order creation error: '.$e->getMessage());
|
||||
throw ValidationException::withMessages(['payment' => 'PayPal-Bestellung fehlgeschlagen.']);
|
||||
}
|
||||
}
|
||||
@@ -263,11 +270,11 @@ class PackageController extends Controller
|
||||
$capture = $response->result;
|
||||
|
||||
if ($capture->status !== 'COMPLETED') {
|
||||
throw new \Exception('PayPal capture not completed: ' . $capture->status);
|
||||
throw new \Exception('PayPal capture not completed: '.$capture->status);
|
||||
}
|
||||
|
||||
$customId = $capture->purchaseUnits[0]->customId ?? null;
|
||||
if (!$customId) {
|
||||
if (! $customId) {
|
||||
throw new \Exception('No metadata in PayPal order');
|
||||
}
|
||||
|
||||
@@ -275,7 +282,7 @@ class PackageController extends Controller
|
||||
$tenant = Tenant::find($metadata['tenant_id']);
|
||||
$package = Package::find($metadata['package_id']);
|
||||
|
||||
if (!$tenant || !$package) {
|
||||
if (! $tenant || ! $package) {
|
||||
throw new \Exception('Tenant or package not found');
|
||||
}
|
||||
|
||||
@@ -325,8 +332,9 @@ class PackageController extends Controller
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Payment successful']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('PayPal capture error: ' . $e->getMessage(), ['order_id' => $orderId]);
|
||||
return response()->json(['success' => false, 'message' => 'Capture failed: ' . $e->getMessage()], 422);
|
||||
Log::error('PayPal capture error: '.$e->getMessage(), ['order_id' => $orderId]);
|
||||
|
||||
return response()->json(['success' => false, 'message' => 'Capture failed: '.$e->getMessage()], 422);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,14 +388,16 @@ class PackageController extends Controller
|
||||
$type = $request->type;
|
||||
|
||||
if ($type === 'reseller_subscription') {
|
||||
$response = (new StripeController())->createSubscription($request);
|
||||
$response = (new StripeController)->createSubscription($request);
|
||||
|
||||
return $response;
|
||||
} else {
|
||||
$response = (new StripeController())->createPaymentIntent($request);
|
||||
$response = (new StripeController)->createPaymentIntent($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for PayPal client - add this if not exists, or use global
|
||||
// But since SDK has PayPalClient, assume it's used
|
||||
}
|
||||
}
|
||||
|
||||
83
app/Http/Controllers/Api/Tenant/DashboardController.php
Normal file
83
app/Http/Controllers/Api/Tenant/DashboardController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Tenant;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Event;
|
||||
use App\Models\Photo;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (! $tenant instanceof Tenant) {
|
||||
$decoded = $request->attributes->get('decoded_token', []);
|
||||
$tenantId = Arr::get($decoded, 'tenant_id');
|
||||
|
||||
if ($tenantId) {
|
||||
$tenant = Tenant::query()->find($tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $tenant instanceof Tenant) {
|
||||
return response()->json([
|
||||
'message' => 'Tenant context missing.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$eventsQuery = Event::query()
|
||||
->where('tenant_id', $tenant->getKey());
|
||||
|
||||
$activeEvents = (clone $eventsQuery)
|
||||
->where(fn ($query) => $query
|
||||
->where('is_active', true)
|
||||
->orWhere('status', 'published'))
|
||||
->count();
|
||||
|
||||
$upcomingEvents = (clone $eventsQuery)
|
||||
->whereDate('date', '>=', Carbon::now()->startOfDay())
|
||||
->count();
|
||||
|
||||
$eventsWithTasks = (clone $eventsQuery)
|
||||
->whereHas('tasks')
|
||||
->count();
|
||||
|
||||
$totalEvents = (clone $eventsQuery)->count();
|
||||
|
||||
$taskProgress = $totalEvents > 0
|
||||
? (int) round(($eventsWithTasks / $totalEvents) * 100)
|
||||
: 0;
|
||||
|
||||
$newPhotos = Photo::query()
|
||||
->whereHas('event', fn ($query) => $query->where('tenant_id', $tenant->getKey()))
|
||||
->where('created_at', '>=', Carbon::now()->subDays(7))
|
||||
->count();
|
||||
|
||||
$activePackage = $tenant->tenantPackages()
|
||||
->with('package')
|
||||
->where('active', true)
|
||||
->orderByDesc('expires_at')
|
||||
->orderByDesc('purchased_at')
|
||||
->first();
|
||||
|
||||
return response()->json([
|
||||
'active_events' => $activeEvents,
|
||||
'new_photos' => $newPhotos,
|
||||
'task_progress' => $taskProgress,
|
||||
'credit_balance' => $tenant->event_credits_balance ?? null,
|
||||
'upcoming_events' => $upcomingEvents,
|
||||
'active_package' => $activePackage ? [
|
||||
'name' => $activePackage->package?->getNameForLocale(app()->getLocale()) ?? $activePackage->package?->name ?? '',
|
||||
'expires_at' => optional($activePackage->expires_at)->toIso8601String(),
|
||||
'remaining_events' => $activePackage->remaining_events ?? null,
|
||||
] : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ namespace App\Http\Controllers\Api\Tenant;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Tenant\EventStoreRequest;
|
||||
use App\Http\Resources\Tenant\EventJoinTokenResource;
|
||||
use App\Http\Resources\Tenant\EventResource;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\EventPackage;
|
||||
use App\Models\Package;
|
||||
use App\Models\Photo;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\EventJoinTokenService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -21,9 +22,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class EventController extends Controller
|
||||
{
|
||||
public function __construct(private readonly EventJoinTokenService $joinTokenService)
|
||||
{
|
||||
}
|
||||
public function __construct(private readonly EventJoinTokenService $joinTokenService) {}
|
||||
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
@@ -36,7 +35,12 @@ class EventController extends Controller
|
||||
}
|
||||
|
||||
$query = Event::where('tenant_id', $tenantId)
|
||||
->with(['eventType', 'photos'])
|
||||
->with([
|
||||
'eventType',
|
||||
'photos',
|
||||
'eventPackages.package',
|
||||
'eventPackage.package',
|
||||
])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
if ($request->has('status')) {
|
||||
@@ -65,9 +69,31 @@ class EventController extends Controller
|
||||
$validated = $request->validated();
|
||||
$tenantId = $tenant->id;
|
||||
|
||||
$packageId = $validated['package_id'] ?? 1; // Default to Free package ID 1
|
||||
$requestedPackageId = $validated['package_id'] ?? null;
|
||||
unset($validated['package_id']);
|
||||
|
||||
$tenantPackage = $tenant->tenantPackages()
|
||||
->with('package')
|
||||
->where('active', true)
|
||||
->orderByDesc('purchased_at')
|
||||
->first();
|
||||
|
||||
$package = null;
|
||||
|
||||
if ($requestedPackageId) {
|
||||
$package = Package::query()->find($requestedPackageId);
|
||||
}
|
||||
|
||||
if (! $package && $tenantPackage) {
|
||||
$package = $tenantPackage->package ?? Package::query()->find($tenantPackage->package_id);
|
||||
}
|
||||
|
||||
if (! $package) {
|
||||
throw ValidationException::withMessages([
|
||||
'package_id' => __('Aktuell ist kein aktives Paket verfügbar. Bitte buche zunächst ein Paket.'),
|
||||
]);
|
||||
}
|
||||
|
||||
$eventData = array_merge($validated, [
|
||||
'tenant_id' => $tenantId,
|
||||
'status' => $validated['status'] ?? 'draft',
|
||||
@@ -122,37 +148,30 @@ class EventController extends Controller
|
||||
|
||||
$eventData = Arr::only($eventData, $allowed);
|
||||
|
||||
$event = DB::transaction(function () use ($tenant, $eventData, $packageId) {
|
||||
$event = DB::transaction(function () use ($tenant, $eventData, $package) {
|
||||
$event = Event::create($eventData);
|
||||
|
||||
$package = \App\Models\Package::findOrFail($packageId);
|
||||
\App\Models\EventPackage::create([
|
||||
EventPackage::create([
|
||||
'event_id' => $event->id,
|
||||
'package_id' => $packageId,
|
||||
'price' => $package->price,
|
||||
'package_id' => $package->id,
|
||||
'purchased_price' => $package->price,
|
||||
'purchased_at' => now(),
|
||||
'gallery_expires_at' => $package->gallery_days ? now()->addDays($package->gallery_days) : null,
|
||||
]);
|
||||
|
||||
\App\Models\PackagePurchase::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'event_id' => $event->id,
|
||||
'package_id' => $packageId,
|
||||
'provider_id' => 'free',
|
||||
'price' => $package->price,
|
||||
'type' => 'endcustomer_event',
|
||||
'metadata' => json_encode(['note' => 'Free package assigned on event creation']),
|
||||
]);
|
||||
if ($package->isReseller()) {
|
||||
$note = sprintf('Event #%d created (%s)', $event->id, $event->name);
|
||||
|
||||
$note = sprintf('Event #%d created (%s)', $event->id, $event->name);
|
||||
if (! $tenant->consumeEventAllowance(1, 'event.create', $note)) {
|
||||
throw new HttpException(402, 'Insufficient credits or package allowance.');
|
||||
if (! $tenant->consumeEventAllowance(1, 'event.create', $note)) {
|
||||
throw new HttpException(402, 'Insufficient credits or package allowance.');
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
});
|
||||
|
||||
$tenant->refresh();
|
||||
$event->load(['eventType', 'tenant', 'eventPackage.package']);
|
||||
$event->load(['eventType', 'tenant', 'eventPackages.package']);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Event created successfully',
|
||||
@@ -175,6 +194,10 @@ class EventController extends Controller
|
||||
'photos' => fn ($query) => $query->with('likes')->latest(),
|
||||
'tasks',
|
||||
'tenant' => fn ($query) => $query->select('id', 'name', 'event_credits_balance'),
|
||||
'eventPackages' => fn ($query) => $query
|
||||
->with('package')
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('created_at'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
@@ -229,7 +252,6 @@ class EventController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function stats(Request $request, Event $event): JsonResponse
|
||||
{
|
||||
$tenantId = $request->attributes->get('tenant_id');
|
||||
@@ -312,6 +334,7 @@ class EventController extends Controller
|
||||
'join_token' => new EventJoinTokenResource($joinToken),
|
||||
]);
|
||||
}
|
||||
|
||||
public function bulkUpdateStatus(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = $request->attributes->get('tenant_id');
|
||||
@@ -341,7 +364,7 @@ class EventController extends Controller
|
||||
->where('tenant_id', $tenantId)
|
||||
->when($excludeId, fn ($query) => $query->where('id', '!=', $excludeId))
|
||||
->exists()) {
|
||||
$slug = $originalSlug . '-' . $counter;
|
||||
$slug = $originalSlug.'-'.$counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,15 @@ use App\Http\Resources\Tenant\EventJoinTokenResource;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventJoinToken;
|
||||
use App\Services\EventJoinTokenService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class EventJoinTokenController extends Controller
|
||||
{
|
||||
public function __construct(private readonly EventJoinTokenService $joinTokenService)
|
||||
{
|
||||
}
|
||||
public function __construct(private readonly EventJoinTokenService $joinTokenService) {}
|
||||
|
||||
public function index(Request $request, Event $event): JsonResponse
|
||||
public function index(Request $request, Event $event): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorizeEvent($request, $event);
|
||||
|
||||
@@ -48,7 +46,7 @@ class EventJoinTokenController extends Controller
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Event $event, EventJoinToken $joinToken): JsonResponse
|
||||
public function destroy(Request $request, Event $event, EventJoinToken $joinToken): EventJoinTokenResource
|
||||
{
|
||||
$this->authorizeEvent($request, $event);
|
||||
|
||||
|
||||
20
app/Http/Controllers/Api/Tenant/EventTypeController.php
Normal file
20
app/Http/Controllers/Api/Tenant/EventTypeController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Tenant;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Tenant\EventTypeResource;
|
||||
use App\Models\EventType;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class EventTypeController extends Controller
|
||||
{
|
||||
public function __invoke(): AnonymousResourceCollection
|
||||
{
|
||||
$eventTypes = EventType::query()
|
||||
->orderBy('slug')
|
||||
->get();
|
||||
|
||||
return EventTypeResource::collection($eventTypes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user