- 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>
92 lines
2.9 KiB
PHP
92 lines
2.9 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Billing;
|
||
|
||
use App\Enums\PressReleaseStatus;
|
||
use App\Enums\SinglePurchaseStatus;
|
||
use App\Enums\SinglePurchaseType;
|
||
use App\Exceptions\ProofPdfNotAvailableException;
|
||
use App\Models\PressRelease;
|
||
use App\Models\SinglePurchase;
|
||
use App\Models\User;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
/**
|
||
* Kauf des Veröffentlichungsnachweis-PDF laut Decision-Update §2.3: 3 Credits
|
||
* pauschal pro PM aus der Wallet. Der Kauf wird als bezahlter SinglePurchase
|
||
* (Typ ProofPdf) festgehalten — ein erneuter Download derselben PM ist danach
|
||
* kostenfrei (Impulskauf, keine Doppelbelastung).
|
||
*/
|
||
class ProofPdfService
|
||
{
|
||
public function __construct(
|
||
private readonly CreditPricingService $pricing,
|
||
private readonly CreditWalletService $wallet,
|
||
) {}
|
||
|
||
public function priceCredits(): int
|
||
{
|
||
return $this->pricing->proofPdfCredits();
|
||
}
|
||
|
||
/**
|
||
* Ein Nachweis ist nur für veröffentlichte Meldungen sinnvoll.
|
||
*/
|
||
public function isAvailable(PressRelease $pressRelease): bool
|
||
{
|
||
return $pressRelease->status === PressReleaseStatus::Published;
|
||
}
|
||
|
||
public function hasPurchased(User $user, PressRelease $pressRelease): bool
|
||
{
|
||
return $this->purchaseQuery($user, $pressRelease)->exists();
|
||
}
|
||
|
||
/**
|
||
* Kauft (oder liefert den bereits gekauften) Nachweis. Belastet die Wallet
|
||
* nur beim Erstkauf. Wirft ProofPdfNotAvailableException, wenn die PM nicht
|
||
* veröffentlicht ist, und InsufficientCreditsException ohne Deckung.
|
||
*/
|
||
public function purchase(User $user, PressRelease $pressRelease): SinglePurchase
|
||
{
|
||
if (! $this->isAvailable($pressRelease)) {
|
||
throw ProofPdfNotAvailableException::notPublished();
|
||
}
|
||
|
||
$existing = $this->purchaseQuery($user, $pressRelease)->first();
|
||
|
||
if ($existing) {
|
||
return $existing;
|
||
}
|
||
|
||
$credits = $this->priceCredits();
|
||
|
||
return DB::transaction(function () use ($user, $pressRelease, $credits): SinglePurchase {
|
||
$purchase = $user->singlePurchases()->create([
|
||
'type' => SinglePurchaseType::ProofPdf,
|
||
'status' => SinglePurchaseStatus::Paid,
|
||
'price_cents' => $credits * 100,
|
||
'currency' => 'eur',
|
||
'press_release_id' => $pressRelease->id,
|
||
'paid_at' => now(),
|
||
]);
|
||
|
||
$this->wallet->debit(
|
||
$user,
|
||
$credits,
|
||
"Veröffentlichungsnachweis – PM #{$pressRelease->id}",
|
||
$purchase,
|
||
);
|
||
|
||
return $purchase;
|
||
});
|
||
}
|
||
|
||
private function purchaseQuery(User $user, PressRelease $pressRelease)
|
||
{
|
||
return $user->singlePurchases()
|
||
->where('type', SinglePurchaseType::ProofPdf->value)
|
||
->where('status', SinglePurchaseStatus::Paid->value)
|
||
->where('press_release_id', $pressRelease->id);
|
||
}
|
||
}
|