'decimal:2', 'max_photos' => 'integer', 'max_guests' => 'integer', 'gallery_days' => 'integer', 'max_tasks' => 'integer', 'max_events_per_year' => 'integer', 'expires_after' => 'datetime', 'watermark_allowed' => 'boolean', 'branding_allowed' => 'boolean', 'features' => 'array', 'name_translations' => 'array', 'description_translations' => 'array', 'description_table' => 'array', 'paddle_synced_at' => 'datetime', 'paddle_snapshot' => 'array', ]; protected $appends = [ 'activates_immediately', ]; protected function features(): Attribute { return Attribute::make( get: function ($value) { if (is_array($value)) { return $value; } if (is_string($value)) { $decoded = json_decode($value, true); if (is_string($decoded)) { $decoded = json_decode($decoded, true); } if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { return $decoded; } } return []; }, ); } public function eventPackages(): HasMany { return $this->hasMany(EventPackage::class); } public function tenantPackages(): HasMany { return $this->hasMany(TenantPackage::class); } public function packagePurchases(): HasMany { return $this->hasMany(PackagePurchase::class); } public function coupons(): BelongsToMany { return $this->belongsToMany(Coupon::class)->withTimestamps(); } public function isEndcustomer(): bool { return $this->type === 'endcustomer'; } public function isReseller(): bool { return $this->type === 'reseller'; } public function getNameForLocale(?string $locale = null): string { $locale = $locale ?: app()->getLocale(); $translations = $this->name_translations ?? []; if (! empty($translations[$locale])) { return $translations[$locale]; } foreach (['en', 'de'] as $fallback) { if ($locale !== $fallback && ! empty($translations[$fallback])) { return $translations[$fallback]; } } return $this->name ?? ''; } public function getLimitsAttribute(): array { return [ 'max_photos' => $this->max_photos, 'max_guests' => $this->max_guests, 'gallery_days' => $this->gallery_days, 'max_tasks' => $this->max_tasks, 'max_events_per_year' => $this->max_events_per_year, ]; } public function getPaddleSyncErrorMessageAttribute(): ?string { $message = data_get($this->paddle_snapshot, 'error.message'); return is_string($message) && $message !== '' ? $message : null; } public function linkPaddleIds(string $productId, string $priceId): void { $this->forceFill([ 'paddle_product_id' => $productId, 'paddle_price_id' => $priceId, 'paddle_sync_status' => 'linked', 'paddle_synced_at' => now(), ])->save(); } public function getActivatesImmediatelyAttribute(): bool { // Default: Pakete werden nach Kauf sofort freigeschaltet (digitale Dienstleistung). return true; } }