findOrFail($planId); $this->editingPlanId = $plan->id; $this->name = $plan->name; $this->monthlyPrice = number_format($plan->monthly_price_cents / 100, 2, ',', ''); $this->yearlyPrice = number_format($plan->yearly_price_cents / 100, 2, ',', ''); $this->quota = (string) $plan->press_release_quota; $this->dailyLimit = $plan->daily_limit === null ? '' : (string) $plan->daily_limit; $this->isActive = $plan->is_active; $this->sortOrder = (string) $plan->sort_order; $this->resetValidation(); Flux::modal('plan-edit')->show(); } public function save(StripePlanSyncService $stripeSync): void { // Deutsche Dezimal-Eingaben (49,00) für die numeric-Regel normalisieren. $this->monthlyPrice = str_replace(',', '.', trim($this->monthlyPrice)); $this->yearlyPrice = str_replace(',', '.', trim($this->yearlyPrice)); $validated = $this->validate( [ 'name' => ['required', 'string', 'max:120'], 'monthlyPrice' => ['required', 'numeric', 'min:0'], 'yearlyPrice' => ['required', 'numeric', 'min:0'], 'quota' => ['required', 'integer', 'min:0'], 'dailyLimit' => ['nullable', 'integer', 'min:1'], 'sortOrder' => ['required', 'integer', 'min:0'], ], attributes: [ 'name' => __('Name'), 'monthlyPrice' => __('Monatspreis'), 'yearlyPrice' => __('Jahrespreis'), 'quota' => __('PM-Kontingent'), 'dailyLimit' => __('Tageslimit'), 'sortOrder' => __('Sortierung'), ], ); $plan = Plan::query()->findOrFail($this->editingPlanId); $plan->fill([ 'name' => trim($validated['name']), 'monthly_price_cents' => $this->toCents($validated['monthlyPrice']), 'yearly_price_cents' => $this->toCents($validated['yearlyPrice']), 'press_release_quota' => (int) $validated['quota'], 'daily_limit' => $validated['dailyLimit'] === null || $validated['dailyLimit'] === '' ? null : (int) $validated['dailyLimit'], 'is_active' => $this->isActive, 'sort_order' => (int) $validated['sortOrder'], ]); $priceChanged = $plan->isDirty(['monthly_price_cents', 'yearly_price_cents']); $plan->save(); $stripeSync->syncAfterUpdate($plan, $plan->getChanges()); $this->savedMessage = $priceChanged ? __('Tarif „:name" gespeichert. Der neue Preis gilt sofort für neue Buchungen — Bestandsabos behalten ihren bisherigen Preis.', ['name' => $plan->name]) : __('Tarif „:name" gespeichert.', ['name' => $plan->name]); Flux::modal('plan-edit')->close(); } /** * Wandelt eine Preiseingabe (deutsches oder englisches Dezimalformat) * verlustfrei in Cent um. */ private function toCents(string $price): int { return (int) round(((float) str_replace(',', '.', $price)) * 100); } public function with(): array { return [ 'plans' => Plan::query()->orderBy('sort_order')->orderBy('id')->get(), 'singlePmPriceCents' => (int) config('billing.single_pm_price_cents'), 'singlePmPriceId' => config('billing.single_pm_stripe_price_id'), ]; } }; ?>
{{ __('Preise, Kontingente und Limits der Tarife pflegen. Änderungen erscheinen sofort auf der Buchungs-Seite und werden direkt nach Stripe synchronisiert.') }}
{{ __('Preis: :price € netto pro Veröffentlichung.', ['price' => number_format($singlePmPriceCents / 100, 2, ',', '.')]) }} @if ($singlePmPriceId) {{ __('Stripe verknüpft') }} @else {{ __('STRIPE_PRICE_SINGLE_PM fehlt') }} @endif
{{ __('Der Einzel-PM-Preis wird in config/billing.php bzw. über die ENV-Variable STRIPE_PRICE_SINGLE_PM gepflegt (ein fester Preis, kein Tarif). Eine Änderung erfordert „billing:sync-stripe-plans" mit geleerter ENV-Variable.') }}