Golang Course With Building a Fintech Banking App - Lesson 5: Bank Transactions PART 2

by Radek Fabisiak

Intro to Golang course Bank transactions

In Lesson 5 of the Golang course, we will continue with banking transactions.

In the previous episodes of the Course, we learned how to do migrations:
Golang course with building a fintech banking app – Lesson 1: Start the project

We learned how to do user login:
Golang course with building a fintech banking app – Lesson 2: Login and REST API

We learned how to do user registration:
Golang course with building a fintech banking app – Lesson 3: User registration

And we built user authentication, and started with transactions:
Golang Course With Building a Fintech Banking App – Lesson 4: User Authentication and Bank Transactions PART 1

As well you need to remember about Angular 9 course created by my friend Anna:
Angular Course with building a banking application with Tailwind CSS – Lesson 1: Start the project

Today, we will focus on the next part of the bank transactions.

We will build a new module named transactions that we will use to create transaction history, but not only!

Like the rest of today’s lesson, we will do a bit refactoring, create a logic of the making bank transfers, and handle new API endpoints.

Let’s start!

If you prefer video, here is the youtube version:

Create a transaction interface

As the first step, we need to prepare some interface of transactions that we will use for DB-related logic.

To do that, we need to go into the interfaces.go file, and create struct named “Transaction”.

That element should contain these props: gorm.Model, From as uint, To as uint and Amount as int.

Take a look at the example:

type Transaction struct {
  gorm.Model
  From uint
  To uint
  Amount int
}

Create function MigrateTransactions in migrations.go

In the next step, we should focus on implementing transactions inside the database.

To do that, we could update the function named “Migrate”, and even it is a better way of doing it.

But in this case, I will create a separate function not to mix your migrations with previous ones.

We need to go into the migrations.go file and create a function named “MigrateTransactions”.

Inside that function, we should create DB-migration logic, but related to interface named “Transactions”.

func MigrateTransactions() {
	Transactions := &interfaces.Transaction{}

	db := helpers.ConnectDB()
	db.AutoMigrate(&Transactions)
	defer db.Close()
}

Create function getAccount

The next logic that we will need is a function that will find an account by ID and return it.

To do that, as the first move, we need to go into the useraccounts.go and create a function named “getAccount”.

Inside the function, we need to connect with DB, find for the account by id, and return.

func getAccount(id uint) *interfaces.Account{
	db := helpers.ConnectDB()
	account := &interfaces.Account{}
	if db.Where("id = ? ", id).First(&account).RecordNotFound() {
		return nil
	}
	defer db.Close()
	return account
}

Create function Transaction

Next, we can go into the more exciting part of the lesson, the transaction.

To start working with that logic, we should stay in the same useraccounts.go file.

As the first step, we should create the function named “Transaction” with a few params, and response, as map[string]interface{}.

Params should be userId, from, and to, as uint, the amount should be int and jwt as a string.

func Transaction(userId uint, from uint, to uint, amount int, jwt string) map[string]interface{} {

}

Convert uint to string

The next part of the Transaction function should be an action that will convert userId from uint to string.

We do that only because ValidateToken takes userId as a string. We can refactor that later when we have more places that use validation and are not from the GET.

userIdString := fmt.Sprint(userId)

Validate jwt

As we did in the previous episode, we should validate the jwt token as well today.

Inside the function “Transaction” we need to create the same if/else statement with validation.

func Transaction(userId uint, from uint, to uint, amount int, jwt string) map[string]interface{} {
	userIdString := fmt.Sprint(userId)
	isValid := helpers.ValidateToken(userIdString, jwt)
	if isValid {

	} else {
		return map[string]interface{}{"message": "Not valid token"}
	}
}

Take sender and receiver

Next, we should take two accounts, both using the “getAccount” function that we created before. The first one is the sender account assigned to the variable named “fromAccount”.

The second one is the receiver assigned to the “toAccount”.

fromAccount := getAccount(from)
toAccount := getAccount(to)

Handle errors

As the next part, we should look for a few mistakes and verify if everything is fine.

The first and very important point is to verify if both accounts exist.

The next one is to verify if we are the owner of the account.

The last one is to check if we have enough money to create the transfer.

if fromAccount == nil || toAccount == nil {
  return map[string]interface{}{"message": "Account not found"}
} else if fromAccount.UserID != userId {
  return map[string]interface{}{"message": "You are not owner of the account"}
} else if int(fromAccount.Balance) < amount {
  return map[string]interface{}{"message": "Account balance is too small"}
}

Update account

All the logic verified if our data is fine, and we can do the transfer.

Next, we can send money.

To save in DB, we’ve sent money, and the receiver has got that, we should update our bank accounts.

We need to update our account and deduct the money from our balance.

And we should do the same with the receivers account with one small difference, we should increase his balance.

updatedAccount := updateAccount(from, int(fromAccount.Balance) - amount)
updateAccount(to, int(toAccount.Balance) + amount)

