T12: Depublikations-Checkout mit Tombstone (config-gated)
Kunden koennen eigene veroeffentlichte PMs kostenpflichtig aus der oeffentlichen Ansicht nehmen: Panel auf der PM-Detailseite (inkl. T1neu-Unternehmer-Checkbox und Doppelausweis netto/brutto), Stripe- Ad-hoc-Checkout wie Credit-Topups, Erfuellung im Webhook — die PM wechselt auf archived + depublished_at, die oeffentliche URL zeigt dauerhaft einen Tombstone (410 Gone, ohne Inhalte, mit ehrlichem Drittarchive-Hinweis). Doppelbuchungs-Bremse pro PM; Status-Log mit Quelle "depublication". Gate: ohne BILLING_DEPUBLICATION_PRICE_CENTS bleibt der Weg deaktiviert und /melden zeigt die neutrale Preisformulierung [EXTERN: Preis festlegen, O6]. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2a9be7a9af
commit
198be381d6
16 changed files with 522 additions and 7 deletions
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Enums\SinglePurchaseStatus;
|
||||
use App\Enums\SinglePurchaseType;
|
||||
use App\Models\BookingConfirmation;
|
||||
use App\Models\Plan;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\SinglePurchase;
|
||||
use App\Services\Billing\CreditTopupService;
|
||||
use App\Services\Billing\StripeCheckoutService;
|
||||
|
|
@ -102,6 +104,62 @@ class CheckoutController extends Controller
|
|||
return $this->checkout->forCreditTopup($request->user(), $topup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depublikation einer eigenen, veröffentlichten PM (T12, Meldeverfahren
|
||||
* Weg 3). Config-gated: ohne konfigurierten Preis ist der Weg
|
||||
* deaktiviert. Die Entfernung selbst übernimmt der Webhook nach
|
||||
* Zahlungseingang (Tombstone bleibt öffentlich sichtbar).
|
||||
*/
|
||||
public function depublication(Request $request, int $pressReleaseId): Checkout|RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$priceCents = (int) config('billing.depublication_price_cents');
|
||||
|
||||
if ($priceCents <= 0) {
|
||||
return $this->backToBookings(__('Die Depublikation ist derzeit nicht online buchbar. Bitte wenden Sie sich an :email.', ['email' => (string) config('legal.contact_email')]));
|
||||
}
|
||||
|
||||
$pressRelease = PressRelease::query()
|
||||
->accessibleBy($user)
|
||||
->whereKey($pressReleaseId)
|
||||
->firstOrFail();
|
||||
|
||||
if ($pressRelease->status !== PressReleaseStatus::Published) {
|
||||
return $this->backToBookings(__('Nur veröffentlichte Pressemitteilungen können depubliziert werden.'));
|
||||
}
|
||||
|
||||
if (! $user->hasCompleteBillingAddress()) {
|
||||
return $this->backToProfile();
|
||||
}
|
||||
|
||||
if ($redirect = $this->requireEntrepreneurConfirmation($request)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
// Doppelbuchungs-Bremse: eine offene oder bereits bezahlte
|
||||
// Depublikation zu dieser PM reicht.
|
||||
$alreadyRequested = SinglePurchase::query()
|
||||
->where('press_release_id', $pressRelease->id)
|
||||
->where('type', SinglePurchaseType::Depublication->value)
|
||||
->whereIn('status', [SinglePurchaseStatus::Pending->value, SinglePurchaseStatus::Paid->value])
|
||||
->exists();
|
||||
|
||||
if ($alreadyRequested) {
|
||||
return $this->backToBookings(__('Für diese Pressemitteilung liegt bereits ein Depublikations-Vorgang vor.'));
|
||||
}
|
||||
|
||||
$purchase = SinglePurchase::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'type' => SinglePurchaseType::Depublication->value,
|
||||
'status' => SinglePurchaseStatus::Pending->value,
|
||||
'price_cents' => $priceCents,
|
||||
'currency' => config('billing.default_currency'),
|
||||
'press_release_id' => $pressRelease->id,
|
||||
]);
|
||||
|
||||
return $this->checkout->forDepublication($user, $purchase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stripe Billing Portal: Selbstverwaltung des Abos (Zahlungsmethode,
|
||||
* Rechnungen, Kündigung). Nur mit aktivem Abo sinnvoll.
|
||||
|
|
|
|||
|
|
@ -6,13 +6,21 @@ use App\Http\Controllers\Controller;
|
|||
use App\Services\Web\ReleaseDetailPageData;
|
||||
use App\Support\WebPortalContext;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ReleaseDetailController extends Controller
|
||||
{
|
||||
// $edition stammt aus dem Gruppen-Prefix {edition} und steht positionsbedingt
|
||||
// vor {slug}; es wird hier nicht gebraucht, muss aber in der Signatur stehen.
|
||||
public function __invoke(string $edition, string $slug, ReleaseDetailPageData $pageData): View
|
||||
public function __invoke(string $edition, string $slug, ReleaseDetailPageData $pageData): View|Response
|
||||
{
|
||||
return view('web.release-detail', $pageData->build(WebPortalContext::fromRequest(), $slug));
|
||||
$data = $pageData->build(WebPortalContext::fromRequest(), $slug);
|
||||
|
||||
// T12: depublizierte PM → Tombstone mit 410 Gone (ohne Inhalte).
|
||||
if (isset($data['tombstone'])) {
|
||||
return response()->view('web.release-tombstone', [], 410);
|
||||
}
|
||||
|
||||
return view('web.release-detail', $data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue