Files
ladill-meet/app/Services/Meet/UsageMeteringService.php
T
isaaccladandCursor 965fb992e9
Deploy Ladill Meet / deploy (push) Failing after 7s
Initial Ladill Meet release.
Phases 0–18: core meetings, webinar, breakouts, team chat, live streaming, town hall, billing, and Ladill Mail calendar wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 23:35:29 +00:00

120 lines
3.7 KiB
PHP

<?php
namespace App\Services\Meet;
use App\Models\Organization;
use App\Models\Session;
use App\Models\UsageRecord;
use App\Services\Billing\BillingClient;
use Illuminate\Support\Facades\Log;
class UsageMeteringService
{
public function __construct(
protected BillingClient $billing,
protected LicenseService $licenses,
) {}
public function recordSessionUsage(Session $session): void
{
$room = $session->room;
$organization = $room->organization;
$minutes = $this->participantMinutes($session);
UsageRecord::create([
'owner_ref' => $session->owner_ref,
'organization_id' => $organization->id,
'branch_id' => $room->branch_id,
'metric' => 'participant_minutes',
'quantity' => $minutes,
'metadata' => ['session_uuid' => $session->uuid, 'room_uuid' => $room->uuid],
'recorded_at' => now(),
]);
$this->maybeDebit($organization, $minutes, 'participant_minutes', $session->uuid);
}
public function recordRecordingStorage(Organization $organization, int $bytes, string $recordingUuid): void
{
UsageRecord::create([
'owner_ref' => $organization->owner_ref,
'organization_id' => $organization->id,
'metric' => 'recording_storage_bytes',
'quantity' => $bytes,
'metadata' => ['recording_uuid' => $recordingUuid],
'recorded_at' => now(),
]);
}
public function recordAiMinutes(Organization $organization, int $minutes, string $sessionUuid): void
{
UsageRecord::create([
'owner_ref' => $organization->owner_ref,
'organization_id' => $organization->id,
'metric' => 'ai_minutes',
'quantity' => $minutes,
'metadata' => ['session_uuid' => $sessionUuid],
'recorded_at' => now(),
]);
$this->maybeDebit($organization, $minutes, 'ai_minutes', $sessionUuid);
}
public function usageSummary(Organization $organization, ?int $branchId = null, int $days = 30): array
{
$since = now()->subDays($days);
$query = UsageRecord::where('organization_id', $organization->id)
->where('recorded_at', '>=', $since);
if ($branchId) {
$query->where('branch_id', $branchId);
}
return $query
->selectRaw('metric, SUM(quantity) as total')
->groupBy('metric')
->pluck('total', 'metric')
->all();
}
protected function participantMinutes(Session $session): int
{
if (! $session->started_at) {
return 0;
}
$ended = $session->ended_at ?? now();
$durationMinutes = max(1, (int) ceil($session->started_at->diffInSeconds($ended) / 60));
$participants = max(1, $session->participants()->count());
return $durationMinutes * $participants;
}
protected function maybeDebit(Organization $organization, int $quantity, string $metric, string $reference): void
{
$rates = config('meet.billing.rates', []);
$rateMinor = (int) ($rates[$metric] ?? 0);
if ($rateMinor <= 0) {
return;
}
$amountMinor = $quantity * $rateMinor;
try {
$this->billing->debit(
$organization->owner_ref,
$amountMinor,
'meet_'.$metric,
'meet-usage-'.$reference,
'Ladill Meet '.$metric,
);
} catch (\Throwable $e) {
Log::warning('Meet usage billing debit failed', [
'organization' => $organization->id,
'metric' => $metric,
'error' => $e->getMessage(),
]);
}
}
}