Create transaction

When the transfer is done, we should save the info about that somewhere.

To have the info about a transfer in our history, we need to create a transaction by calling the function “CreateTransaction”.

We will create that function in the next steps, now, just add call.

transactions.CreateTransaction(from, to, amount)

Return response

As the last step in the function “Transaction” is to prepare a response, and return that.

var response = map[string]interface{}{"message": "all is fine"}
response["data"] = updatedAccount
return response

Update function updateAccount

Until we’ll move from the useraccounts.go, we need to update the function named “updateAccount”.

First, we need to refactor the way we update the account, so we will have an easier way to manipulate it with updated data later.

Next, we should add some logic that will prepare the response of the account, and return that.

func updateAccount(id uint, amount int) interfaces.ResponseAccount {
	db := helpers.ConnectDB()
	account := interfaces.Account{}
	responseAcc := interfaces.ResponseAccount{}

	db.Where("id = ? ", id).First(&account)
	account.Balance = uint(amount)
	db.Save(&account)
	
	responseAcc.ID = account.ID
	responseAcc.Name = account.Name
	responseAcc.Balance = int(account.Balance)
	defer db.Close()
	return responseAcc
}

Create module transactions

As we finished with useraccounts.go, we can go into the module “transactions”.

First, we need to create a directory named “transactions”.

Inside the directory, we need to create a file with the same name and declare a package named “transactions” inside.

package transactions

import (
	"duomly.com/go-bank-backend/helpers"
	"duomly.com/go-bank-backend/interfaces"
)

Create function CreateTransaction

Next, inside the file transactions.go, we should create a function named “CreateTransaction”.

Inside the function, we need to add logic that will be responsible for creating a new record in the database.

func CreateTransaction(From uint, To uint, Amount int) {
	db := helpers.ConnectDB()
	transaction := &interfaces.Transaction{From: From, To: To, Amount: Amount}
	db.Create(&transaction)

	defer db.Close()
}

Create interface TransactionBody in api.go

In this step, we can go into the api.go, that will be the last file, where we develop code.

As the first step in that file, we should create an interface named “TransactionBody”.

That interface will be responsible for the body of the transaction call.

type TransactionBody struct {
	UserId uint
	From uint
	To uint
	Amount int
}

Refactor apiResponse

Next, we need to do one small change inside our apiResponse.

We should return the whole call variable instead of “data”.

func apiResponse(call map[string]interface{}, w http.ResponseWriter) {
	if call["message"] == "all is fine" {
		resp := call
		json.NewEncoder(w).Encode(resp)
	} else {
		resp := call
		json.NewEncoder(w).Encode(resp)
	}
}

Create function transaction

In the last step of creating the logic, we need to handle the API call.

To do that, we should create a function named “transaction”.

Almost all of the logic will be similar to the rest of the API calls. Still, in this case, we need to remember about handle the auth and use the interface that we created before.

func transaction(w http.ResponseWriter, r *http.Request) {
	body := readBody(r)
	auth := r.Header.Get("Authorization")
	var formattedBody TransactionBody
	err := json.Unmarshal(body, &formattedBody)
	helpers.HandleErr(err)

	transaction := useraccounts.Transaction(formattedBody.UserId, formattedBody.From, formattedBody.To, formattedBody.Amount, auth)
	apiResponse(transaction, w)
}

Handle endpoint in a router

Now it left only to handle the “/transaction” endpoint in the routing.

Pass “transaction” function, and use the method “POST”.

func StartApi() {
	router := mux.NewRouter()
	router.Use(helpers.PanicHandler)
	router.HandleFunc("/login", login).Methods("POST")
	router.HandleFunc("/register", register).Methods("POST")
	router.HandleFunc("/transaction", transaction).Methods("POST")
	router.HandleFunc("/user/{id}", getUser).Methods("GET")
	fmt.Println("App is working on port :8888")
	log.Fatal(http.ListenAndServe(":8888", router))
}

Do migration

All the code is ready!

Now we can start migrating.

As we did in the first lesson, we need to go into the main.go and add call “MigrateTransactions” in the “main” function.

Don’t forget to comment „api.StartApi()”.

import "duomly.com/go-bank-backend/migrations"

func main() {
	migrations.MigrateTransactions()
	// api.StartApi()
}

Conclusion

Congratulations, you can do the first bank transfer now!

I’m super excited your project’s features are much more advanced now, and your project looks much more complete.

Here is the code repository for the current lesson:
https://github.com/Duomly/go-bank-backend/tree/Golang-course-Lesson-5

In the next episode, we will focus on performance optimization and database connections refactoring.

That will give us much better performance, eliminate some dangers like DB kills on overcall, and help us to make the application much more stable.

Stay updated, and follow us because I cannot wait to build more code with you!

Thanks for reading, Radek from Duomly

Radek Fabisiak

If you liked it share and comment!