Fix avatar wallet balance by serving JSON from /wallet/balance.
Deploy Ladill Care / deploy (push) Successful in 28s
Deploy Ladill Care / deploy (push) Successful in 28s
The dropdown was fetching care.wallet (account redirect HTML) instead of a billing balance API, so Alpine always fell back to "View wallet". Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,6 +4,6 @@ return [
|
||||
'api_url' => env('BILLING_API_URL', 'https://ladill.com/api/billing'),
|
||||
'api_key' => env('BILLING_API_KEY_CARE'),
|
||||
'service' => 'care',
|
||||
'wallet_balance_route' => 'care.wallet',
|
||||
'wallet_balance_route' => 'wallet.balance',
|
||||
'currency' => 'GHS',
|
||||
];
|
||||
|
||||
@@ -129,6 +129,11 @@ Alpine.data('afia', (config = {}) => ({
|
||||
Alpine.data('walletWidget', (config = {}) => ({
|
||||
display: '…',
|
||||
async load() {
|
||||
if (! config.url) {
|
||||
this.display = 'View wallet';
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(config.url, { headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
const data = await res.json();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Auth\SsoLoginController;
|
||||
use App\Http\Controllers\WalletBalanceController;
|
||||
use App\Http\Controllers\Care\AiController;
|
||||
use App\Http\Controllers\Care\AppointmentController;
|
||||
use App\Http\Controllers\Care\AppointmentMeetController;
|
||||
@@ -67,6 +68,8 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
|
||||
Route::post('/notifications/{id}/read', [NotificationController::class, 'markAsRead'])->name('notifications.mark-read');
|
||||
Route::post('/notifications/mark-all-read', [NotificationController::class, 'markAllAsRead'])->name('notifications.mark-all-read');
|
||||
|
||||
Route::get('/wallet/balance', [WalletBalanceController::class, 'balance'])->name('wallet.balance');
|
||||
|
||||
Route::get('/onboarding', [OnboardingController::class, 'show'])->name('care.onboarding.show');
|
||||
Route::post('/onboarding', [OnboardingController::class, 'store'])->name('care.onboarding.store');
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\EnsurePlatformSession;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WalletBalanceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||
config([
|
||||
'billing.api_url' => 'https://billing.test',
|
||||
'billing.api_key' => 'test-care-key',
|
||||
'billing.currency' => 'GHS',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_wallet_balance_endpoint_returns_formatted_json(): void
|
||||
{
|
||||
Http::fake([
|
||||
'billing.test/balance*' => Http::response(['balance_minor' => 12550]),
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'public_id' => 'care-wallet-user',
|
||||
'name' => 'Wallet User',
|
||||
'email' => 'wallet@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson(route('wallet.balance'))
|
||||
->assertOk()
|
||||
->assertJson([
|
||||
'available' => true,
|
||||
'balance_minor' => 12550,
|
||||
'currency' => 'GHS',
|
||||
'formatted' => 'GHS 125.50',
|
||||
]);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/balance')
|
||||
&& $request['user'] === 'care-wallet-user';
|
||||
});
|
||||
}
|
||||
|
||||
public function test_wallet_balance_endpoint_marks_unavailable_when_billing_fails(): void
|
||||
{
|
||||
Http::fake([
|
||||
'billing.test/balance*' => Http::response(['error' => 'unauthorized'], 401),
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'public_id' => 'care-wallet-user-2',
|
||||
'name' => 'Wallet User',
|
||||
'email' => 'wallet2@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson(route('wallet.balance'))
|
||||
->assertOk()
|
||||
->assertJson(['available' => false]);
|
||||
}
|
||||
|
||||
public function test_wallet_balance_route_is_not_the_account_redirect(): void
|
||||
{
|
||||
$this->assertSame('wallet.balance', config('billing.wallet_balance_route'));
|
||||
$this->assertTrue(\Illuminate\Support\Facades\Route::has('wallet.balance'));
|
||||
$this->assertNotSame(route('care.wallet'), route('wallet.balance'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user