- Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env

hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads,
attach packages, and surface localized success/error states.
- Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/
PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent
creation, webhooks, and the wizard CTA.
- Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/
useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages,
Checkout) with localized copy and experiment tracking.
- Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing
localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations.
- Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke
test for the hero CTA while reconciling outstanding checklist items.
This commit is contained in:
Codex Agent
2025-10-19 11:41:03 +02:00
parent ae9b9160ac
commit a949c8d3af
113 changed files with 5169 additions and 712 deletions

View File

@@ -8,8 +8,11 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\TenantPackage;
use App\Models\EventCreditsLedger;
class Tenant extends Model
@@ -55,6 +58,13 @@ class Tenant extends Model
return $this->hasMany(TenantPackage::class);
}
public function packages(): BelongsToMany
{
return $this->belongsToMany(Package::class, 'tenant_packages')
->withPivot(['price', 'purchased_at', 'expires_at', 'active'])
->withTimestamps();
}
public function activeResellerPackage(): HasOne
{
return $this->hasOne(TenantPackage::class)->where('active', true);
@@ -62,18 +72,13 @@ class Tenant extends Model
public function canCreateEvent(): bool
{
$package = $this->activeResellerPackage()->first();
if (!$package) {
return false;
}
return $package->canCreateEvent();
return $this->hasEventAllowance();
}
public function incrementUsedEvents(int $amount = 1): bool
{
$package = $this->activeResellerPackage()->first();
if (!$package) {
$package = $this->getActiveResellerPackage();
if (! $package) {
return false;
}
@@ -108,6 +113,13 @@ class Tenant extends Model
'note' => $note,
]);
Log::info('Tenant credits incremented', [
'tenant_id' => $this->id,
'delta' => $amount,
'reason' => $reason,
'purchase_id' => $purchaseId,
]);
return true;
}
@@ -130,9 +142,54 @@ class Tenant extends Model
'note' => $note,
]);
Log::info('Tenant credits decremented', [
'tenant_id' => $this->id,
'delta' => -$amount,
'reason' => $reason,
'purchase_id' => $purchaseId,
]);
return true;
}
public function hasEventAllowance(): bool
{
$package = $this->getActiveResellerPackage();
if ($package && $package->canCreateEvent()) {
return true;
}
return (int) ($this->event_credits_balance ?? 0) > 0;
}
public function consumeEventAllowance(int $amount = 1, string $reason = 'event.create', ?string $note = null): bool
{
$package = $this->getActiveResellerPackage();
if ($package && $package->canCreateEvent()) {
$package->increment('used_events', $amount);
Log::info('Tenant package usage recorded', [
'tenant_id' => $this->id,
'tenant_package_id' => $package->id,
'used_events' => $package->used_events,
'amount' => $amount,
]);
return true;
}
return $this->decrementCredits($amount, $reason, $note);
}
public function getActiveResellerPackage(): ?TenantPackage
{
return $this->activeResellerPackage()
->whereHas('package', fn ($query) => $query->where('type', 'reseller'))
->where('active', true)
->orderByDesc('expires_at')
->first();
}
public function activeSubscription(): Attribute
{
return Attribute::make(