feat(banking): let Wise credentials be updated (#588)
## What Makes **Wise** connections support credential updates, like every other API-key provider. Wise was the only API-key provider not wired into the credential-update path: `ConnectionController::validateProviderCredentials()` had no Wise arm, so it fell to the `Unsupported provider` default, and the update dialog (driven by the provider registry's `updatable` flag) rendered nothing. A Wise connection in an auth-error state therefore showed an **Update Credentials** button that opened an empty, unusable dialog. ## Fix - Add the Wise validation arm in `validateProviderCredentials` (`WiseClient::getProfiles()`). The validation rules and the `api_token` → column mapping already come from `BankingProvider`'s credential registry, so nothing else on the backend changes. - Drop the now-unused `updatable` flag from the connect-providers registry — every connect provider is updatable — and simplify `update-credentials-dialog` to render fields for any registry provider. ## Tests - Updating Wise credentials with a valid token succeeds (status back to active, token stored, sync dispatched). - `BankingProviderTest`: every API-key provider declares credential fields, guarding against adding a provider without an update path again. Follow-up to #581 (this branch is off the updated `main`).
This commit is contained in:
parent
f60e6d7035
commit
619ed0f1db
|
|
@ -17,6 +17,7 @@ use App\Services\Banking\BitpandaClient;
|
|||
use App\Services\Banking\CoinbaseClient;
|
||||
use App\Services\Banking\IndexaCapitalClient;
|
||||
use App\Services\Banking\InteractiveBrokersClient;
|
||||
use App\Services\Banking\WiseClient;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -119,6 +120,7 @@ class ConnectionController extends Controller
|
|||
BankingProvider::Bitpanda => (new BitpandaClient($validated['api_key']))->getCryptoWallets(),
|
||||
BankingProvider::Coinbase => (new CoinbaseClient($validated['api_key_name'], $validated['private_key']))->getAccounts(limit: 1),
|
||||
BankingProvider::InteractiveBrokers => (new InteractiveBrokersClient($validated['token'], $validated['query_id']))->fetchStatement(),
|
||||
BankingProvider::Wise => (new WiseClient($validated['api_token']))->getProfiles(),
|
||||
default => throw new \InvalidArgumentException('Unsupported provider for credential update.'),
|
||||
};
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
|
|
|
|||
|
|
@ -34,9 +34,6 @@ export function UpdateCredentialsDialog({
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const provider = connectProviderByKey(connection.provider);
|
||||
// Only providers the backend exposes a credential-update path for (Wise has none).
|
||||
const updatableProvider =
|
||||
provider && provider.updatable !== false ? provider : undefined;
|
||||
|
||||
const setCredential = useCallback((key: string, value: string) => {
|
||||
setCredentials((current) => ({ ...current, [key]: value }));
|
||||
|
|
@ -55,7 +52,7 @@ export function UpdateCredentialsDialog({
|
|||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!updatableProvider) {
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -64,14 +61,14 @@ export function UpdateCredentialsDialog({
|
|||
|
||||
router.patch(
|
||||
`/settings/connections/${connection.id}/credentials`,
|
||||
credentialPayload(updatableProvider, credentials),
|
||||
credentialPayload(provider, credentials),
|
||||
{
|
||||
onSuccess: () => {
|
||||
onOpenChange(false);
|
||||
resetState();
|
||||
},
|
||||
onError: (errors) => {
|
||||
const fieldError = updatableProvider.fields
|
||||
const fieldError = provider.fields
|
||||
.map((f) => errors[f.key])
|
||||
.find(Boolean);
|
||||
|
||||
|
|
@ -90,8 +87,8 @@ export function UpdateCredentialsDialog({
|
|||
);
|
||||
}
|
||||
|
||||
const isValid = updatableProvider
|
||||
? isProviderComplete(updatableProvider, credentials)
|
||||
const isValid = provider
|
||||
? isProviderComplete(provider, credentials)
|
||||
: false;
|
||||
|
||||
return (
|
||||
|
|
@ -108,9 +105,9 @@ export function UpdateCredentialsDialog({
|
|||
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
|
||||
{updatableProvider && (
|
||||
{provider && (
|
||||
<ProviderCredentialFields
|
||||
provider={updatableProvider}
|
||||
provider={provider}
|
||||
values={credentials}
|
||||
onChange={setCredential}
|
||||
idPrefix="update"
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ export type ConnectProvider = {
|
|||
onlyCountry?: string;
|
||||
/** Hidden unless this Pennant feature is active. */
|
||||
feature?: keyof Features;
|
||||
/** Backend exposes a credential-update path for this provider. */
|
||||
updatable?: boolean;
|
||||
/** Confirm-step header copy (i18n key). */
|
||||
headerDescription: string;
|
||||
/** Confirm-card copy (i18n key). */
|
||||
|
|
@ -190,7 +188,6 @@ export const CONNECT_PROVIDERS: ConnectProvider[] = [
|
|||
maximum_consent_validity: null,
|
||||
},
|
||||
endpoint: '/open-banking/wise/connect',
|
||||
updatable: false,
|
||||
headerDescription:
|
||||
'Enter your Wise Personal API token to connect your account.',
|
||||
cardDescription:
|
||||
|
|
|
|||
|
|
@ -327,6 +327,34 @@ test('users can update bitpanda credentials with valid api key', function () {
|
|||
expect($connection->api_token)->toBe('new-valid-bitpanda-key-12345');
|
||||
});
|
||||
|
||||
test('users can update wise credentials with valid api token', function () {
|
||||
Queue::fake();
|
||||
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->wise()->error()->create([
|
||||
'user_id' => $user->id,
|
||||
'error_message' => 'Authentication failed. Your credentials may have expired or been revoked.',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'api.wise.com/v1/profiles' => Http::response([
|
||||
['id' => 1, 'type' => 'personal', 'details' => []],
|
||||
]),
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->patch("/settings/connections/{$connection->id}/credentials", [
|
||||
'api_token' => 'new-valid-wise-token-12345',
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('success');
|
||||
|
||||
$connection->refresh();
|
||||
expect($connection->status)->toBe(BankingConnectionStatus::Active);
|
||||
expect($connection->error_message)->toBeNull();
|
||||
expect($connection->api_token)->toBe('new-valid-wise-token-12345');
|
||||
});
|
||||
|
||||
test('updating credentials with invalid token returns validation error', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->indexaCapital()->error()->create([
|
||||
|
|
|
|||
|
|
@ -55,3 +55,16 @@ it('maps credential inputs onto the encrypted connection columns', function () {
|
|||
|
||||
expect(BankingProvider::EnableBanking->credentialColumns([]))->toBe([]);
|
||||
});
|
||||
|
||||
it('defines credential fields for every API-key provider', function () {
|
||||
foreach (BankingProvider::cases() as $provider) {
|
||||
if (! $provider->usesApiKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Every API-key provider must declare its credential shape; this is
|
||||
// what drives connect/update rules, the column mapping and the update
|
||||
// dialog — so a new provider can't be added without an update path.
|
||||
expect($provider->credentialFields())->not->toBeEmpty();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue