Setup system for handeling transactions between acounts.

Made so each Transaction handler in the Account class to return the transaction object.
Updated the Utils class to handle the transactions que reading and writing.
Added org.json to aid in trnsaction que handling.
made so accounts can update there balance and transactions from the transaction que.
Added a tranfer window to handle the transfer of money between acounts.
This commit is contained in:
2025-04-23 13:36:49 +02:00
parent d2881cb52d
commit 86b505b4e3
11 changed files with 462 additions and 44 deletions

View File

@@ -13,6 +13,7 @@ repositories {
dependencies {
implementation 'com.google.code.gson:gson:2.12.1'
implementation("org.jetbrains:annotations:23.1.0")
implementation 'org.json:json:20250107'
}
test {

View File

@@ -49,7 +49,7 @@ public class Account {
transactions.add(new Transaction(0, "Account opened", id, id, TransactionType.OPEN));
}
public void WithdrawTransaction(double amount, String description, UUID destination) {
public Transaction WithdrawTransaction(double amount, String description, UUID destination) {
if(amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
@@ -59,27 +59,30 @@ public class Account {
Transaction transaction = new Transaction(amount, description, destination, this.id, TransactionType.WITHDRAW);
transactions.add(transaction);
balance -= amount;
return transaction;
}
public void DepositTransaction(double amount, String description, UUID originator) {
public Transaction DepositTransaction(double amount, String description, UUID originator) {
if(amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
Transaction transaction = new Transaction(amount, description, this.id, originator, TransactionType.DEPOSIT);
transactions.add(transaction);
balance += amount;
return transaction;
}
public void TransferTransaction(double amount, String description, UUID originator, UUID destination) {
public Transaction TransferTransaction(double amount, String description, UUID originator, UUID destination) {
if(amount < 0 && balance < Math.abs(amount)) {
throw new IllegalArgumentException("Insufficient balance");
}
Transaction transaction = new Transaction(amount, description, originator, destination, TransactionType.TRANSFER);
transactions.add(transaction);
balance += amount;
return transaction;
}
public void handleTransaction(Transaction transaction) {
public Transaction handleTransaction(Transaction transaction) {
if(transaction.getType() == TransactionType.WITHDRAW) {
if(transaction.getAmount() < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
@@ -97,6 +100,16 @@ public class Account {
balance += transaction.getAmount();
}
transactions.add(transaction);
return transaction;
}
public void load()
{
ArrayList<Transaction> transactions = Utils.GetTransactions(id);
for(Transaction transaction : transactions) {
handleTransaction(transaction);
}
}
public String getName() {
@@ -121,7 +134,12 @@ public class Account {
@Override
public String toString() {
return name + " - " + balance + " kr";
return toString(false);
}
public String toString(boolean negativ)
{
return name + " - " + (negativ ? -balance : balance) + " kr";
}
public static class Interests

View File

@@ -13,22 +13,45 @@ public class Main {
User user;
public static void main(String[] args) {
String username = "user";
String user = SHA256(username);
File userFile = new File("./users/" + user + ".json");
if(!userFile.exists()) {
User u = new User(username);
u.createAccount("Konto");
Account a = u.getAccount("Konto");
a.DepositTransaction(100, "Deposit", UUID.randomUUID());
String json = gson.toJson(u);
try {
WriteFile(userFile, Encrypt(json, "pass"));
} catch (Exception e) {
throw new RuntimeException(e);
{
String username = "theo";
String user = SHA256(username);
File userFile = new File("./users/" + user + ".json");
if (!userFile.exists()) {
User u = new User(username);
u.createAccount("Konto");
Account a = u.getAccount("Konto");
a.DepositTransaction(100, "Deposit", UUID.randomUUID());
String json = gson.toJson(u);
try {
WriteFile(userFile, Encrypt(json, "pass"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
{
String username = "user";
String user = SHA256(username);
File userFile = new File("./users/" + user + ".json");
if (!userFile.exists()) {
User u = new User(username);
u.createAccount("Konto");
Account a = u.getAccount("Konto");
a.DepositTransaction(100, "Deposit", UUID.randomUUID());
String json = gson.toJson(u);
try {
WriteFile(userFile, Encrypt(json, "pass"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -44,7 +67,7 @@ public class Main {
//return;
}
String name = "user";//console.readLine("Enter your name: ");
String name = "theo";//console.readLine("Enter your name: ");
String userHash = SHA256(name);

View File

@@ -4,9 +4,12 @@ import me.zacharias.bank.transaction.Transaction;
import me.zacharias.bank.transaction.TransactionType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.UUID;
import static me.zacharias.bank.Utils.*;
public class User {
UUID id;
String name;
@@ -69,4 +72,21 @@ public class User {
public ArrayList<Account> getAccounts() {
return accounts;
}
public void write() {
String user = SHA256(name);
File userFile = new File("./users/" + user + ".json");
String json = gson.toJson(this);
try {
WriteFile(userFile, Encrypt(json, "pass"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void update() {
for(Account account : accounts) {
account.load();
}
}
}

View File

@@ -2,19 +2,24 @@ package me.zacharias.bank;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import me.zacharias.bank.transaction.Transaction;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.UUID;
public class Utils {
@@ -57,7 +62,7 @@ public class Utils {
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 12;
public static String Encrypt(String data, String username) throws Exception {
public static String Encrypt(String data, String username) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
@@ -81,7 +86,7 @@ public class Utils {
return Base64.getEncoder().encodeToString(combined);
}
public static String Decrypt(String data, String username) throws Exception {
public static String Decrypt(String data, String username) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
byte[] combined = Base64.getDecoder().decode(data);
// Extract salt, IV, and encrypted data
@@ -103,7 +108,7 @@ public class Utils {
return new String(decrptedData);
}
private static SecretKey deriveKey(String password, byte[] salt) throws Exception {
private static SecretKey deriveKey(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_SIZE);
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
@@ -149,4 +154,130 @@ public class Utils {
throw new RuntimeException(e);
}
}
public static void AddTrnsationQue(Transaction transaction) {
try {
File transactionQueFile = new File("./data/transactionQue.json");
JSONObject jsonObject = getJsonObject(transactionQueFile);
JSONArray transactions = jsonObject.getJSONArray("transactions");
String validation = jsonObject.getString("validation");
if (!validate(transactions, validation)) {
throw new RuntimeException("Validation failed");
}
transactions.put(Encrypt(gson.toJson(transaction), transaction.getDestination().toString()));
jsonObject.remove("transactions");
jsonObject.put("transactions", transactions);
String validation1 = SHA256(jsonObject.getJSONArray("transactions").toString());
jsonObject.put("validation", Base64.getEncoder().encodeToString(validation1.getBytes()));
BufferedWriter bw = new BufferedWriter(new FileWriter(transactionQueFile));
bw.write(jsonObject.toString());
bw.close();
}catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException |
InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
public static ArrayList<Transaction> GetTransactions(UUID account) {
try{
File transactionQueFile = new File("./data/transactionQue.json");
JSONObject jsonObject = getJsonObject(transactionQueFile);
JSONArray transactions = jsonObject.getJSONArray("transactions");
String validation = jsonObject.getString("validation");
if (!validate(transactions, validation)) {
throw new RuntimeException("Validation failed");
}
ArrayList<Transaction> transactionList = new ArrayList<>();
for(int i = 0; i < transactions.length(); i++) {
String transaction = Decrypt(transactions.getString(i), account.toString());
try {
Transaction tr = gson.fromJson(transaction, Transaction.class);
transactionList.add(tr);
transactions.remove(i);
}catch (JsonSyntaxException e)
{
continue;
}
}
jsonObject.remove("transactions");
jsonObject.put("transactions", transactions);
String validation1 = SHA256(jsonObject.getJSONArray("transactions").toString());
jsonObject.put("validation", Base64.getEncoder().encodeToString(validation1.getBytes()));
BufferedWriter bw = new BufferedWriter(new FileWriter(transactionQueFile));
bw.write(jsonObject.toString());
bw.close();
return transactionList;
}catch (IOException | InvalidAlgorithmParameterException | NoSuchPaddingException | IllegalBlockSizeException |
NoSuchAlgorithmException | BadPaddingException | InvalidKeySpecException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
private static @NotNull JSONObject getJsonObject(File transactionQueFile) throws IOException {
if(!transactionQueFile.exists())
{
if(!transactionQueFile.getParentFile().exists())
{
transactionQueFile.getParentFile().mkdirs();
}
transactionQueFile.createNewFile();
JSONObject jsonObject = new JSONObject();
jsonObject.put("transactions", new JSONArray());
String validation1 = SHA256(jsonObject.getJSONArray("transactions").toString());
jsonObject.put("validation", Base64.getEncoder().encodeToString(validation1.getBytes()));
return jsonObject;
}
BufferedReader in = new BufferedReader(new FileReader(transactionQueFile));
StringBuilder sb = new StringBuilder();
String tmp = "";
while((tmp = in.readLine()) != null)
{
sb.append(tmp);
}
in.close();
JSONObject jsonObject = new JSONObject(sb.toString());
return jsonObject;
}
private static boolean validate(JSONArray transactions, String validation) {
byte[] decodedBytes = Base64.getDecoder().decode(validation);
StringBuilder result = new StringBuilder();
for (byte b : decodedBytes) {
char originalChar = (char)(b);
result.append(originalChar);
}
return SHA256(transactions.toString()).equals(result.toString());
}
}

View File

@@ -4,9 +4,12 @@ import me.zacharias.bank.Account;
import me.zacharias.bank.app.component.DrawManager;
import me.zacharias.bank.app.component.InteractiveList;
import me.zacharias.bank.transaction.Transaction;
import me.zacharias.bank.Utils;
import me.zacharias.bank.transaction.TransactionType;
import javax.swing.*;
import java.awt.*;
import java.util.UUID;
public class AccountView extends JPanel {
@@ -15,8 +18,10 @@ public class AccountView extends JPanel {
JLabel accountName;
JLabel balance;
JLabel AccountNumber;
JLabel accountNumber;
InteractiveList<Transaction> transactionList;
JButton transferButton;
JButton backButton;
public AccountView(Main bankApplication, Account account) {
this.bankApplication = bankApplication;
@@ -25,21 +30,28 @@ public class AccountView extends JPanel {
this.setLayout(null);
accountName = new JLabel(account.getName());
accountName.setBounds(10, 10, 300, 30);
accountName.setBounds(10, 10, 600, 30);
balance = new JLabel(account.getBalance() + " kr");
balance.setBounds(10, 50, 300, 30);
balance.setBounds(10, 50, 600, 30);
AccountNumber = new JLabel(account.getId().toString());
AccountNumber.setBounds(10, 90, 300, 30);
accountNumber = new JLabel(account.getId().toString());
accountNumber.setBounds(10, 90, 600, 30);
transferButton = new JButton("Transfer");
transferButton.setBounds(10, 130, 300, 30);
transferButton.addActionListener(e -> {
TransferWindow transferWindow = new TransferWindow(bankApplication, account);
transferWindow.makeVisible();
});
transactionList = new InteractiveList<>();
transactionList.setBounds(10, 130, 300, 30);
transactionList.setBounds(10, 170, 300, 90);
transactionList.setBackground(Color.GRAY);
transactionList.setDrawManager(new DrawManager<Transaction>() {
public Component draw(InteractiveList<Transaction> interactiveList, Transaction value, int index, boolean isHovered, boolean isSelected) {
JButton label = new JButton();
label.setText(value.getDescription() + " " + value.getAmount() + " kr");
label.setText(value.getDescription() + " " + (value.getType() == TransactionType.WITHDRAW?"-":"+") + value.getAmount() + " kr");
label.setBackground(Color.GRAY);
label.addActionListener(e -> {
bankApplication.showTransaction(value);
@@ -48,18 +60,26 @@ public class AccountView extends JPanel {
}
});
backButton = new JButton("Back");
backButton.setBounds(0, getHeight()-50, 200, 30);
backButton.addActionListener(e -> bankApplication.returnFromAccount());
this.add(accountName);
this.add(balance);
this.add(AccountNumber);
this.add(accountNumber);
this.add(transactionList);
this.add(transferButton);
this.add(backButton);
}
@Override
protected void paintComponent(Graphics g) {
accountName.setBounds(10, 10, 200, 30);
balance.setBounds(10, 50, 200, 30);
AccountNumber.setBounds(10, 90, 200, 30);
transactionList.setBounds(10, 130, 200, 30);
accountName.setBounds(10, accountName.getY(), accountName.getWidth(), accountName.getHeight());
balance.setBounds(10, balance.getY(), balance.getWidth(), balance.getHeight());
accountNumber.setBounds(10, accountNumber.getY(), accountNumber.getWidth(), accountNumber.getHeight());
transactionList.setBounds(10, transactionList.getY(), transactionList.getWidth(), transactionList.getHeight());
backButton.setBounds(0, getHeight()-50, backButton.getWidth(), backButton.getHeight());
transferButton.setBounds(10, transferButton.getY(), transferButton.getWidth(), transferButton.getHeight());
transactionList.setList(account.getTransactions());
super.paintComponent(g);

View File

@@ -7,6 +7,8 @@ import me.zacharias.bank.transaction.Transaction;
import javax.swing.*;
import java.awt.*;
import static me.zacharias.bank.Utils.*;
public class Main extends JPanel {
public static void main(String[] args) {
new Main();
@@ -45,6 +47,10 @@ public class Main extends JPanel {
public void logout() {
frame.remove(mainManu);
mainManu.user.write();
mainManu = null;
accountView = null;
transactionView = null;
login.clear();
frame.add(login);
frame.setVisible(true);
@@ -69,4 +75,10 @@ public class Main extends JPanel {
frame.add(accountView);
frame.setVisible(true);
}
public void returnFromAccount() {
frame.remove(accountView);
frame.add(mainManu);
frame.setVisible(true);
}
}

View File

@@ -4,6 +4,7 @@ import me.zacharias.bank.Account;
import me.zacharias.bank.User;
import me.zacharias.bank.app.component.DrawManager;
import me.zacharias.bank.app.component.InteractiveList;
import org.json.JSONArray;
import javax.swing.*;
import javax.swing.border.LineBorder;
@@ -23,6 +24,8 @@ public class MainMenu extends JPanel {
this.user = user;
this.bankApplication = bankApplication;
user.update();
this.setLayout(null);
welcome = new JLabel("Welcome " + user.getName());

View File

@@ -1,6 +1,7 @@
package me.zacharias.bank.app;
import me.zacharias.bank.transaction.Transaction;
import me.zacharias.bank.transaction.TransactionType;
import javax.swing.*;
@@ -26,7 +27,7 @@ public class TransactionView extends JPanel {
transactionLabel = new JLabel("Description: "+transaction.getDescription());
transactionLabel.setBounds(0, 0, 500, 20);
amountLabel = new JLabel("Amount: "+transaction.getAmount());
amountLabel = new JLabel("Amount: "+(transaction.getType() == TransactionType.WITHDRAW?-transaction.getAmount():transaction.getAmount()));
amountLabel.setBounds(0, 20, 500, 20);
originatingAccountLabel = new JLabel("Originating account: "+transaction.getOriginator().toString());
@@ -50,6 +51,7 @@ public class TransactionView extends JPanel {
this.add(originatingAccountLabel);
this.add(destinationAccountLabel);
this.add(transactionDateLabel);
this.add(backButton);
}
}

View File

@@ -0,0 +1,184 @@
package me.zacharias.bank.app;
import me.zacharias.bank.Account;
import me.zacharias.bank.Utils;
import me.zacharias.bank.transaction.Transaction;
import me.zacharias.bank.transaction.TransactionType;
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.*;
import java.util.UUID;
public class TransferWindow extends JPanel {
Main bankApplication;
Account account;
JLabel accountName;
JLabel balance;
JLabel AccountNumber;
JTextField amount;
JTextField description;
JTextField destination;
JLabel warning;
JButton transferButton;
float finalAmount;
UUID finalDestination;
String finalDescription;
JFrame frame;
Timer t = new Timer(100, e -> frame.repaint());
public TransferWindow(Main bankApplication, Account account) {
this.bankApplication = bankApplication;
this.account = account;
frame = new JFrame();
frame.setSize(400, 300);
frame.setLocationRelativeTo(bankApplication);
frame.setTitle("Transfer");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(null);
accountName = new JLabel(account.getName());
accountName.setBounds(10, 10, 300, 30);
balance = new JLabel(account.getBalance() + " kr");
balance.setBounds(10, 50, 300, 30);
AccountNumber = new JLabel(account.getId().toString());
AccountNumber.setBounds(10, 90, 300, 30);
destination = new JTextField();
destination.setBounds(10, 130, 300, 30);
destination.setText("Destination");
amount = new JTextField();
amount.setBounds(10, 170, 300, 30);
amount.addActionListener((a) -> {
if(!amount.getText().matches("[0-9]*(.[0-9]{1,2})?")) {
amount.setBorder(new LineBorder(Color.RED));
}
if(Float.parseFloat(amount.getText()) < 0 || Float.parseFloat(amount.getText()) > account.getBalance()) {
amount.setBorder(new LineBorder(Color.RED));
}
else {
amount.setBorder(new LineBorder(Color.BLACK));
}
});
amount.setBorder(new LineBorder(Color.BLACK));
amount.setText("Amount");
description = new JTextField();
description.setBounds(10, 200, 300, 30);
description.setText("Description");
warning = new JLabel("Warning: This will transfer the amount to the destination account. The amount will be deducted from the source account. If the destination account does not exist, we cannot guarantee that the amount will be returned.");
warning.setForeground(Color.RED);
warning.setBounds(10, 240, 300, 30);
transferButton = new JButton("Transfer");
transferButton.setBounds(10, 270, 300, 30);
transferButton.addActionListener(e -> {
if(amount.getText().matches("[0-9]*(.[0-9]{1,2})?") && Float.parseFloat(amount.getText()) > 0 && Float.parseFloat(amount.getText()) <= account.getBalance()) {
finalAmount = Float.parseFloat(amount.getText());
}
else
{
amount.setBorder(new LineBorder(Color.RED));
return;
}
if(destination.getText().matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) {
finalDestination = UUID.fromString(destination.getText());
}
else
{
destination.setBorder(new LineBorder(Color.RED));
return;
}
if(!description.getText().isEmpty()) {
finalDescription = description.getText();
}
else
{
description.setBorder(new LineBorder(Color.RED));
return;
}
float finalAmount = this.getAmount();
String finalDescription = this.getDescription();
UUID finalDestination = this.getDestination();
if(finalAmount > 0 && finalAmount <= account.getBalance() && finalDestination != null) {
Transaction transaction = account.WithdrawTransaction(finalAmount, finalDescription, finalDestination);
Transaction t2 = transaction.copy(TransactionType.DEPOSIT);
Utils.AddTrnsationQue(t2);
}
frame.dispose();
});
this.add(accountName);
this.add(balance);
this.add(AccountNumber);
this.add(amount);
this.add(description);
this.add(transferButton);
this.add(destination);
this.add(warning);
frame.add(this);
frame.setVisible(true);
t.start();
}
@Override
protected void paintComponent(Graphics g) {
accountName.setBounds(10, 10, accountName.getWidth(), accountName.getHeight());
balance.setBounds(10, 50, balance.getWidth(), balance.getHeight());
AccountNumber.setBounds(10, 90, AccountNumber.getWidth(), AccountNumber.getHeight());
destination.setBounds(10, 130, destination.getWidth(), destination.getHeight());
amount.setBounds(10, 170, amount.getWidth(), amount.getHeight());
description.setBounds(10, 200, description.getWidth(), description.getHeight());
warning.setBounds(10, 240, warning.getWidth(), warning.getHeight());
transferButton.setBounds(10, 270, transferButton.getWidth(), transferButton.getHeight());
super.paintComponents(g);
}
public void requestFocus() {
//this.requestFocus(FocusEvent.Cause.ACTIVATION);
}
public boolean isOpen() {
return frame.isVisible();
}
public String getDescription() {
return finalDescription;
}
public float getAmount() {
return finalAmount;
}
public UUID getDestination() {
return finalDestination;
}
public void makeVisible() {
frame.setVisible(true);
}
}

View File

@@ -45,4 +45,8 @@ public class Transaction {
public String getDate() {
return date;
}
public Transaction copy(TransactionType transactionType) {
return new Transaction(amount, description, destination, originator, transactionType);
}
}