Fix join 500, wallet dropdown balance, and launcher Meet entry.
Deploy Ladill Meet / deploy (push) Successful in 1m35s

Handle billing API failures gracefully on session start, add wallet.balance JSON endpoint, and sync Meet into the app launcher.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 16:50:02 +00:00
co-authored by Cursor
parent 59b59cb70e
commit d19c131428
6 changed files with 72 additions and 6 deletions
@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers;
use App\Services\Billing\BillingClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
/**
* Lightweight wallet balance for the avatar dropdown widget.
*/
class WalletBalanceController extends Controller
{
public function balance(Request $request, BillingClient $billing): JsonResponse
{
$publicId = (string) $request->user()->public_id;
$minor = Cache::remember("wallet_balance:{$publicId}", now()->addSeconds(30), function () use ($billing, $publicId) {
try {
return $billing->balanceMinor($publicId);
} catch (\Throwable) {
return null;
}
});
if ($minor === null) {
return response()->json(['available' => false]);
}
$currency = (string) config('billing.currency', 'GHS');
return response()->json([
'available' => true,
'balance_minor' => $minor,
'currency' => $currency,
'formatted' => $currency.' '.number_format($minor / 100, 2),
]);
}
}
+25 -3
View File
@@ -83,8 +83,14 @@ class MeetBillingService
max(1, (int) config('meet.billing.minimum_streaming_hours', 1))
);
$affordable = $this->canAffordSafely($organization->owner_ref, $amountMinor);
if ($affordable === null) {
abort(503, 'Billing is temporarily unavailable. Please try again shortly.');
}
abort_unless(
$this->billing->canAfford($organization->owner_ref, $amountMinor),
$affordable,
402,
sprintf(
'Insufficient wallet balance. Meetings are billed at GHS %.2f per hour — top up your Ladill wallet to start.',
@@ -198,7 +204,7 @@ class MeetBillingService
$periodEnd = $this->nextPeriodEnd($base);
$reference = sprintf('meet-%s-renewal:%s:through:%s', $type, $uuid, $periodEnd->format('Y-m-d'));
if (! $this->billing->canAfford($organization->owner_ref, $amountMinor)) {
if (! ($this->canAffordSafely($organization->owner_ref, $amountMinor) ?? false)) {
return false;
}
@@ -301,7 +307,7 @@ class MeetBillingService
return true;
}
if (! $this->billing->canAfford($organization->owner_ref, $amountMinor)) {
if (! ($this->canAffordSafely($organization->owner_ref, $amountMinor) ?? false)) {
return false;
}
@@ -357,6 +363,22 @@ class MeetBillingService
]);
}
/** @return bool|null null when the billing API is unreachable */
protected function canAffordSafely(string $ownerRef, int $amountMinor): ?bool
{
try {
return $this->billing->canAfford($ownerRef, $amountMinor);
} catch (\Throwable $e) {
Log::warning('Meet billing can-afford check failed', [
'owner_ref' => $ownerRef,
'amount_minor' => $amountMinor,
'error' => $e->getMessage(),
]);
return null;
}
}
protected function debit(
string $ownerRef,
int $amountMinor,
+2 -2
View File
@@ -67,11 +67,11 @@ class SecurityPolicyService
/**
* @return array<string, mixed>
*/
protected function mergedPolicy(Organization $org, Room $room): array
protected function mergedPolicy(?Organization $org, Room $room): array
{
return array_merge(
config('meet.security_defaults'),
$org->security_policy ?? [],
$org?->security_policy ?? [],
$room->settings['security'] ?? [],
);
}