Private
Public Access
1
0

Fix Plaid link token error handling and show user-visible errors

Validate Plaid credentials are configured before calling the API,
and validate the returned link token before passing it to Plaid
Link JS. Show error snackbars for all bank sync failures instead
of only logging to console.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 16:29:16 -05:00
parent 6e80bcf98b
commit 849a392d14
2 changed files with 31 additions and 9 deletions
+27 -9
View File
@@ -242,10 +242,13 @@
</v-card>
</v-dialog>
<!-- Sync Results Snackbar -->
<!-- Snackbars -->
<v-snackbar v-model="showSyncResult" :timeout="5000" color="success">
{{ syncResultMessage }}
</v-snackbar>
<v-snackbar v-model="showError" :timeout="6000" color="error">
{{ errorMessage }}
</v-snackbar>
</v-tabs-window-item>
<!-- Tab 3: Sync History -->
@@ -341,6 +344,10 @@ const deleteLoading = ref(false)
const showSyncResult = ref(false)
const syncResultMessage = ref('')
// Error display
const showError = ref(false)
const errorMessage = ref('')
// Sync logs
const syncLogs = ref<SyncLogEntry[]>([])
const logsLoading = ref(false)
@@ -420,6 +427,13 @@ async function startPlaidLink() {
try {
const { data: linkData } = await bankSyncApi.createLinkToken()
if (!linkData.linkToken) {
errorMessage.value = 'Plaid returned an empty link token. Check your Plaid credentials in appsettings.json.'
showError.value = true
plaidLoading.value = false
return
}
// Dynamically load Plaid Link script
await loadPlaidScript()
@@ -431,8 +445,9 @@ async function startPlaidLink() {
connections.value.unshift(conn)
syncIntervals[conn.id] = conn.syncIntervalMinutes
openMappingDialog(conn)
} catch (err) {
console.error('Failed to complete Plaid link:', err)
} catch (err: any) {
errorMessage.value = err.response?.data?.error || 'Failed to complete Plaid link'
showError.value = true
}
},
onExit: () => {
@@ -440,8 +455,9 @@ async function startPlaidLink() {
}
})
plaid.open()
} catch (err) {
console.error('Failed to create link token:', err)
} catch (err: any) {
errorMessage.value = err.response?.data?.error || 'Failed to create Plaid link token. Check your Plaid credentials.'
showError.value = true
plaidLoading.value = false
}
}
@@ -469,8 +485,9 @@ async function connectSimpleFin() {
showSimpleFinDialog.value = false
simpleFinToken.value = ''
openMappingDialog(conn)
} catch (err) {
console.error('Failed to connect SimpleFIN:', err)
} catch (err: any) {
errorMessage.value = err.response?.data?.error || 'Failed to connect via SimpleFIN. Check your setup token.'
showError.value = true
} finally {
simpleFinLoading.value = false
}
@@ -540,8 +557,9 @@ async function syncNow(conn: SyncConnection) {
syncResultMessage.value = `Synced ${conn.institutionName}: ${data.transactionsImported} imported, ${data.transactionsSkipped} skipped`
showSyncResult.value = true
await loadConnections()
} catch (err) {
console.error('Sync failed:', err)
} catch (err: any) {
errorMessage.value = err.response?.data?.error || 'Sync failed'
showError.value = true
} finally {
syncingConnections[conn.id] = false
}
@@ -43,6 +43,10 @@ public class PlaidSyncProvider : IAccountSyncProvider
public async Task<PlaidLinkTokenResult> CreateLinkTokenAsync(string userId)
{
if (string.IsNullOrEmpty(_config.ClientId) || string.IsNullOrEmpty(_config.Secret))
throw new InvalidOperationException(
"Plaid credentials are not configured. Set BankSync:Plaid:ClientId and BankSync:Plaid:Secret in appsettings.json.");
var response = await _client.LinkTokenCreateAsync(new LinkTokenCreateRequest
{
ClientId = _config.ClientId,