From 256a929755300eb7c16f620cd313ac3ee7b33546 Mon Sep 17 00:00:00 2001 From: Alazar Kassahun Date: Mon, 11 May 2026 11:21:33 +0300 Subject: [PATCH] Bugfix: always store tokens in db before usage --- src/Providers/BoaUssd/BoaUssd.php | 34 ++++++++++++------- .../Feature/Providers/BoaUssd/BoaUssdTest.php | 15 ++++---- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Providers/BoaUssd/BoaUssd.php b/src/Providers/BoaUssd/BoaUssd.php index 2d83115..7f88a49 100644 --- a/src/Providers/BoaUssd/BoaUssd.php +++ b/src/Providers/BoaUssd/BoaUssd.php @@ -25,8 +25,6 @@ class BoaUssd extends Provider { private const ACCESS_TOKEN_CACHE_KEY = 'boa_ussd.access_token'; - private const REFRESH_TOKEN_CACHE_KEY = 'boa_ussd.refresh_token'; - private const TOKEN_LOCK_KEY = 'boa_ussd.token_refresh_lock'; public function __construct() @@ -99,7 +97,7 @@ private function getAccessToken(): string $storedToken = $this->storedAccessToken(); if ($storedToken !== null && $storedToken !== '') { - Cache::put(self::ACCESS_TOKEN_CACHE_KEY, $storedToken, now()->addMinutes(25)); + Cache::put(self::ACCESS_TOKEN_CACHE_KEY, $storedToken, 7200); return $storedToken; } @@ -129,25 +127,16 @@ private function refreshTokens(): string } $this->storeTokens($accessToken, $refreshToken); - Cache::put(self::ACCESS_TOKEN_CACHE_KEY, $accessToken, now()->addMinutes(25)); - Cache::put(self::REFRESH_TOKEN_CACHE_KEY, $refreshToken, now()->addDays(7)); + Cache::put(self::ACCESS_TOKEN_CACHE_KEY, $accessToken, 7200); return $accessToken; } private function resolveRefreshToken(): string { - $cachedToken = Cache::get(self::REFRESH_TOKEN_CACHE_KEY); - - if (is_string($cachedToken) && $cachedToken !== '') { - return $cachedToken; - } - $storedToken = $this->storedRefreshToken(); if ($storedToken !== null && $storedToken !== '') { - Cache::put(self::REFRESH_TOKEN_CACHE_KEY, $storedToken, now()->addDays(7)); - return $storedToken; } @@ -157,6 +146,8 @@ private function resolveRefreshToken(): string throw new RuntimeException('BOA USSD refresh token is not configured.'); } + $this->storeRefreshToken($seedToken); + return $seedToken; } @@ -197,6 +188,23 @@ private function storeTokens(string $accessToken, string $refreshToken): void ])->save(); } + private function storeRefreshToken(string $refreshToken): void + { + $record = BoAToken::query()->first(); + + if ($record === null) { + BoAToken::query()->create([ + 'refresh_token' => $refreshToken, + ]); + + return; + } + + $record->fill([ + 'refresh_token' => $refreshToken, + ])->save(); + } + private function storedAccessToken(): ?string { $accessToken = BoAToken::query()->value('access_token'); diff --git a/tests/Feature/Providers/BoaUssd/BoaUssdTest.php b/tests/Feature/Providers/BoaUssd/BoaUssdTest.php index 836dcf3..0d625dd 100644 --- a/tests/Feature/Providers/BoaUssd/BoaUssdTest.php +++ b/tests/Feature/Providers/BoaUssd/BoaUssdTest.php @@ -13,7 +13,6 @@ config()->set('app.key', 'base64:'.base64_encode(random_bytes(32))); Cache::forget('boa_ussd.access_token'); - Cache::forget('boa_ussd.refresh_token'); Cache::forget('boa_ussd.token_refresh_lock'); config()->set('moneyman.providers.boa_ussd.base_url', 'https://api.boa.test'); @@ -167,14 +166,11 @@ && $request['client_secret'] === config('moneyman.providers.boa_ussd.client_secret')); expect(Cache::get('boa_ussd.access_token'))->toBe('access-token-2'); - expect(Cache::get('boa_ussd.refresh_token'))->toBe('refresh-token-2'); expect((string) BoAToken::query()->value('access_token'))->toBe('access-token-2'); expect((string) BoAToken::query()->value('refresh_token'))->toBe('refresh-token-2'); }); -it('falls back to configured refresh token when cache is empty', function (): void { - Cache::forget('boa_ussd.refresh_token'); - +it('seeds configured refresh token into db when no refresh token is stored', function (): void { Http::fake([ config('moneyman.providers.boa_ussd.base_url').'/ussd/push/oauth2/token' => Http::response([ 'access_token' => 'access-token-3', @@ -201,6 +197,8 @@ Http::assertSent(fn ($request) => $request->url() === config('moneyman.providers.boa_ussd.base_url').'/ussd/push/oauth2/token' && $request['refresh_token'] === config('moneyman.providers.boa_ussd.refresh_token')); + + expect((string) BoAToken::query()->value('refresh_token'))->toBe('refresh-token-3'); }); it('uses stored access token when cache is empty', function (): void { @@ -234,7 +232,7 @@ }); it('refreshes token and retries once when initiate returns 401', function (): void { - Cache::put('boa_ussd.access_token', 'expired-access-token', now()->addMinutes(5)); + Cache::put('boa_ussd.access_token', 'expired-access-token', 7200); Http::fake([ config('moneyman.providers.boa_ussd.base_url').'/ussd/push/oauth2/token' => Http::response([ @@ -267,10 +265,13 @@ Http::assertSentCount(3); Http::assertSent(fn ($request) => $request->url() === config('moneyman.providers.boa_ussd.base_url').'/ussd/push' && $request->hasHeader('Authorization', 'Bearer new-access-token')); + expect(Cache::get('boa_ussd.access_token'))->toBe('new-access-token'); + expect((string) BoAToken::query()->value('access_token'))->toBe('new-access-token'); + expect((string) BoAToken::query()->value('refresh_token'))->toBe('new-refresh-token'); }); it('returns error verify response after retry when api keeps returning 401', function (): void { - Cache::put('boa_ussd.access_token', 'expired-access-token', now()->addMinutes(5)); + Cache::put('boa_ussd.access_token', 'expired-access-token', 7200); Http::fake([ config('moneyman.providers.boa_ussd.base_url').'/ussd/push/oauth2/token' => Http::response([