Split SimpleFIN connections by institution
Group discovered SimpleFIN accounts by institution and create a separate SyncConnection per institution so each bank appears as its own card in the UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ export const bankSyncApi = {
|
|||||||
api.post<SyncConnection>('/bank-sync/plaid/complete-link', { publicToken }),
|
api.post<SyncConnection>('/bank-sync/plaid/complete-link', { publicToken }),
|
||||||
|
|
||||||
connectSimpleFin: (setupToken: string) =>
|
connectSimpleFin: (setupToken: string) =>
|
||||||
api.post<SyncConnection>('/bank-sync/simplefin/connect', { setupToken }),
|
api.post<SyncConnection[]>('/bank-sync/simplefin/connect', { setupToken }),
|
||||||
|
|
||||||
discoverAccounts: (connectionId: string) =>
|
discoverAccounts: (connectionId: string) =>
|
||||||
api.get<DiscoveredAccount[]>(`/bank-sync/connections/${connectionId}/discover-accounts`),
|
api.get<DiscoveredAccount[]>(`/bank-sync/connections/${connectionId}/discover-accounts`),
|
||||||
|
|||||||
@@ -728,12 +728,16 @@ function loadPlaidScript(): Promise<void> {
|
|||||||
async function connectSimpleFin() {
|
async function connectSimpleFin() {
|
||||||
simpleFinLoading.value = true
|
simpleFinLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { data: conn } = await bankSyncApi.connectSimpleFin(simpleFinToken.value)
|
const { data: newConnections } = await bankSyncApi.connectSimpleFin(simpleFinToken.value)
|
||||||
|
for (const conn of newConnections) {
|
||||||
connections.value.unshift(conn)
|
connections.value.unshift(conn)
|
||||||
syncIntervals[conn.id] = conn.syncIntervalMinutes
|
syncIntervals[conn.id] = conn.syncIntervalMinutes
|
||||||
|
}
|
||||||
showSimpleFinDialog.value = false
|
showSimpleFinDialog.value = false
|
||||||
simpleFinToken.value = ''
|
simpleFinToken.value = ''
|
||||||
openMappingDialog(conn)
|
if (newConnections.length > 0) {
|
||||||
|
openMappingDialog(newConnections[0])
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
errorMessage.value = err.response?.data?.error || 'Failed to connect via SimpleFIN. Check your setup token.'
|
errorMessage.value = err.response?.data?.error || 'Failed to connect via SimpleFIN. Check your setup token.'
|
||||||
showError.value = true
|
showError.value = true
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class BankSyncController : ControllerBase
|
|||||||
=> Ok(await _bankSyncService.CompletePlaidLinkAsync(UserId, request));
|
=> Ok(await _bankSyncService.CompletePlaidLinkAsync(UserId, request));
|
||||||
|
|
||||||
[HttpPost("simplefin/connect")]
|
[HttpPost("simplefin/connect")]
|
||||||
public async Task<ActionResult<SyncConnectionResponse>> ConnectSimpleFin(CreateSimpleFinConnectionRequest request)
|
public async Task<ActionResult<List<SyncConnectionResponse>>> ConnectSimpleFin(CreateSimpleFinConnectionRequest request)
|
||||||
=> Ok(await _bankSyncService.CreateSimpleFinConnectionAsync(UserId, request));
|
=> Ok(await _bankSyncService.CreateSimpleFinConnectionAsync(UserId, request));
|
||||||
|
|
||||||
[HttpGet("connections/{id}/discover-accounts")]
|
[HttpGet("connections/{id}/discover-accounts")]
|
||||||
|
|||||||
@@ -180,26 +180,32 @@ public class BankSyncService : IBankSyncService
|
|||||||
|
|
||||||
// --- SimpleFIN Flow ---
|
// --- SimpleFIN Flow ---
|
||||||
|
|
||||||
public async Task<SyncConnectionResponse> CreateSimpleFinConnectionAsync(Guid userId, CreateSimpleFinConnectionRequest request)
|
public async Task<List<SyncConnectionResponse>> CreateSimpleFinConnectionAsync(Guid userId, CreateSimpleFinConnectionRequest request)
|
||||||
{
|
{
|
||||||
var accessUrl = await _simpleFin.ExchangeSetupTokenAsync(request.SetupToken);
|
var accessUrl = await _simpleFin.ExchangeSetupTokenAsync(request.SetupToken);
|
||||||
|
|
||||||
var config = new Dictionary<string, string> { ["AccessUrl"] = accessUrl };
|
var config = new Dictionary<string, string> { ["AccessUrl"] = accessUrl };
|
||||||
var accounts = await _simpleFin.GetAccountsAsync(config);
|
var accounts = await _simpleFin.GetAccountsAsync(config);
|
||||||
var institutionName = accounts.FirstOrDefault()?.Institution ?? "SimpleFIN Connection";
|
var encryptedAccessUrl = _encryption.Encrypt(accessUrl);
|
||||||
|
|
||||||
|
var groupedByInstitution = accounts.GroupBy(a => a.Institution ?? "SimpleFIN Connection");
|
||||||
|
var connectionIds = new List<Guid>();
|
||||||
|
|
||||||
|
foreach (var group in groupedByInstitution)
|
||||||
|
{
|
||||||
var connection = new SyncConnection
|
var connection = new SyncConnection
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Provider = SyncProvider.SimpleFIN,
|
Provider = SyncProvider.SimpleFIN,
|
||||||
InstitutionName = institutionName,
|
InstitutionName = group.Key,
|
||||||
EncryptedAccessToken = _encryption.Encrypt(accessUrl)
|
EncryptedAccessToken = encryptedAccessUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
_db.SyncConnections.Add(connection);
|
_db.SyncConnections.Add(connection);
|
||||||
|
connectionIds.Add(connection.Id);
|
||||||
|
|
||||||
foreach (var account in accounts)
|
foreach (var account in group)
|
||||||
{
|
{
|
||||||
_db.LinkedAccounts.Add(new LinkedAccount
|
_db.LinkedAccounts.Add(new LinkedAccount
|
||||||
{
|
{
|
||||||
@@ -211,10 +217,17 @@ public class BankSyncService : IBankSyncService
|
|||||||
LastKnownBalance = account.Balance
|
LastKnownBalance = account.Balance
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
return await GetConnectionAsync(userId, connection.Id);
|
var results = new List<SyncConnectionResponse>();
|
||||||
|
foreach (var id in connectionIds)
|
||||||
|
{
|
||||||
|
results.Add(await GetConnectionAsync(userId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Account Linking ---
|
// --- Account Linking ---
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public interface IBankSyncService
|
|||||||
Task<SyncConnectionResponse> CompletePlaidLinkAsync(Guid userId, CreatePlaidConnectionRequest request);
|
Task<SyncConnectionResponse> CompletePlaidLinkAsync(Guid userId, CreatePlaidConnectionRequest request);
|
||||||
|
|
||||||
// SimpleFIN flow
|
// SimpleFIN flow
|
||||||
Task<SyncConnectionResponse> CreateSimpleFinConnectionAsync(Guid userId, CreateSimpleFinConnectionRequest request);
|
Task<List<SyncConnectionResponse>> CreateSimpleFinConnectionAsync(Guid userId, CreateSimpleFinConnectionRequest request);
|
||||||
|
|
||||||
// Account linking
|
// Account linking
|
||||||
Task<List<DiscoveredAccountResponse>> DiscoverAccountsAsync(Guid userId, Guid connectionId);
|
Task<List<DiscoveredAccountResponse>> DiscoverAccountsAsync(Guid userId, Guid connectionId);
|
||||||
|
|||||||
Reference in New Issue
Block a user