CluPilotCloud/app/Actions/GrantAddon.php

71 lines
2.3 KiB
PHP

<?php
namespace App\Actions;
use App\Models\Operator;
use App\Models\Order;
use App\Models\Subscription;
use App\Models\SubscriptionAddon;
use App\Services\Billing\AddonCatalogue;
use Illuminate\Support\Carbon;
use RuntimeException;
/**
* Gives a customer a module without a payment — "ein Plugin schenken".
*
* Books through the same BookAddon a paid order uses, on a synthetic Order
* (no Stripe id, price set to what the customer actually pays) so the module
* ends up frozen on `subscription_addons` exactly like a real booking would.
*/
class GrantAddon
{
public function __construct(private BookAddon $bookAddon) {}
public function __invoke(
Subscription $subscription,
Operator $grantedBy,
string $addonKey,
int $priceCents,
int $quantity = 1,
?string $note = null,
?Carbon $until = null,
): SubscriptionAddon {
$cataloguePrice = app(AddonCatalogue::class)->priceCents($addonKey);
if ($cataloguePrice === null) {
throw new RuntimeException("Unknown add-on: {$addonKey}");
}
if ($priceCents < 0 || $priceCents > $cataloguePrice) {
throw new RuntimeException('A grant cannot charge more than the catalogue price.');
}
// Vestigial for a module — nothing is provisioned at a datacenter for
// it — but the column is not nullable, and the contract's own order
// already carries the right one.
$datacenter = $subscription->order?->datacenter ?? 'fsn';
$order = Order::create([
'customer_id' => $subscription->customer_id,
'plan' => $subscription->plan,
'type' => 'addon',
'addon_key' => $addonKey,
'amount_cents' => $priceCents,
'currency' => Subscription::catalogueCurrency(),
'datacenter' => $datacenter,
'stripe_event_id' => null,
'stripe_subscription_id' => null,
'status' => 'paid',
]);
return ($this->bookAddon)($subscription, $addonKey, $quantity, $order, [
'price_cents' => $priceCents,
'granted_by' => $grantedBy->id,
'granted_at' => now(),
'grant_note' => $note,
'granted_until' => $until,
'catalogue_price_cents' => $cataloguePrice,
]);
}
}