admin: Credits und freie PMs ohne Zahlung gutschreiben
Neues Panel "Guthaben & Kontingent" auf der Benutzer-Detailseite (users:manage): Credits gehen als Grant-Buchung ins Wallet-Ledger, freie Pressemitteilungen als bezahlte 0-€-Einzelkäufe, die das Buchungs-Gate erfüllen und beim Veröffentlichen regulär eingelöst werden — Stripe bleibt unberührt. Jede Gutschrift landet mit Vergeber und Grund als neuer Typ "Gutschrift" im Aktivitäts-Verlauf. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
80c5f03e6f
commit
3f4679fbb2
4 changed files with 322 additions and 0 deletions
|
|
@ -22,6 +22,7 @@ enum ActivityType: string
|
|||
case SubscriptionChange = 'subscription_change';
|
||||
case MailSent = 'mail_sent';
|
||||
case EmailUnsubscribe = 'email_unsubscribe';
|
||||
case AdminGrant = 'admin_grant';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
|
|
@ -39,6 +40,7 @@ enum ActivityType: string
|
|||
self::SubscriptionChange => 'Abo',
|
||||
self::MailSent => 'E-Mail',
|
||||
self::EmailUnsubscribe => 'Mail-Abmeldung',
|
||||
self::AdminGrant => 'Gutschrift',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ enum ActivityType: string
|
|||
self::LegalRequest, self::WithdrawalRequest, self::TerminationRequest, self::SubscriptionChange => 'red',
|
||||
self::MailSent => 'purple',
|
||||
self::EmailUnsubscribe => 'orange',
|
||||
self::AdminGrant => 'amber',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
89
app/Services/Billing/AdminGrantService.php
Normal file
89
app/Services/Billing/AdminGrantService.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Billing;
|
||||
|
||||
use App\Enums\ActivityType;
|
||||
use App\Enums\CreditTransactionType;
|
||||
use App\Enums\SinglePurchaseStatus;
|
||||
use App\Enums\SinglePurchaseType;
|
||||
use App\Models\CreditTransaction;
|
||||
use App\Models\User;
|
||||
use App\Services\ActivityLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Admin-Gutschriften ohne Zahlung (Admin ▸ Benutzer ▸ Guthaben & Kontingent):
|
||||
* Credits als Grant-Buchung ins Wallet-Ledger, freie Pressemitteilungen als
|
||||
* bezahlte 0-€-Einzelkäufe — beides greift in die reguläre Buchungs-/
|
||||
* Kontingent-Mechanik, ohne Stripe zu berühren, und wird im
|
||||
* Aktivitäts-Verlauf protokolliert.
|
||||
*/
|
||||
class AdminGrantService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CreditWalletService $wallet,
|
||||
private readonly ActivityLog $activityLog,
|
||||
) {}
|
||||
|
||||
public function grantCredits(User $user, int $credits, User $grantedBy, ?string $note = null): CreditTransaction
|
||||
{
|
||||
$description = 'Admin-Gutschrift durch '.$grantedBy->name.($note !== null && $note !== '' ? ': '.$note : '');
|
||||
|
||||
$transaction = $this->wallet->credit($user, $credits, CreditTransactionType::Grant, $description);
|
||||
|
||||
$this->activityLog->record(
|
||||
ActivityType::AdminGrant,
|
||||
sprintf(
|
||||
'%s hat %s (%s) %d Credits gutgeschrieben — ohne Zahlung.%s',
|
||||
$grantedBy->name,
|
||||
$user->name,
|
||||
$user->email,
|
||||
$credits,
|
||||
$note !== null && $note !== '' ? ' Grund: '.$note : '',
|
||||
),
|
||||
user: $user,
|
||||
subject: $transaction,
|
||||
meta: ['credits' => $credits, 'granted_by' => $grantedBy->id, 'note' => $note],
|
||||
);
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt dem User freie Pressemitteilungen gut: bezahlte Einzel-PM-Käufe
|
||||
* zu 0 € ({@see SinglePurchase::scopeGrantingSubmission()}), die das
|
||||
* Submit-Gate erfüllen und beim Veröffentlichen regulär eingelöst werden.
|
||||
*/
|
||||
public function grantFreePressReleases(User $user, int $count, User $grantedBy, ?string $note = null): void
|
||||
{
|
||||
if ($count < 1) {
|
||||
throw new \InvalidArgumentException('Anzahl muss positiv sein.');
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($user, $count): void {
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$user->singlePurchases()->create([
|
||||
'type' => SinglePurchaseType::SinglePm,
|
||||
'status' => SinglePurchaseStatus::Paid,
|
||||
'price_cents' => 0,
|
||||
'currency' => config('billing.default_currency'),
|
||||
'paid_at' => now(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->activityLog->record(
|
||||
ActivityType::AdminGrant,
|
||||
sprintf(
|
||||
'%s hat %s (%s) %d freie Pressemitteilung(en) gutgeschrieben — ohne Zahlung.%s',
|
||||
$grantedBy->name,
|
||||
$user->name,
|
||||
$user->email,
|
||||
$count,
|
||||
$note !== null && $note !== '' ? ' Grund: '.$note : '',
|
||||
),
|
||||
user: $user,
|
||||
meta: ['free_press_releases' => $count, 'granted_by' => $grantedBy->id, 'note' => $note],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue