error('STRIPE_SECRET ist nicht gesetzt.'); return self::FAILURE; } $dryRun = (bool) $this->option('dry-run'); $stripe = Cashier::stripe(); foreach (Plan::query()->active()->get() as $plan) { if ($plan->stripe_product_id && $plan->stripe_price_id_monthly && $plan->stripe_price_id_yearly) { $this->line("{$plan->slug}: vollständig verknüpft, übersprungen."); continue; } if ($dryRun) { $this->line(sprintf( '[dry-run] %s: Produkt + Preise %s €/Monat, %s €/Jahr (netto) anlegen.', $plan->slug, number_format($plan->monthly_price_cents / 100, 2, ',', '.'), number_format($plan->yearly_price_cents / 100, 2, ',', '.'), )); continue; } $productId = $plan->stripe_product_id; if (! $productId) { $product = $stripe->products->create([ 'name' => $plan->name, 'metadata' => ['plan_slug' => $plan->slug], ]); $productId = $product->id; } $monthlyId = $plan->stripe_price_id_monthly ?: $this->createPrice($stripe, $productId, $plan, $plan->monthly_price_cents, 'month'); $yearlyId = $plan->stripe_price_id_yearly ?: $this->createPrice($stripe, $productId, $plan, $plan->yearly_price_cents, 'year'); $plan->update([ 'stripe_product_id' => $productId, 'stripe_price_id_monthly' => $monthlyId, 'stripe_price_id_yearly' => $yearlyId, ]); $this->info("{$plan->slug}: {$productId} · monatlich {$monthlyId} · jährlich {$yearlyId}"); } $this->syncSinglePmPrice($stripe, $dryRun); return self::SUCCESS; } /** * Legt das Einmal-Produkt „Einzel-Pressemitteilung" an (Netto-Preis aus * billing.single_pm_price_cents). Die Price-ID landet bewusst in der ENV * (STRIPE_PRICE_SINGLE_PM) statt in einer Tabelle — es gibt genau einen * solchen Preis, und ohne ENV bleibt der Checkout deaktiviert. */ private function syncSinglePmPrice(object $stripe, bool $dryRun): void { if (config('billing.single_pm_stripe_price_id')) { $this->line('einzel-pm: bereits verknüpft (STRIPE_PRICE_SINGLE_PM), übersprungen.'); return; } $amount = (int) config('billing.single_pm_price_cents'); if ($dryRun) { $this->line(sprintf( '[dry-run] einzel-pm: Einmal-Produkt + Preis %s € (netto) anlegen.', number_format($amount / 100, 2, ',', '.'), )); return; } $product = $stripe->products->create([ 'name' => 'Einzel-Pressemitteilung', 'metadata' => ['purpose' => 'single_pm'], ]); $price = $stripe->prices->create([ 'product' => $product->id, 'currency' => 'eur', 'unit_amount' => $amount, 'tax_behavior' => 'exclusive', 'metadata' => ['purpose' => 'single_pm'], ]); $this->info("einzel-pm: {$product->id} · {$price->id}"); $this->warn("Bitte in die .env eintragen: STRIPE_PRICE_SINGLE_PM={$price->id}"); } private function createPrice(object $stripe, string $productId, Plan $plan, int $unitAmount, string $interval): string { $price = $stripe->prices->create([ 'product' => $productId, 'currency' => strtolower($plan->currency), 'unit_amount' => $unitAmount, 'tax_behavior' => 'exclusive', 'recurring' => ['interval' => $interval], 'metadata' => ['plan_slug' => $plan->slug], ]); return $price->id; } }