presseportale/app/Services/Billing/ExtraPmPurchaseService.php
Kevin Adametz 69411b4c87 Proof-PDF + Extra-PM-Verkauf ueber die Credit-Wallet (Decision-Update 2.1/2.3)
- ProofPdfService: Veroeffentlichungsnachweis 3 Credits pauschal, einmal pro
  PM (Zweitdownload kostenfrei); ProofPdfRenderer erzeugt das PDF on-demand
  aus vorhandenen PM-Daten (kein externer Renderer); GET-Download-Endpoint
  /admin/me/press-releases/{id}/nachweis hinter downloadProof-Policy + Kauf-Gate
- ExtraPmPurchaseService: tier-gestaffelter Nachkauf (19/15/12/10/8) aus der
  Wallet; verbucht als bezahlter SinglePurchase(ExtraPm) und greift damit in
  die bestehende Kontingent-/Slot-Mechanik. InsufficientCreditsException
  liefert das Mini-Checkout-Signal (required/available/shortfall)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:28:08 +00:00

64 lines
2 KiB
PHP

<?php
namespace App\Services\Billing;
use App\Enums\SinglePurchaseStatus;
use App\Enums\SinglePurchaseType;
use App\Models\SinglePurchase;
use App\Models\User;
use Illuminate\Support\Facades\DB;
/**
* Extra-PM-Nachkauf laut Decision-Update §2.1: die Brücke zwischen
* Tarif-Kontingent und Credit-Wallet. Ist das Monatskontingent voll, kann eine
* einzelne weitere PM über die Wallet nachgekauft werden — Preis tier-gestaffelt
* und zur Kaufzeit aus dem aktiven Abo abgeleitet (nicht statisch gespeichert).
*
* Der Kauf wird als bezahlter SinglePurchase (Typ ExtraPm) verbucht und greift
* damit in die bestehende Kontingent-/Slot-Mechanik
* ({@see User::pressReleaseQuotaRemaining()},
* PressReleaseService::consumePublishSlot()).
*/
class ExtraPmPurchaseService
{
public function __construct(
private readonly CreditPricingService $pricing,
private readonly CreditWalletService $wallet,
) {}
/**
* Tier-abgeleiteter Extra-PM-Preis für den User (Credits).
*/
public function priceCredits(User $user): int
{
return $this->pricing->extraPmCreditsFor($user);
}
/**
* Bucht eine Extra-PM aus der Wallet. Wirft InsufficientCreditsException,
* wenn das Guthaben nicht reicht (Signal für den Mini-Checkout).
*/
public function purchase(User $user): SinglePurchase
{
$credits = $this->priceCredits($user);
return DB::transaction(function () use ($user, $credits): SinglePurchase {
$purchase = $user->singlePurchases()->create([
'type' => SinglePurchaseType::ExtraPm,
'status' => SinglePurchaseStatus::Paid,
'price_cents' => $credits * 100,
'currency' => 'eur',
'paid_at' => now(),
]);
$this->wallet->debit(
$user,
$credits,
'Extra-PM ('.$user->currentTier()->label().')',
$purchase,
);
return $purchase;
});
}
}