/// <summary> /// Add an account to the database - Using OFX data specification /// </summary> /// <param name="account">Populated account object to add to database</param> /// <param name="financialInstitution">OFX financial institution to link in database. Will be created if necessary.</param> /// <param name="fiUser">User credentials for the financial institution</param> /// <returns>Created account</returns> public Account AddAccount(Account account, OFX.Types.FinancialInstitution financialInstitution, FinancialInstitutionUser fiUser) { // TODO: See if there's an existing FI or user with this info already // Look for existing FI entry with the same name FinancialInstitution fi; try { fi = DbContext.FinancialInstitutions.First(i => i.Name == financialInstitution.Name); } catch (Exception ex) { // Can result in InvalidOperationException or NullReferenceException depending on provider if (ex is InvalidOperationException || ex is NullReferenceException) { // FI Doesn't exist, add a new one fi = new FinancialInstitution { Name = financialInstitution.Name, OfxFinancialUnitId = financialInstitution.FinancialId, OfxOrganizationId = financialInstitution.OrganizationId, OfxUpdateUrl = financialInstitution.ServiceEndpoint.ToString() }; DbContext.FinancialInstitutions.Add(fi); } else { throw; // Unhandled } } // Look for existing user under this FI with same userId try { fiUser = fi.Users.First(u => u.UserId == fiUser.UserId && u.Password == fiUser.Password); } catch (Exception ex) { // Can result in InvalidOperationException or NullReferenceException depending on provider if (ex is InvalidOperationException || ex is NullReferenceException) { // User doesn't exist, add as new fi.Users.Add(fiUser); DbContext.FinancialInstitutionUsers.Add(fiUser); } else { throw; // Unhandled } } fiUser.Accounts.Add(account); DbContext.Accounts.Add(account); return(account); }
/// <summary> /// Retrieve the list of accounts from a financial institution using OFX and return all accounts that are not already present in the database /// </summary> /// <param name="financialInstitution">Financial institution to query</param> /// <param name="fiCredentials">Credentials for financial institution account</param> /// <returns>List of accounts</returns> public static async Task <IEnumerable <Account> > EnumerateNewAccounts( OFX.Types.FinancialInstitution financialInstitution, OFX.Types.Credentials fiCredentials) { using (BackgroundTaskTracker.BeginTask("Retrieving Account Information")) { var ofxService = new OFX2Service(financialInstitution, fiCredentials); var accountList = new List <Account>(); var ofxAccountList = await ofxService.ListAccounts().ConfigureAwait(false); // TODO: If ofxAccountList is null, raise a more detailed exception using (var dataService = new DataService()) { foreach (var ofxAccount in ofxAccountList) { // Convert from OFX account type to db account type and encode account id AccountType accountType = AccountType.Checking; string accountId = ""; if (ofxAccount.GetType() == typeof(OFX.Types.CheckingAccount)) { accountType = AccountType.Checking; accountId = ((OFX.Types.CheckingAccount)ofxAccount).RoutingId + ":" + ofxAccount.AccountId; } else if (ofxAccount.GetType() == typeof(OFX.Types.SavingsAccount)) { accountType = AccountType.Savings; accountId = ((OFX.Types.SavingsAccount)ofxAccount).RoutingId + ":" + ofxAccount.AccountId; } else if (ofxAccount.GetType() == typeof(OFX.Types.CreditCardAccount)) { accountType = AccountType.Creditcard; accountId = ofxAccount.AccountId; } // Look for a matching account in the database if (!dataService.GetAccountByFinancialId(accountId).Any()) { // This account is not already in the DB, add to new account list accountList.Add(new Account { AccountName = accountType + ":" + ofxAccount.AccountId.Substring(ofxAccount.AccountId.Length - 4), AccountType = accountType.ToString(), Currency = "USD", FiAccountId = accountId }); } } } // Return the finalized list of new accounts return(accountList); } }
/// <summary> /// Verify the provided account credentials. Raises an exception if validation fails /// </summary> /// <param name="financialInstitution">Financial institution to query</param> /// <param name="fiCredentials">Credentials for financial institution account</param> /// <returns>List of accounts</returns> public static async Task VerifyAccountCredentials(FinancialInstitution financialInstitution, OFX.Types.Credentials fiCredentials) { using (BackgroundTaskTracker.BeginTask("Verifying Credentials")) { // Convert from data model FI into OFX FI var ofxFinancialInstitition = new OFX.Types.FinancialInstitution(financialInstitution.Name, new Uri(financialInstitution.OfxUpdateUrl), financialInstitution.OfxOrganizationId, financialInstitution.OfxFinancialUnitId); var ofxService = new OFX2Service(ofxFinancialInstitition, fiCredentials); // Call list accounts to validate credentials await ofxService.ListAccounts().ConfigureAwait(false); } }
public void TestAddAccountFromFi() { // Mock setup for DataService var mockAccountSet = new Mock <DbSet <Account> >(); var mockFiSet = new Mock <DbSet <FinancialInstitution> >(); var mockFiUserSet = new Mock <DbSet <FinancialInstitutionUser> >(); var mockContext = new Mock <SoCashDbContext>(); mockContext.Setup(m => m.Accounts).Returns(mockAccountSet.Object); mockContext.Setup(m => m.FinancialInstitutions).Returns(mockFiSet.Object); mockContext.Setup(m => m.FinancialInstitutionUsers).Returns(mockFiUserSet.Object); // Account to add var newAccount = new Account { AccountName = "Test Account", AccountType = AccountType.Checking.ToString(), Currency = "USD" }; // Dummy FI var financialInstitution = new OFX.Types.FinancialInstitution("Test FI", new Uri("http://test.com/"), "Test Org ID", "Test Unit ID"); // Dummy FI User var financialInstitutionUser = new FinancialInstitutionUser { UserId = "Test User", Password = "******" }; // Add the account in a transaction using (var service = new DataService(mockContext.Object)) { service.AddAccount(newAccount, financialInstitution, financialInstitutionUser); } // Verify that the service added 1 account, 1 fi and 1 fi user mockAccountSet.Verify(m => m.Add(newAccount), Times.Once()); mockFiSet.Verify(m => m.Add(It.IsAny <FinancialInstitution>()), Times.Once()); mockFiUserSet.Verify(m => m.Add(financialInstitutionUser), Times.Once()); // Verify that the transaction ended properly mockContext.Verify(m => m.SaveChanges(), Times.Once()); }
public void TestAddAccountFromFi() { // Mock setup for DataService var mockAccountSet = new Mock<DbSet<Account>>(); var mockFiSet = new Mock<DbSet<FinancialInstitution>>(); var mockFiUserSet = new Mock<DbSet<FinancialInstitutionUser>>(); var mockContext = new Mock<SoCashDbContext>(); mockContext.Setup(m => m.Accounts).Returns(mockAccountSet.Object); mockContext.Setup(m => m.FinancialInstitutions).Returns(mockFiSet.Object); mockContext.Setup(m => m.FinancialInstitutionUsers).Returns(mockFiUserSet.Object); // Account to add var newAccount = new Account { AccountName = "Test Account", AccountType = AccountType.Checking.ToString(), Currency = "USD" }; // Dummy FI var financialInstitution = new OFX.Types.FinancialInstitution("Test FI", new Uri("http://test.com/"), "Test Org ID", "Test Unit ID"); // Dummy FI User var financialInstitutionUser = new FinancialInstitutionUser { UserId = "Test User", Password = "******" }; // Add the account in a transaction using (var service = new DataService(mockContext.Object)) { service.AddAccount(newAccount, financialInstitution, financialInstitutionUser); } // Verify that the service added 1 account, 1 fi and 1 fi user mockAccountSet.Verify(m => m.Add(newAccount), Times.Once()); mockFiSet.Verify(m => m.Add(It.IsAny<FinancialInstitution>()), Times.Once()); mockFiUserSet.Verify(m => m.Add(financialInstitutionUser), Times.Once()); // Verify that the transaction ended properly mockContext.Verify(m => m.SaveChanges(), Times.Once()); }
/// <summary> /// Download OFX transactions for an account and merge them into the account transaction list /// </summary> /// <param name="account">Account configured with financial institution login information</param> public static async Task DownloadOfxTransactionsForAccount(Account account) { using (BackgroundTaskTracker.BeginTask("Downloading statements")) { // Default retrieval parameters OFX2Service ofxService; OFX.Types.Account ofxAccount; var endTime = DateTimeOffset.Now; var startTime = new DateTimeOffset(new DateTime(1997, 1, 1)); using (var dataService = new DataService()) { // Retrieve matching account from DB - we need to get an entity in the current db session var updateAccount = dataService.GetAccountById(account.AccountId); // Form FI connection properties for transaction retrieval var fi = new OFX.Types.FinancialInstitution( updateAccount.FinancialInstitutionUser.FinancialInstitution.Name, new Uri(updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxUpdateUrl), updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxOrganizationId, updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxFinancialUnitId ); // Form credentials for login var credentials = new OFX.Types.Credentials( updateAccount.FinancialInstitutionUser.UserId, updateAccount.FinancialInstitutionUser.Password ); // Create service ofxService = new OFX2Service(fi, credentials); // Create proper account type for this account var accountType = (AccountType)account.AccountType; if (accountType == AccountType.Checking) { // Split routing and account id from combined string var accountIdComponents = account.FiAccountId.Split(':'); ofxAccount = new OFX.Types.CheckingAccount(accountIdComponents[0], accountIdComponents[1], "", true); } else if (accountType == AccountType.Savings) { // Split routing and account id from combined string var accountIdComponents = account.FiAccountId.Split(':'); ofxAccount = new OFX.Types.SavingsAccount(accountIdComponents[0], accountIdComponents[1], "", true); } else //if (accountType == AccountType.CREDITCARD) { ofxAccount = new OFX.Types.CreditCardAccount(account.FiAccountId, "", true); } // Use the start time of the latest transaction if we have any try { var lastTransaction = (from transaction in updateAccount.Transactions orderby transaction.Date descending select transaction).First(); startTime = new DateTimeOffset(lastTransaction.Date); } catch (InvalidOperationException) { // No transactions - ignore and use default start date. } } // Retrieve statement(s) (should only be one per protocol, but we can handle any number) try { var ofxStatments = await ofxService.GetStatement(ofxAccount, startTime, endTime).ConfigureAwait(false); foreach (var ofxStatement in ofxStatments) { MergeStatementTransactionsIntoAccount(account, ofxStatement); } } catch (OfxException ex) { MessageBox.Show(ex.Message, "Error"); } catch (InvalidOperationException ex) { MessageBox.Show("The data provided by the financial institution could not be parsed.", "Error"); } } }
/// <summary> /// Download OFX transactions for an account and merge them into the account transaction list /// </summary> /// <param name="account">Account configured with financial institution login information</param> public static async Task DownloadOfxTransactionsForAccount(Account account) { using (BackgroundTaskTracker.BeginTask("Downloading statements")) { // Default retrieval parameters OFX2Service ofxService; OFX.Types.Account ofxAccount; var endTime = DateTimeOffset.Now; var startTime = new DateTimeOffset(new DateTime(1997, 1, 1)); using (var dataService = new DataService()) { // Retrieve matching account from DB - we need to get an entity in the current db session var updateAccount = dataService.GetAccountById(account.AccountId); // Form FI connection properties for transaction retrieval var fi = new OFX.Types.FinancialInstitution( updateAccount.FinancialInstitutionUser.FinancialInstitution.Name, new Uri(updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxUpdateUrl), updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxOrganizationId, updateAccount.FinancialInstitutionUser.FinancialInstitution.OfxFinancialUnitId ); // Form credentials for login var credentials = new OFX.Types.Credentials( updateAccount.FinancialInstitutionUser.UserId, updateAccount.FinancialInstitutionUser.Password ); // Create service ofxService = new OFX2Service(fi, credentials); // Create proper account type for this account var accountType = (AccountType) account.AccountType; if (accountType == AccountType.Checking) { // Split routing and account id from combined string var accountIdComponents = account.FiAccountId.Split(':'); ofxAccount = new OFX.Types.CheckingAccount(accountIdComponents[0], accountIdComponents[1], "", true); } else if (accountType == AccountType.Savings) { // Split routing and account id from combined string var accountIdComponents = account.FiAccountId.Split(':'); ofxAccount = new OFX.Types.SavingsAccount(accountIdComponents[0], accountIdComponents[1], "", true); } else //if (accountType == AccountType.CREDITCARD) { ofxAccount = new OFX.Types.CreditCardAccount(account.FiAccountId, "", true); } // Use the start time of the latest transaction if we have any try { var lastTransaction = (from transaction in updateAccount.Transactions orderby transaction.Date descending select transaction).First(); startTime = new DateTimeOffset(lastTransaction.Date); } catch (InvalidOperationException) { // No transactions - ignore and use default start date. } } // Retrieve statement(s) (should only be one per protocol, but we can handle any number) var ofxStatments = await ofxService.GetStatement(ofxAccount, startTime, endTime).ConfigureAwait(false); if (!String.IsNullOrEmpty(ofxStatments.Item2) || !String.IsNullOrWhiteSpace(ofxStatments.Item2)) { MessageBox.Show(ofxStatments.Item2, "Error"); } foreach (var ofxStatement in ofxStatments.Item1) MergeStatementTransactionsIntoAccount(account, ofxStatement); } }