Bill Meet streaming hourly and storage per GB like Transfer.
Deploy Ladill Meet / deploy (push) Successful in 54s
Deploy Ladill Meet / deploy (push) Successful in 54s
Wallet debits at session end, on recording/file storage, and via daily renewals with grace period before asset deletion. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,45 +5,16 @@ 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,
|
||||
protected MeetBillingService $billing,
|
||||
) {}
|
||||
|
||||
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(),
|
||||
]);
|
||||
$this->billing->chargeStreaming($session);
|
||||
}
|
||||
|
||||
public function recordAiMinutes(Organization $organization, int $minutes, string $sessionUuid): void
|
||||
@@ -56,8 +27,6 @@ class UsageMeteringService
|
||||
'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
|
||||
@@ -70,50 +39,34 @@ class UsageMeteringService
|
||||
$query->where('branch_id', $branchId);
|
||||
}
|
||||
|
||||
return $query
|
||||
$raw = $query
|
||||
->selectRaw('metric, SUM(quantity) as total')
|
||||
->groupBy('metric')
|
||||
->pluck('total', 'metric')
|
||||
->all();
|
||||
|
||||
$summary = [];
|
||||
foreach ($raw as $metric => $total) {
|
||||
if ($metric === 'streaming_hours') {
|
||||
$summary['streaming_hours'] = round(((int) $total) / 100, 2);
|
||||
} else {
|
||||
$summary[$metric] = (int) $total;
|
||||
}
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
protected function participantMinutes(Session $session): int
|
||||
public function estimatedCostGhs(array $summary): array
|
||||
{
|
||||
if (! $session->started_at) {
|
||||
return 0;
|
||||
}
|
||||
$streamingHours = (float) ($summary['streaming_hours'] ?? 0);
|
||||
$recordingBytes = (int) ($summary['recording_storage_bytes'] ?? 0);
|
||||
$fileBytes = (int) ($summary['file_storage_bytes'] ?? 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(),
|
||||
]);
|
||||
}
|
||||
return [
|
||||
'streaming' => round($streamingHours * $this->billing->pricePerHourGhs(), 2),
|
||||
'recording_storage' => round($this->billing->storageGb($recordingBytes) * $this->billing->pricePerGbMonthGhs(), 2),
|
||||
'file_storage' => round($this->billing->storageGb($fileBytes) * $this->billing->pricePerGbMonthGhs(), 2),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user