implemented event package addons with filament resource, event-admin purchase path and notifications, showing up in purchase history
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Tenant;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Addons\EventAddonCatalog;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class EventAddonCatalogController extends Controller
|
||||
{
|
||||
public function __construct(private readonly EventAddonCatalog $catalog) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$addons = collect($this->catalog->all())
|
||||
->filter(fn (array $addon) => ! empty($addon['price_id']))
|
||||
->map(fn (array $addon, string $key) => array_merge($addon, ['key' => $key]))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return response()->json(['data' => $addons]);
|
||||
}
|
||||
}
|
||||
114
app/Http/Controllers/Api/Tenant/EventAddonController.php
Normal file
114
app/Http/Controllers/Api/Tenant/EventAddonController.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Tenant;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Tenant\EventAddonCheckoutRequest;
|
||||
use App\Http\Requests\Tenant\EventAddonRequest;
|
||||
use App\Http\Resources\Tenant\EventResource;
|
||||
use App\Models\Event;
|
||||
use App\Services\Addons\EventAddonCheckoutService;
|
||||
use App\Support\ApiError;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class EventAddonController extends Controller
|
||||
{
|
||||
public function __construct(private readonly EventAddonCheckoutService $checkoutService) {}
|
||||
|
||||
public function checkout(EventAddonCheckoutRequest $request, Event $event): JsonResponse
|
||||
{
|
||||
$tenantId = $request->attributes->get('tenant_id');
|
||||
|
||||
if ($event->tenant_id !== $tenantId) {
|
||||
return ApiError::response(
|
||||
'event_not_found',
|
||||
'Event not accessible',
|
||||
__('Das Event konnte nicht gefunden werden.'),
|
||||
404,
|
||||
['event_slug' => $event->slug ?? null]
|
||||
);
|
||||
}
|
||||
|
||||
$eventPackage = $event->eventPackage ?: $event->eventPackages()
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('created_at')
|
||||
->first();
|
||||
|
||||
if ($eventPackage) {
|
||||
$event->setRelation('eventPackage', $eventPackage);
|
||||
}
|
||||
|
||||
$checkout = $this->checkoutService->createCheckout(
|
||||
$request->attributes->get('tenant'),
|
||||
$event,
|
||||
$request->validated(),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'checkout_url' => $checkout['checkout_url'] ?? null,
|
||||
'checkout_id' => $checkout['id'] ?? null,
|
||||
'expires_at' => $checkout['expires_at'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function apply(EventAddonRequest $request, Event $event): JsonResponse
|
||||
{
|
||||
$tenantId = $request->attributes->get('tenant_id');
|
||||
|
||||
if ($event->tenant_id !== $tenantId) {
|
||||
return ApiError::response(
|
||||
'event_not_found',
|
||||
'Event not accessible',
|
||||
__('Das Event konnte nicht gefunden werden.'),
|
||||
404,
|
||||
['event_slug' => $event->slug ?? null]
|
||||
);
|
||||
}
|
||||
|
||||
$eventPackage = $event->eventPackage;
|
||||
|
||||
if (! $eventPackage && method_exists($event, 'eventPackages')) {
|
||||
$eventPackage = $event->eventPackages()
|
||||
->with('package')
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('created_at')
|
||||
->first();
|
||||
}
|
||||
|
||||
if (! $eventPackage) {
|
||||
return ApiError::response(
|
||||
'event_package_missing',
|
||||
'Event package missing',
|
||||
__('Kein Paket ist diesem Event zugeordnet.'),
|
||||
409,
|
||||
['event_slug' => $event->slug ?? null]
|
||||
);
|
||||
}
|
||||
|
||||
$data = $request->validated();
|
||||
|
||||
$eventPackage->fill([
|
||||
'extra_photos' => ($eventPackage->extra_photos ?? 0) + (int) ($data['extra_photos'] ?? 0),
|
||||
'extra_guests' => ($eventPackage->extra_guests ?? 0) + (int) ($data['extra_guests'] ?? 0),
|
||||
'extra_gallery_days' => ($eventPackage->extra_gallery_days ?? 0) + (int) ($data['extend_gallery_days'] ?? 0),
|
||||
]);
|
||||
|
||||
if (isset($data['extend_gallery_days'])) {
|
||||
$base = $eventPackage->gallery_expires_at ?? Carbon::now();
|
||||
$eventPackage->gallery_expires_at = $base->copy()->addDays((int) $data['extend_gallery_days']);
|
||||
}
|
||||
|
||||
$eventPackage->save();
|
||||
|
||||
$event->load([
|
||||
'eventPackage.package',
|
||||
'eventPackages.package',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => __('Add-ons applied successfully.'),
|
||||
'data' => new EventResource($event),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ class EventController extends Controller
|
||||
});
|
||||
|
||||
$tenant->refresh();
|
||||
$event->load(['eventType', 'tenant', 'eventPackages.package']);
|
||||
$event->load(['eventType', 'tenant', 'eventPackages.package', 'eventPackages.addons']);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Event created successfully',
|
||||
@@ -222,7 +222,7 @@ class EventController extends Controller
|
||||
'tasks',
|
||||
'tenant' => fn ($query) => $query->select('id', 'name', 'event_credits_balance'),
|
||||
'eventPackages' => fn ($query) => $query
|
||||
->with('package')
|
||||
->with(['package', 'addons'])
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('created_at'),
|
||||
]);
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EventPackageAddon;
|
||||
use App\Services\Paddle\PaddleTransactionService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TenantBillingController extends Controller
|
||||
@@ -60,4 +62,58 @@ class TenantBillingController extends Controller
|
||||
'meta' => $result['meta'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function addons(Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (! $tenant) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'message' => 'Tenant not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$perPage = max(1, min((int) $request->query('per_page', 25), 100));
|
||||
$page = max(1, (int) $request->query('page', 1));
|
||||
|
||||
$paginator = EventPackageAddon::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->with(['event:id,name,slug'])
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('created_at')
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
$data = $paginator->getCollection()->map(function (EventPackageAddon $addon) {
|
||||
return [
|
||||
'id' => $addon->id,
|
||||
'addon_key' => $addon->addon_key,
|
||||
'label' => $addon->metadata['label'] ?? null,
|
||||
'quantity' => (int) ($addon->quantity ?? 1),
|
||||
'status' => $addon->status,
|
||||
'amount' => $addon->amount !== null ? (float) $addon->amount : null,
|
||||
'currency' => $addon->currency,
|
||||
'extra_photos' => (int) $addon->extra_photos,
|
||||
'extra_guests' => (int) $addon->extra_guests,
|
||||
'extra_gallery_days' => (int) $addon->extra_gallery_days,
|
||||
'purchased_at' => $addon->purchased_at?->toIso8601String(),
|
||||
'receipt_url' => Arr::get($addon->receipt_payload, 'receipt_url'),
|
||||
'event' => $addon->event ? [
|
||||
'id' => $addon->event->id,
|
||||
'slug' => $addon->event->slug,
|
||||
'name' => $addon->event->name,
|
||||
] : null,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json([
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\Addons\EventAddonWebhookService;
|
||||
use App\Services\Checkout\CheckoutWebhookService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -10,7 +11,10 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PaddleWebhookController extends Controller
|
||||
{
|
||||
public function __construct(private readonly CheckoutWebhookService $webhooks) {}
|
||||
public function __construct(
|
||||
private readonly CheckoutWebhookService $webhooks,
|
||||
private readonly EventAddonWebhookService $addonWebhooks,
|
||||
) {}
|
||||
|
||||
public function handle(Request $request): JsonResponse
|
||||
{
|
||||
@@ -31,6 +35,7 @@ class PaddleWebhookController extends Controller
|
||||
|
||||
if ($eventType) {
|
||||
$handled = $this->webhooks->handlePaddleEvent($payload);
|
||||
$handled = $this->addonWebhooks->handle($payload) || $handled;
|
||||
}
|
||||
|
||||
Log::info('Paddle webhook processed', [
|
||||
|
||||
Reference in New Issue
Block a user