diff --git a/EBanking.DataAccess/DatabaseAccess.cs b/EBanking.DataAccess/DatabaseAccess.cs index 0ae0826..909c395 100644 --- a/EBanking.DataAccess/DatabaseAccess.cs +++ b/EBanking.DataAccess/DatabaseAccess.cs @@ -1,7 +1,23 @@ +using System.Configuration; + namespace EBanking.DataAccess { - public class DatabaseAccess + public static class DatabaseAccess { - private const string _connectionString = @"Data Source = .;Initial Catalog=eBanking;Integrated Security=True"; + private const string ConnectionStringName = "EBankingDb"; + + private static readonly Lazy _connectionString = new(() => + { + var configString = ConfigurationManager.ConnectionStrings[ConnectionStringName]?.ConnectionString; + if (string.IsNullOrWhiteSpace(configString)) + { + throw new InvalidOperationException( + $"Connection string '{ConnectionStringName}' was not found in configuration. " + + "Ensure App.config defines ."); + } + return configString; + }); + + public static string ConnectionString => _connectionString.Value; } } diff --git a/EBanking.DataAccess/EBanking.DataAccess.csproj b/EBanking.DataAccess/EBanking.DataAccess.csproj index ac029dc..9a3a01d 100644 --- a/EBanking.DataAccess/EBanking.DataAccess.csproj +++ b/EBanking.DataAccess/EBanking.DataAccess.csproj @@ -7,6 +7,8 @@ + + diff --git a/EBanking.DataAccess/Models/Account.cs b/EBanking.DataAccess/Models/Account.cs index aa1670e..9850938 100644 --- a/EBanking.DataAccess/Models/Account.cs +++ b/EBanking.DataAccess/Models/Account.cs @@ -4,7 +4,7 @@ public class Account { public string AccountNumber { get; set; } public int UserId { get; set; } - public double Balance { get; set; } + public decimal Balance { get; set; } public string Type { get; set; } public string Currency { get; set; } public DateTime DateCreated { get; set; } diff --git a/EBanking.DataAccess/Models/CurrencyExchange.cs b/EBanking.DataAccess/Models/CurrencyExchange.cs index fec6d89..a30c62a 100644 --- a/EBanking.DataAccess/Models/CurrencyExchange.cs +++ b/EBanking.DataAccess/Models/CurrencyExchange.cs @@ -3,6 +3,6 @@ namespace EBanking.DataAccess.Models public class CurrencyExchange { public string Currency { get; set; } - public double Value { get; set; } + public decimal Value { get; set; } } } diff --git a/EBanking.DataAccess/Models/Transaction.cs b/EBanking.DataAccess/Models/Transaction.cs index d52ee74..ab385de 100644 --- a/EBanking.DataAccess/Models/Transaction.cs +++ b/EBanking.DataAccess/Models/Transaction.cs @@ -5,8 +5,8 @@ public class Transaction public int TransactionId { get; set; } public string AccountNumber { get; set; } public string CardNumber { get; set; } - public double Amount { get; set; } - public double BalanceAfterTransaction { get; set; } + public decimal Amount { get; set; } + public decimal BalanceAfterTransaction { get; set; } public DateTime Date { get; set; } public string SecondaryPartyName { get; set; } public string SecondaryPartyAccountNumber { get; set; } diff --git a/EBanking.DataAccess/Repositories/IAccountRepository.cs b/EBanking.DataAccess/Repositories/IAccountRepository.cs index bd8c400..36520c2 100644 --- a/EBanking.DataAccess/Repositories/IAccountRepository.cs +++ b/EBanking.DataAccess/Repositories/IAccountRepository.cs @@ -6,8 +6,8 @@ public interface IAccountRepository { void CreateAccount(Account model); List GetAccountsByUserId(int userId); - Task GetAccountByAccountNumber(string accountNumber); - void UpdateBalance(double balance, string accountNumber); + Account GetAccountByAccountNumber(string accountNumber); + void UpdateBalance(decimal balance, string accountNumber); bool IsValidAccount(string accountNumber); } } diff --git a/EBanking.DataAccess/Repositories/ITransactionRepository.cs b/EBanking.DataAccess/Repositories/ITransactionRepository.cs index 4739b00..ee8810e 100644 --- a/EBanking.DataAccess/Repositories/ITransactionRepository.cs +++ b/EBanking.DataAccess/Repositories/ITransactionRepository.cs @@ -6,5 +6,19 @@ public interface ITransactionRepository { int CreateTransaction(Transaction transaction); List GetTransactionsByAccountNumber(string accountNumber); + void ExecuteTransfer( + string payerAccountNumber, + string recipientAccountNumber, + decimal amount, + string recipientName, + string payerFullName, + DateTime occurredAt); + void ExecuteExchange( + string sourceAccountNumber, + string destinationAccountNumber, + decimal sourceAmount, + decimal destinationAmount, + string userFullName, + DateTime occurredAt); } } diff --git a/EBanking.DataAccess/Repositories/Implementation/AccountRepository.cs b/EBanking.DataAccess/Repositories/Implementation/AccountRepository.cs index 34c996a..9b5a665 100644 --- a/EBanking.DataAccess/Repositories/Implementation/AccountRepository.cs +++ b/EBanking.DataAccess/Repositories/Implementation/AccountRepository.cs @@ -1,15 +1,14 @@ using EBanking.DataAccess.Models; +using System.Data; using System.Data.SqlClient; namespace EBanking.DataAccess.Repositories.Implementation { public class AccountRepository : IAccountRepository { - private const string _connectionString = @"Data Source = .\SQLEXPRESS;Initial Catalog=EBankingSystem;Integrated Security=True"; - public void CreateAccount(Account model) { - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -19,21 +18,21 @@ public void CreateAccount(Account model) "VALUES(@accountNumber, @userId, @balance, @currency, @type, @dateCreated)"; sqlCommand.Parameters.AddWithValue("@accountNumber", model.AccountNumber); sqlCommand.Parameters.AddWithValue("@userId", model.UserId); - sqlCommand.Parameters.AddWithValue("@balance", model.Balance); + sqlCommand.Parameters.Add(new SqlParameter("@balance", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = model.Balance }); sqlCommand.Parameters.AddWithValue("@currency", model.Currency); sqlCommand.Parameters.AddWithValue("@type", model.Type); sqlCommand.Parameters.AddWithValue("@dateCreated", model.DateCreated); - sqlCommand.ExecuteScalar(); + sqlCommand.ExecuteNonQuery(); } } } - public async Task GetAccountByAccountNumber(string accountNumber) + public Account GetAccountByAccountNumber(string accountNumber) { Account account = new Account(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -48,7 +47,7 @@ public async Task GetAccountByAccountNumber(string accountNumber) { account.UserId = (int)reader["userId"]; account.AccountNumber = reader["accountNumber"] as string; - account.Balance = decimal.ToDouble((decimal)reader["balance"]); + account.Balance = (decimal)reader["balance"]; account.DateCreated = (DateTime)reader["dateCreated"]; account.Type = reader["type"] as string; account.Currency = reader["currency"] as string; @@ -64,7 +63,7 @@ public List GetAccountsByUserId(int userId) { List accounts = new List(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -81,7 +80,7 @@ public List GetAccountsByUserId(int userId) { UserId = (int)reader["userId"], AccountNumber = reader["accountNumber"] as string, - Balance = decimal.ToDouble((decimal)reader["balance"]), + Balance = (decimal)reader["balance"], DateCreated = (DateTime)reader["dateCreated"], Type = reader["type"] as string, Currency = reader["currency"] as string, @@ -103,37 +102,33 @@ public List GetAccountsByUserId(int userId) public bool IsValidAccount(string accountNumber) { - object accountId; - - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { - sqlCommand.CommandText = "SELECT * FROM Account WHERE accountNumber = @accountNumber"; + sqlCommand.CommandText = "SELECT 1 FROM Account WHERE accountNumber = @accountNumber"; sqlCommand.Parameters.AddWithValue("@accountNumber", accountNumber); - accountId = sqlCommand.ExecuteScalar(); + return sqlCommand.ExecuteScalar() is not null; } } - - return accountId as string is not null; } - public void UpdateBalance(double balance, string accountNumber) + public void UpdateBalance(decimal balance, string accountNumber) { - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { sqlCommand.CommandText = "UPDATE Account SET balance = @balance WHERE accountNumber = @accountNumber"; - sqlCommand.Parameters.AddWithValue("@balance", balance); + sqlCommand.Parameters.Add(new SqlParameter("@balance", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = balance }); sqlCommand.Parameters.AddWithValue("@accountNumber", accountNumber); - sqlCommand.ExecuteScalar(); + sqlCommand.ExecuteNonQuery(); } } } diff --git a/EBanking.DataAccess/Repositories/Implementation/CurrencyExchangeRepository.cs b/EBanking.DataAccess/Repositories/Implementation/CurrencyExchangeRepository.cs index 962db0f..ae82597 100644 --- a/EBanking.DataAccess/Repositories/Implementation/CurrencyExchangeRepository.cs +++ b/EBanking.DataAccess/Repositories/Implementation/CurrencyExchangeRepository.cs @@ -5,13 +5,11 @@ namespace EBanking.DataAccess.Repositories.Implementation { public class CurrencyExchangeRepository : ICurrencyExchangeRepository { - private const string _connectionString = @"Data Source = .\SQLEXPRESS;Initial Catalog=EBankingSystem;Integrated Security=True"; - public List GetExchangeRatesByCurrency(string currency) { List exchangeRates = new List(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -26,7 +24,7 @@ public List GetExchangeRatesByCurrency(string currency) exchangeRates.Add(new CurrencyExchange { Currency = reader["currency"] as string, - Value = decimal.ToDouble((decimal)reader["value"]), + Value = (decimal)reader["value"], }); } } diff --git a/EBanking.DataAccess/Repositories/Implementation/TransactionRepository.cs b/EBanking.DataAccess/Repositories/Implementation/TransactionRepository.cs index 43dd6da..a9f67cc 100644 --- a/EBanking.DataAccess/Repositories/Implementation/TransactionRepository.cs +++ b/EBanking.DataAccess/Repositories/Implementation/TransactionRepository.cs @@ -1,15 +1,14 @@ using EBanking.DataAccess.Models; +using System.Data; using System.Data.SqlClient; namespace EBanking.DataAccess.Repositories.Implementation { public class TransactionRepository : ITransactionRepository { - private const string _connectionString = @"Data Source = .\SQLEXPRESS;Initial Catalog=EBankingSystem;Integrated Security=True"; - public int CreateTransaction(Transaction transaction) { - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -19,8 +18,8 @@ public int CreateTransaction(Transaction transaction) " VALUES (@accountNumber, @cardNumber, @amount, @balanceAfterTransaction, @date, @secondaryPartyName, @secondaryPartyAccountNumber)"; sqlCommand.Parameters.AddWithValue("@accountNumber", transaction.AccountNumber); sqlCommand.Parameters.AddWithValue("@cardNumber", (object)transaction.CardNumber ?? DBNull.Value); - sqlCommand.Parameters.AddWithValue("@amount", transaction.Amount); - sqlCommand.Parameters.AddWithValue("@balanceAfterTransaction", transaction.BalanceAfterTransaction); + sqlCommand.Parameters.Add(new SqlParameter("@amount", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = transaction.Amount }); + sqlCommand.Parameters.Add(new SqlParameter("@balanceAfterTransaction", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = transaction.BalanceAfterTransaction }); sqlCommand.Parameters.AddWithValue("@date", transaction.Date); sqlCommand.Parameters.AddWithValue("@secondaryPartyName", (object)transaction.SecondaryPartyName ?? DBNull.Value); sqlCommand.Parameters.AddWithValue("@secondaryPartyAccountNumber", (object)transaction.SecondaryPartyAccountNumber ?? DBNull.Value); @@ -30,11 +29,163 @@ public int CreateTransaction(Transaction transaction) } } + public void ExecuteTransfer( + string payerAccountNumber, + string recipientAccountNumber, + decimal amount, + string recipientName, + string payerFullName, + DateTime occurredAt) + { + using var connection = new SqlConnection(DatabaseAccess.ConnectionString); + connection.Open(); + using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); + try + { + decimal payerBalance = ReadBalanceWithUpdLock(connection, transaction, payerAccountNumber) + ?? throw new InvalidOperationException("Payer account not found."); + + decimal recipientBalance = ReadBalanceWithUpdLock(connection, transaction, recipientAccountNumber) + ?? throw new InvalidOperationException("Recipient account not found."); + + if (payerBalance < amount) + { + throw new InvalidOperationException("Insufficient funds."); + } + + UpdateBalanceByDelta(connection, transaction, payerAccountNumber, -amount); + UpdateBalanceByDelta(connection, transaction, recipientAccountNumber, amount); + + InsertTransactionRow( + connection, transaction, + accountNumber: recipientAccountNumber, + amount: amount, + balanceAfter: recipientBalance + amount, + date: occurredAt, + secondaryPartyName: payerFullName, + secondaryPartyAccountNumber: payerAccountNumber); + + InsertTransactionRow( + connection, transaction, + accountNumber: payerAccountNumber, + amount: amount, + balanceAfter: payerBalance - amount, + date: occurredAt, + secondaryPartyName: recipientName, + secondaryPartyAccountNumber: recipientAccountNumber); + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + + public void ExecuteExchange( + string sourceAccountNumber, + string destinationAccountNumber, + decimal sourceAmount, + decimal destinationAmount, + string userFullName, + DateTime occurredAt) + { + using var connection = new SqlConnection(DatabaseAccess.ConnectionString); + connection.Open(); + using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); + try + { + decimal sourceBalance = ReadBalanceWithUpdLock(connection, transaction, sourceAccountNumber) + ?? throw new InvalidOperationException("Source account not found."); + + decimal destinationBalance = ReadBalanceWithUpdLock(connection, transaction, destinationAccountNumber) + ?? throw new InvalidOperationException("Destination account not found."); + + if (sourceBalance < sourceAmount) + { + throw new InvalidOperationException("Insufficient funds."); + } + + UpdateBalanceByDelta(connection, transaction, sourceAccountNumber, -sourceAmount); + UpdateBalanceByDelta(connection, transaction, destinationAccountNumber, destinationAmount); + + InsertTransactionRow( + connection, transaction, + accountNumber: sourceAccountNumber, + amount: sourceAmount, + balanceAfter: sourceBalance - sourceAmount, + date: occurredAt, + secondaryPartyName: userFullName, + secondaryPartyAccountNumber: destinationAccountNumber); + + InsertTransactionRow( + connection, transaction, + accountNumber: destinationAccountNumber, + amount: destinationAmount, + balanceAfter: destinationBalance + destinationAmount, + date: occurredAt, + secondaryPartyName: userFullName, + secondaryPartyAccountNumber: sourceAccountNumber); + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + + private static decimal? ReadBalanceWithUpdLock(SqlConnection conn, SqlTransaction tx, string accountNumber) + { + using var cmd = conn.CreateCommand(); + cmd.Transaction = tx; + cmd.CommandText = "SELECT balance FROM Account WITH (UPDLOCK, ROWLOCK) WHERE accountNumber = @accountNumber"; + cmd.Parameters.AddWithValue("@accountNumber", accountNumber); + var result = cmd.ExecuteScalar(); + return result is null or DBNull ? null : (decimal)result; + } + + private static void UpdateBalanceByDelta(SqlConnection conn, SqlTransaction tx, string accountNumber, decimal delta) + { + using var cmd = conn.CreateCommand(); + cmd.Transaction = tx; + cmd.CommandText = "UPDATE Account SET balance = balance + @delta WHERE accountNumber = @accountNumber"; + cmd.Parameters.Add(new SqlParameter("@delta", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = delta }); + cmd.Parameters.AddWithValue("@accountNumber", accountNumber); + cmd.ExecuteNonQuery(); + } + + private static void InsertTransactionRow( + SqlConnection conn, + SqlTransaction tx, + string accountNumber, + decimal amount, + decimal balanceAfter, + DateTime date, + string secondaryPartyName, + string secondaryPartyAccountNumber) + { + using var cmd = conn.CreateCommand(); + cmd.Transaction = tx; + cmd.CommandText = + "INSERT INTO [Transaction](accountNumber, cardNumber, amount, balanceAfterTransaction, date, secondaryPartyName, secondaryPartyAccountNumber) " + + "VALUES (@accountNumber, NULL, @amount, @balanceAfterTransaction, @date, @secondaryPartyName, @secondaryPartyAccountNumber)"; + cmd.Parameters.AddWithValue("@accountNumber", accountNumber); + cmd.Parameters.Add(new SqlParameter("@amount", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = amount }); + cmd.Parameters.Add(new SqlParameter("@balanceAfterTransaction", SqlDbType.Decimal) { Precision = 18, Scale = 2, Value = balanceAfter }); + cmd.Parameters.AddWithValue("@date", date); + cmd.Parameters.AddWithValue("@secondaryPartyName", (object?)secondaryPartyName ?? DBNull.Value); + cmd.Parameters.AddWithValue("@secondaryPartyAccountNumber", (object?)secondaryPartyAccountNumber ?? DBNull.Value); + cmd.ExecuteNonQuery(); + } + public List GetTransactionsByAccountNumber(string accountNumber) { List transactionList = new List(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -52,8 +203,8 @@ public List GetTransactionsByAccountNumber(string accountNumber) TransactionId = (int)reader["transactionId"], CardNumber = reader["cardNumber"] as string, AccountNumber = reader["accountNumber"] as string, - Amount = decimal.ToDouble((decimal)reader["amount"]), - BalanceAfterTransaction = decimal.ToDouble((decimal)reader["balanceAfterTransaction"]), + Amount = (decimal)reader["amount"], + BalanceAfterTransaction = (decimal)reader["balanceAfterTransaction"], Date = (DateTime)reader["date"], SecondaryPartyName = reader["secondaryPartyName"] as string, SecondaryPartyAccountNumber = reader["secondaryPartyAccountNumber"] as string diff --git a/EBanking.DataAccess/Repositories/Implementation/UserRepository.cs b/EBanking.DataAccess/Repositories/Implementation/UserRepository.cs index b88e098..29c8ee2 100644 --- a/EBanking.DataAccess/Repositories/Implementation/UserRepository.cs +++ b/EBanking.DataAccess/Repositories/Implementation/UserRepository.cs @@ -1,17 +1,16 @@ using EBanking.DataAccess.Models; +using EBanking.DataAccess.Security; using System.Data.SqlClient; namespace EBanking.DataAccess.Repositories.Implementation { public class UserRepository : IUserRepository { - private const string _connectionString = @"Data Source = .\SQLEXPRESS;Initial Catalog=EBankingSystem;Integrated Security=True"; - public User GetUserById(int id) { User user = new User(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -42,30 +41,35 @@ public User GetUserById(int id) public int IsValidUser(string email, string password) { - object userId; - - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { - sqlCommand.CommandText = "SELECT * FROM [User] WHERE email = @email AND password = @password"; + sqlCommand.CommandText = "SELECT userId, password FROM [User] WHERE email = @email"; sqlCommand.Parameters.AddWithValue("@email", email); - sqlCommand.Parameters.AddWithValue("@password", password); - userId = sqlCommand.ExecuteScalar(); + using (SqlDataReader reader = sqlCommand.ExecuteReader()) + { + if (!reader.Read()) + { + return 0; + } + + int userId = (int)reader["userId"]; + string storedHash = (string)reader["password"]; + return PasswordHasher.Verify(password, storedHash) ? userId : 0; + } } } - var id = userId == null ? 0 : (int)userId; - return id; } public List GetAllUsers() { List userList = new List(); - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -98,7 +102,7 @@ public List GetAllUsers() public int AddUser(User user) { - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); @@ -113,7 +117,7 @@ public int AddUser(User user) sqlCommand.Parameters.AddWithValue("@phone", user.Phone); sqlCommand.Parameters.AddWithValue("@email", user.Email); sqlCommand.Parameters.AddWithValue("@userPin", user.UserPin); - sqlCommand.Parameters.AddWithValue("@password", user.Password); + sqlCommand.Parameters.AddWithValue("@password", PasswordHasher.Hash(user.Password)); return sqlCommand.ExecuteNonQuery(); } @@ -122,18 +126,18 @@ public int AddUser(User user) public void UpdateUserPassword(string email, string userPin, string password) { - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { sqlCommand.CommandText = "UPDATE [User] SET password = @password WHERE email = @email AND userPin = @userPin"; - sqlCommand.Parameters.AddWithValue("@password", password); + sqlCommand.Parameters.AddWithValue("@password", PasswordHasher.Hash(password)); sqlCommand.Parameters.AddWithValue("@email", email); sqlCommand.Parameters.AddWithValue("@userPin", userPin); - sqlCommand.ExecuteScalar(); + sqlCommand.ExecuteNonQuery(); } } } @@ -142,7 +146,7 @@ public bool VerifyUser(string email, string userPin) { object userId; - using (SqlConnection sqlConnection = new SqlConnection(_connectionString)) + using (SqlConnection sqlConnection = new SqlConnection(DatabaseAccess.ConnectionString)) { sqlConnection.Open(); diff --git a/EBanking.DataAccess/Security/PasswordHasher.cs b/EBanking.DataAccess/Security/PasswordHasher.cs new file mode 100644 index 0000000..8c0a7d5 --- /dev/null +++ b/EBanking.DataAccess/Security/PasswordHasher.cs @@ -0,0 +1,13 @@ +namespace EBanking.DataAccess.Security +{ + public static class PasswordHasher + { + private const int WorkFactor = 11; + + public static string Hash(string password) + => BCrypt.Net.BCrypt.HashPassword(password, WorkFactor); + + public static bool Verify(string password, string hash) + => BCrypt.Net.BCrypt.Verify(password, hash); + } +} diff --git a/EBanking.Services/IAccountService.cs b/EBanking.Services/IAccountService.cs index ab90b5b..1d977ea 100644 --- a/EBanking.Services/IAccountService.cs +++ b/EBanking.Services/IAccountService.cs @@ -5,8 +5,8 @@ namespace EBanking.Services public interface IAccountService { List GetAccountsForUser(int userId); - void UpdateBalance(double newBalance, string accountNumber); + void UpdateBalance(decimal newBalance, string accountNumber); bool IsValidAccount(string accountNumber); - Task GetAccountByAccountNumber(string accountNumber); + AccountModel GetAccountByAccountNumber(string accountNumber); } } diff --git a/EBanking.Services/ICurrencyExchangeService.cs b/EBanking.Services/ICurrencyExchangeService.cs index b71fae3..0fb8cb5 100644 --- a/EBanking.Services/ICurrencyExchangeService.cs +++ b/EBanking.Services/ICurrencyExchangeService.cs @@ -1,6 +1,9 @@ +using EBanking.Services.Models; + namespace EBanking.Services { public interface ICurrencyExchangeService { + void Exchange(ExchangeRequest request); } } diff --git a/EBanking.Services/ITransactionService.cs b/EBanking.Services/ITransactionService.cs index f0f8761..73341a6 100644 --- a/EBanking.Services/ITransactionService.cs +++ b/EBanking.Services/ITransactionService.cs @@ -6,5 +6,6 @@ public interface ITransactionService { void CreateTransaction(TransactionModel transactionModel); List GetTransactionsByAccountNumber(string accountNumber); + void TransferFunds(TransferRequest request); } } diff --git a/EBanking.Services/Implementation/AccountService.cs b/EBanking.Services/Implementation/AccountService.cs index 0e66176..87d0a9d 100644 --- a/EBanking.Services/Implementation/AccountService.cs +++ b/EBanking.Services/Implementation/AccountService.cs @@ -17,9 +17,9 @@ public AccountService(IAccountRepository accountRepository, ITransactionReposito _currencyExchangeRepository = currencyExchangeRepository; } - public async Task GetAccountByAccountNumber(string accountNumber) + public AccountModel GetAccountByAccountNumber(string accountNumber) { - Account account = await _accountRepository.GetAccountByAccountNumber(accountNumber); + Account account = _accountRepository.GetAccountByAccountNumber(accountNumber); return new AccountModel { @@ -108,7 +108,7 @@ public bool IsValidAccount(string accountNumber) return _accountRepository.IsValidAccount(accountNumber); } - public void UpdateBalance(double newBalance, string accountNumber) + public void UpdateBalance(decimal newBalance, string accountNumber) { _accountRepository.UpdateBalance(newBalance, accountNumber); } diff --git a/EBanking.Services/Implementation/CurrencyExchangeService.cs b/EBanking.Services/Implementation/CurrencyExchangeService.cs index f7a8562..2a1143d 100644 --- a/EBanking.Services/Implementation/CurrencyExchangeService.cs +++ b/EBanking.Services/Implementation/CurrencyExchangeService.cs @@ -1,14 +1,54 @@ using EBanking.DataAccess.Repositories; +using EBanking.Services.Models; namespace EBanking.Services.Implementation { public class CurrencyExchangeService : ICurrencyExchangeService { private readonly ICurrencyExchangeRepository _currencyExchangeRepository; + private readonly ITransactionRepository _transactionRepository; - public CurrencyExchangeService(ICurrencyExchangeRepository currencyExchangeRepository) + public CurrencyExchangeService( + ICurrencyExchangeRepository currencyExchangeRepository, + ITransactionRepository transactionRepository) { _currencyExchangeRepository = currencyExchangeRepository; + _transactionRepository = transactionRepository; + } + + public void Exchange(ExchangeRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + if (request.Amount <= 0) + { + throw new ArgumentException("Amount must be greater than zero.", nameof(request)); + } + if (string.Equals(request.SourceAccountNumber, request.DestinationAccountNumber, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Cannot exchange to the same account."); + } + + var rates = _currencyExchangeRepository.GetExchangeRatesByCurrency(request.SourceCurrency); + var rate = rates.FirstOrDefault(r => + string.Equals(r.Currency, request.DestinationCurrency, StringComparison.OrdinalIgnoreCase)) + ?? throw new InvalidOperationException( + $"Exchange rate from {request.SourceCurrency} to {request.DestinationCurrency} not found."); + + // The rate column is DECIMAL(18,4); Account.balance is DECIMAL(18,2). + // Round the credit to 2dp before it crosses into the transaction so the + // SqlParameter (Scale = 2) doesn't silently truncate at the driver. + decimal destinationAmount = decimal.Round(request.Amount * rate.Value, 2, MidpointRounding.AwayFromZero); + + _transactionRepository.ExecuteExchange( + sourceAccountNumber: request.SourceAccountNumber, + destinationAccountNumber: request.DestinationAccountNumber, + sourceAmount: request.Amount, + destinationAmount: destinationAmount, + userFullName: request.UserFullName, + occurredAt: DateTime.UtcNow); } } } diff --git a/EBanking.Services/Implementation/TransactionService.cs b/EBanking.Services/Implementation/TransactionService.cs index 0d1cfd0..bb54c9f 100644 --- a/EBanking.Services/Implementation/TransactionService.cs +++ b/EBanking.Services/Implementation/TransactionService.cs @@ -13,6 +13,30 @@ public TransactionService(ITransactionRepository transactionRepository) _transactionRepository = transactionRepository; } + public void TransferFunds(TransferRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + if (request.Amount <= 0) + { + throw new ArgumentException("Amount must be greater than zero.", nameof(request)); + } + if (string.Equals(request.PayerAccountNumber, request.RecipientAccountNumber, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Cannot transfer to the same account."); + } + + _transactionRepository.ExecuteTransfer( + payerAccountNumber: request.PayerAccountNumber, + recipientAccountNumber: request.RecipientAccountNumber, + amount: request.Amount, + recipientName: request.RecipientName, + payerFullName: request.PayerFullName, + occurredAt: DateTime.UtcNow); + } + public void CreateTransaction(TransactionModel transactionModel) { _transactionRepository.CreateTransaction(new Transaction diff --git a/EBanking.Services/Models/AccountModel.cs b/EBanking.Services/Models/AccountModel.cs index f0ac43a..f652f42 100644 --- a/EBanking.Services/Models/AccountModel.cs +++ b/EBanking.Services/Models/AccountModel.cs @@ -4,7 +4,7 @@ public class AccountModel { public string AccountNumber { get; set; } public int UserId { get; set; } - public double Balance { get; set; } + public decimal Balance { get; set; } public string Type { get; set; } public string Currency { get; set; } public DateTime DateCreated { get; set; } diff --git a/EBanking.Services/Models/CurrencyExchangeModel.cs b/EBanking.Services/Models/CurrencyExchangeModel.cs index 960f34a..197f21d 100644 --- a/EBanking.Services/Models/CurrencyExchangeModel.cs +++ b/EBanking.Services/Models/CurrencyExchangeModel.cs @@ -3,6 +3,6 @@ namespace EBanking.Services.Models public class CurrencyExchangeModel { public string Currency { get; set; } - public double Value { get; set; } + public decimal Value { get; set; } } } diff --git a/EBanking.Services/Models/TransactionInfo.cs b/EBanking.Services/Models/TransactionInfo.cs index 1e3cc77..b63b99f 100644 --- a/EBanking.Services/Models/TransactionInfo.cs +++ b/EBanking.Services/Models/TransactionInfo.cs @@ -4,6 +4,6 @@ public class TransactionInfo { public string AccountNumber { get; set; } public string UserFullName { get; set; } - public double CurrentBalance { get; set; } + public decimal CurrentBalance { get; set; } } } diff --git a/EBanking.Services/Models/TransactionModel.cs b/EBanking.Services/Models/TransactionModel.cs index ddb88f7..590cf35 100644 --- a/EBanking.Services/Models/TransactionModel.cs +++ b/EBanking.Services/Models/TransactionModel.cs @@ -5,8 +5,8 @@ public class TransactionModel public int TransactionId { get; set; } public string AccountNumber { get; set; } public string CardNumber { get; set; } - public double Amount { get; set; } - public double BalanceAfterTransaction { get; set; } + public decimal Amount { get; set; } + public decimal BalanceAfterTransaction { get; set; } public DateTime Date { get; set; } public string SecondaryPartyName { get; set; } public string SecondaryPartyAccountNumber { get; set; } diff --git a/EBanking.Services/Models/TransferRequest.cs b/EBanking.Services/Models/TransferRequest.cs new file mode 100644 index 0000000..3f05a5f --- /dev/null +++ b/EBanking.Services/Models/TransferRequest.cs @@ -0,0 +1,12 @@ +namespace EBanking.Services.Models +{ + public class TransferRequest + { + public string PayerAccountNumber { get; set; } = ""; + public string RecipientAccountNumber { get; set; } = ""; + public decimal Amount { get; set; } + public string RecipientName { get; set; } = ""; + public string PaymentPurpose { get; set; } = ""; + public string PayerFullName { get; set; } = ""; + } +} diff --git a/EBanking.TestConsole/App.config b/EBanking.TestConsole/App.config new file mode 100644 index 0000000..fadec26 --- /dev/null +++ b/EBanking.TestConsole/App.config @@ -0,0 +1,8 @@ + + + + + + diff --git a/EBanking.TestConsole/Program.cs b/EBanking.TestConsole/Program.cs new file mode 100644 index 0000000..ba37d7e --- /dev/null +++ b/EBanking.TestConsole/Program.cs @@ -0,0 +1,51 @@ +using EBanking.DataAccess; +using EBanking.DataAccess.Security; +using System.Data.SqlClient; + +// One-shot utility for the password modernization pass. +// +// Usage: +// dotnet run --project EBanking.TestConsole -> re-hash any plaintext rows in [User] +// dotnet run --project EBanking.TestConsole -- hash -> print a BCrypt hash for +// +// "Plaintext" is detected by the absence of the BCrypt marker '$2' at the +// start of the password column. The rehash pass is idempotent: running it +// again on an already-hashed table is a no-op. + +if (args.Length >= 2 && args[0].Equals("hash", StringComparison.OrdinalIgnoreCase)) +{ + Console.WriteLine(PasswordHasher.Hash(args[1])); + return; +} + +using var connection = new SqlConnection(DatabaseAccess.ConnectionString); +connection.Open(); + +var rows = new List<(int UserId, string Password)>(); +using (var read = connection.CreateCommand()) +{ + read.CommandText = "SELECT userId, password FROM [User] WHERE password NOT LIKE '$2%'"; + using var reader = read.ExecuteReader(); + while (reader.Read()) + { + rows.Add(((int)reader["userId"], (string)reader["password"])); + } +} + +if (rows.Count == 0) +{ + Console.WriteLine("No plaintext passwords found. Nothing to do."); + return; +} + +Console.WriteLine($"Rehashing {rows.Count} user row(s)..."); +foreach (var (userId, plaintext) in rows) +{ + using var update = connection.CreateCommand(); + update.CommandText = "UPDATE [User] SET password = @hash WHERE userId = @id"; + update.Parameters.AddWithValue("@hash", PasswordHasher.Hash(plaintext)); + update.Parameters.AddWithValue("@id", userId); + update.ExecuteNonQuery(); + Console.WriteLine($" userId={userId} updated"); +} +Console.WriteLine("Done."); diff --git a/EBanking.UI/App.config b/EBanking.UI/App.config new file mode 100644 index 0000000..fadec26 --- /dev/null +++ b/EBanking.UI/App.config @@ -0,0 +1,8 @@ + + + + + + diff --git a/EBanking.UI/Common/Validation/PaymentViewValidator.cs b/EBanking.UI/Common/Validation/PaymentViewValidator.cs index fddda2b..e42f36e 100644 --- a/EBanking.UI/Common/Validation/PaymentViewValidator.cs +++ b/EBanking.UI/Common/Validation/PaymentViewValidator.cs @@ -8,17 +8,22 @@ public bool ValidateModel(TModel model) { int errors = 0; - if (model.RecipientAccountNumber == null || model.RecipientAccountNumber == "") + if (string.IsNullOrEmpty(model.RecipientAccountNumber)) { model.RecipientAccountNumberError = "You have not entered the recipient's account number"; errors++; } + else if (model.RecipientAccountNumber == model.PayerAccountNumber) + { + model.RecipientAccountNumberError = "Cannot transfer to the same account"; + errors++; + } else { model.RecipientAccountNumberError = null; } - if (model.RecipientName == null || model.RecipientName == "") + if (string.IsNullOrEmpty(model.RecipientName)) { model.RecipientNameError = "You have not entered the recipient's name"; errors++; @@ -28,11 +33,16 @@ public bool ValidateModel(TModel model) model.RecipientNameError = null; } - if (model.Amount == null || model.Amount <= 0) + if (model.Amount <= 0) { model.AmountError = "You have not entered the amount"; errors++; } + else if (model.Amount > model.CurrentBalance) + { + model.AmountError = "Insufficient funds"; + errors++; + } else { model.AmountError = null; diff --git a/EBanking.UI/Models/AccountModel.cs b/EBanking.UI/Models/AccountModel.cs index 31fef73..5c30e13 100644 --- a/EBanking.UI/Models/AccountModel.cs +++ b/EBanking.UI/Models/AccountModel.cs @@ -5,7 +5,7 @@ namespace EBanking.UI.Models public class AccountModel : BaseModel { public string AccountNumber { get; set; } - public double Balance { get; set; } + public decimal Balance { get; set; } public string UserFullName { get; set; } public Services.Models.AccountModel SelectedAccount { get; set; } public List Accounts { get; set; } diff --git a/EBanking.UI/Models/CurrencyExchangeModel.cs b/EBanking.UI/Models/CurrencyExchangeModel.cs index 15ca636..ccec88c 100644 --- a/EBanking.UI/Models/CurrencyExchangeModel.cs +++ b/EBanking.UI/Models/CurrencyExchangeModel.cs @@ -5,8 +5,8 @@ namespace EBanking.UI.Models { public class CurrencyExchangeModel : BaseModel { - public double Amount { get; set; } - public double ConvertedValue { get; set; } + public decimal Amount { get; set; } + public decimal ConvertedValue { get; set; } public Services.Models.AccountModel Account { get; set; } public Services.Models.AccountModel SelectedAccount { get; set; } public List UserAccounts { get; set; } diff --git a/EBanking.UI/Models/LoginModel.cs b/EBanking.UI/Models/LoginModel.cs index 6e3f2d9..7d43876 100644 --- a/EBanking.UI/Models/LoginModel.cs +++ b/EBanking.UI/Models/LoginModel.cs @@ -2,9 +2,9 @@ namespace EBanking.UI.Models { public class LoginModel : BaseModel { - public string Email { get; set; } = "123@gmail.com"; + public string Email { get; set; } = ""; - public string Password { get; set; } = "2222"; + public string Password { get; set; } = ""; #region Validation public string EmailError { get; set; } diff --git a/EBanking.UI/Models/PaymentModel.cs b/EBanking.UI/Models/PaymentModel.cs index c283a3b..d5bca03 100644 --- a/EBanking.UI/Models/PaymentModel.cs +++ b/EBanking.UI/Models/PaymentModel.cs @@ -3,12 +3,12 @@ namespace EBanking.UI.Models public class PaymentModel : BaseModel { public string PayerAccountNumber { get; set; } - public double CurrentBalance { get; set; } + public decimal CurrentBalance { get; set; } public string RecipientName { get; set; } public string RecipientAccountNumber { get; set; } public string ReferenceNumber { get; set; } public string PaymentPurpose { get; set; } - public double Amount { get; set; } + public decimal Amount { get; set; } #region Validation public string PayerAccountNumberError { get; set; } diff --git a/EBanking.UI/Models/RegistrationModel.cs b/EBanking.UI/Models/RegistrationModel.cs index e29dd6d..d97f942 100644 --- a/EBanking.UI/Models/RegistrationModel.cs +++ b/EBanking.UI/Models/RegistrationModel.cs @@ -7,13 +7,12 @@ public class RegistrationModel : BaseModel public string Password { get; set; } public string ConfirmPassword { get; set; } - public string Label { get; set; } - #region Validation public string EmailError { get; set; } public string UserPinError { get; set; } public string PasswordError { get; set; } public string ConfirmPasswordError { get; set; } + public string FormError { get; set; } #endregion } } diff --git a/EBanking.UI/ViewModels/Windows/AccountViewModel.cs b/EBanking.UI/ViewModels/Windows/AccountViewModel.cs index b067811..b080c70 100644 --- a/EBanking.UI/ViewModels/Windows/AccountViewModel.cs +++ b/EBanking.UI/ViewModels/Windows/AccountViewModel.cs @@ -4,6 +4,7 @@ using EBanking.UI.Views; using GalaSoft.MvvmLight.Ioc; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using System.Windows; @@ -56,12 +57,14 @@ public void OpenCurrencyExchange() var accounts = Model.Accounts.Where(x => x.AccountNumber != Model.SelectedAccount.AccountNumber).ToList(); CurrencyExchangeView view = new CurrencyExchangeView { - DataContext = new CurrencyExchangeViewModel(_transactionService, _accountService, _currencyExchangeService, Model.SelectedAccount, accounts, Model.UserFullName) + DataContext = new CurrencyExchangeViewModel(_currencyExchangeService, Model.SelectedAccount, accounts, Model.UserFullName) }; - view.Closing += (s, o) => + void OnCurrencyExchangeClosing(object? sender, CancelEventArgs e) { + view.Closing -= OnCurrencyExchangeClosing; GetAccount(UserId); - }; + } + view.Closing += OnCurrencyExchangeClosing; view.Show(); } else @@ -75,20 +78,22 @@ public void Logout() Close(); } - public async void Payment() + public void Payment() { if (Model.SelectedAccount is not null) { PaymentView view = new PaymentView { - DataContext = new PaymentViewModel(_transactionService, _accountService, + DataContext = new PaymentViewModel(_transactionService, new TransactionInfo { AccountNumber = Model.SelectedAccount.AccountNumber, CurrentBalance = Model.SelectedAccount.Balance, UserFullName = Model.Accounts.First().User.FirstName + " " + Model.Accounts.First().User.LastName }) }; - view.Closing += (s, o) => + void OnPaymentClosing(object? sender, CancelEventArgs e) { + view.Closing -= OnPaymentClosing; GetAccount(UserId); - }; + } + view.Closing += OnPaymentClosing; view.Show(); } else diff --git a/EBanking.UI/ViewModels/Windows/CurrencyExchangeViewModel.cs b/EBanking.UI/ViewModels/Windows/CurrencyExchangeViewModel.cs index d8461ac..0b137d2 100644 --- a/EBanking.UI/ViewModels/Windows/CurrencyExchangeViewModel.cs +++ b/EBanking.UI/ViewModels/Windows/CurrencyExchangeViewModel.cs @@ -5,17 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; using System.Windows; namespace EBanking.UI.ViewModels.Windows { public class CurrencyExchangeViewModel : BaseViewModel { - private readonly ITransactionService _transactionService; - private readonly IAccountService _accountService; private readonly ICurrencyExchangeService _currencyExchangeService; [PreferredConstructor] @@ -24,8 +19,7 @@ public CurrencyExchangeViewModel() Model.Title = "Currency Exchange"; } - public CurrencyExchangeViewModel(ITransactionService transactionService, - IAccountService accountService, + public CurrencyExchangeViewModel( ICurrencyExchangeService currencyExchangeService, AccountModel account, List userAccounts, @@ -35,8 +29,6 @@ public CurrencyExchangeViewModel(ITransactionService transactionService, Model.Account = account; Model.UserAccounts = userAccounts; Model.Accounts = new System.Collections.ObjectModel.ObservableCollection(Model.UserAccounts); - _transactionService = transactionService; - _accountService = accountService; _currencyExchangeService = currencyExchangeService; ConvertCommand = new RelayCommand(Convert); ExecuteTransactionCommand = new RelayCommand(ExecuteTransaction); @@ -69,44 +61,39 @@ public void Convert() public void ExecuteTransaction() { - if (Model.Account.Balance > Model.Amount) + if (Model.SelectedAccount is null) { - _transactionService.CreateTransaction(new TransactionModel + MessageBox.Show("You have not selected an account"); + return; + } + if (Model.Amount <= 0) + { + MessageBox.Show("Enter an amount greater than zero"); + return; + } + if (Model.Account.Balance < Model.Amount) + { + MessageBox.Show("Insufficient funds on the account"); + return; + } + + try + { + _currencyExchangeService.Exchange(new ExchangeRequest { - AccountNumber = Model.Account.AccountNumber, + SourceAccountNumber = Model.Account.AccountNumber, + DestinationAccountNumber = Model.SelectedAccount.AccountNumber, Amount = Model.Amount, - SecondaryPartyAccountNumber = Model.SelectedAccount.AccountNumber, - SecondaryPartyName = UserFullName, - BalanceAfterTransaction = Model.Account.Balance - Model.Amount, - Date = DateTime.UtcNow + SourceCurrency = Model.Account.Currency, + DestinationCurrency = Model.SelectedAccount.Currency, + UserFullName = UserFullName, }); - - _accountService.UpdateBalance(Model.Account.Balance - Model.Amount, Model.Account.AccountNumber); - - AddTransactionToRecipient(); Close(); } - else + catch (Exception ex) { - MessageBox.Show("Insufficient funds on the account"); + MessageBox.Show(ex.Message, "Exchange failed"); } } - - private void AddTransactionToRecipient() - { - _transactionService.CreateTransaction(new TransactionModel - { - AccountNumber = Model.SelectedAccount.AccountNumber, - Amount = Model.ConvertedValue, - SecondaryPartyAccountNumber = Model.Account.AccountNumber, - SecondaryPartyName = UserFullName, - BalanceAfterTransaction = Model.SelectedAccount.Balance + Model.ConvertedValue, - Date = DateTime.UtcNow - }); - - _accountService.UpdateBalance(Model.SelectedAccount.Balance + Model.ConvertedValue, Model.SelectedAccount.AccountNumber); - } } - - } diff --git a/EBanking.UI/ViewModels/Windows/LoginViewModel.cs b/EBanking.UI/ViewModels/Windows/LoginViewModel.cs index 7b58317..8d1ae6e 100644 --- a/EBanking.UI/ViewModels/Windows/LoginViewModel.cs +++ b/EBanking.UI/ViewModels/Windows/LoginViewModel.cs @@ -3,6 +3,7 @@ using EBanking.UI.Common.Validation; using EBanking.UI.Models; using EBanking.UI.Views; +using System.ComponentModel; using System.Windows; namespace EBanking.UI.ViewModels.Windows @@ -46,14 +47,16 @@ public void Login() { DataContext = new AccountViewModel(_accountService, _transactionService, _currencyExchangeService, userId) }; - accountView.Closing += (s, o) => + void OnAccountViewClosing(object? sender, CancelEventArgs e) { + accountView.Closing -= OnAccountViewClosing; LoginView view = new LoginView { DataContext = this }; view.Show(); - }; + } + accountView.Closing += OnAccountViewClosing; accountView.Show(); Close(); } diff --git a/EBanking.UI/ViewModels/Windows/PaymentViewModel.cs b/EBanking.UI/ViewModels/Windows/PaymentViewModel.cs index f261991..3806728 100644 --- a/EBanking.UI/ViewModels/Windows/PaymentViewModel.cs +++ b/EBanking.UI/ViewModels/Windows/PaymentViewModel.cs @@ -5,24 +5,23 @@ using EBanking.UI.Models; using GalaSoft.MvvmLight.Ioc; using System; +using System.Windows; namespace EBanking.UI.ViewModels.Windows { public class PaymentViewModel : BaseViewModel { private readonly ITransactionService _transactionService; - private readonly IAccountService _accountService; [PreferredConstructor] public PaymentViewModel() { } - public PaymentViewModel(ITransactionService transactionService, IAccountService accountService, TransactionInfo transactionInfo) + public PaymentViewModel(ITransactionService transactionService, TransactionInfo transactionInfo) { Validator = new PaymentViewValidator(); _transactionService = transactionService; - _accountService = accountService; Model.Title = "New Payment"; Model.PayerAccountNumber = transactionInfo.AccountNumber; Model.CurrentBalance = transactionInfo.CurrentBalance; @@ -35,43 +34,28 @@ public PaymentViewModel(ITransactionService transactionService, IAccountService public void Pay() { - if (Validator.ValidateModel(Model)) + if (!Validator.ValidateModel(Model)) { - _transactionService.CreateTransaction(new TransactionModel + return; + } + + try + { + _transactionService.TransferFunds(new TransferRequest { - AccountNumber = Model.PayerAccountNumber, + PayerAccountNumber = Model.PayerAccountNumber, + RecipientAccountNumber = Model.RecipientAccountNumber, Amount = Model.Amount, - SecondaryPartyAccountNumber = Model.RecipientAccountNumber, - SecondaryPartyName = Model.RecipientName, - BalanceAfterTransaction = Model.CurrentBalance - Model.Amount, - Date = DateTime.UtcNow + RecipientName = Model.RecipientName, + PaymentPurpose = Model.PaymentPurpose, + PayerFullName = TransactionInfo.UserFullName, }); - - _accountService.UpdateBalance(Model.CurrentBalance - Model.Amount, Model.PayerAccountNumber); - - if (_accountService.IsValidAccount(Model.RecipientAccountNumber)) - { - AddTransactionToRecipient(); - } Close(); } - } - - private async void AddTransactionToRecipient() - { - var account = await _accountService.GetAccountByAccountNumber(Model.RecipientAccountNumber); - - _transactionService.CreateTransaction(new TransactionModel + catch (Exception ex) { - AccountNumber = Model.RecipientAccountNumber, - Amount = Model.Amount, - SecondaryPartyAccountNumber = Model.PayerAccountNumber, - SecondaryPartyName = TransactionInfo.UserFullName, - BalanceAfterTransaction = account.Balance + Model.Amount, - Date = DateTime.UtcNow - }); - - _accountService.UpdateBalance(account.Balance + Model.Amount, Model.RecipientAccountNumber); + MessageBox.Show(ex.Message, "Payment failed"); + } } } } diff --git a/EBanking.UI/ViewModels/Windows/RegistrationViewModel.cs b/EBanking.UI/ViewModels/Windows/RegistrationViewModel.cs index 7646046..304437a 100644 --- a/EBanking.UI/ViewModels/Windows/RegistrationViewModel.cs +++ b/EBanking.UI/ViewModels/Windows/RegistrationViewModel.cs @@ -1,11 +1,8 @@ -using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using EBanking.Services; using EBanking.UI.Common.Validation; using EBanking.UI.Models; using EBanking.UI.Views; -using System; -using System.Windows; namespace EBanking.UI.ViewModels.Windows { @@ -16,7 +13,7 @@ public class RegistrationViewModel : BaseViewModel public RegistrationViewModel(IUserService userService) { Validator = new RegistrationViewValidator(); - Model.Title = "Registration"; + Model.Title = "Activate access"; _userService = userService; RegistrationCommand = new RelayCommand(ActivateUser); } @@ -31,28 +28,28 @@ public void ShowLoginWindow() public void ActivateUser() { - if (Validator.ValidateModel(Model)) + Model.FormError = ""; + + if (!Validator.ValidateModel(Model)) + { + return; + } + + if (!_userService.VerifyUser(Model.Email, Model.UserPin)) { - if (_userService.VerifyUser(Model.Email, Model.UserPin)) - { - if (Model.Password.Equals(Model.ConfirmPassword)) - { - _userService.UpdateUserPassword(Model.Email, Model.UserPin, Model.Password); - - ShowLoginWindow(); - - Close(); - } - else - { - Model.Label = "Passwords do not match"; - } - } - else - { - MessageBox.Show("User PIN and email do not match.", "Error"); - } + Model.FormError = "Email or activation PIN is not recognized."; + return; } + + if (!Model.Password.Equals(Model.ConfirmPassword)) + { + Model.ConfirmPasswordError = "Passwords do not match"; + return; + } + + _userService.UpdateUserPassword(Model.Email, Model.UserPin, Model.Password); + ShowLoginWindow(); + Close(); } } } diff --git a/EBanking.UI/Views/CurrencyExchangeView.xaml b/EBanking.UI/Views/CurrencyExchangeView.xaml index 89830ba..a974205 100644 --- a/EBanking.UI/Views/CurrencyExchangeView.xaml +++ b/EBanking.UI/Views/CurrencyExchangeView.xaml @@ -35,8 +35,10 @@