This project aims to create a backend system that mocks payment services. It provides a suite of APIs that can be used for user authentication, payment, and other services.
Fig 1. An overview of the backend system
The web layer was separated from the application layer so that they could be scaled independently. The application layer handles all the business logic; on the other hand, the web layer handles all the user requests. Both layers communicate with each other using a custom communication protocol.
Techstack
- Programming Language:
Golang
. - Development:
MySQL
,Redis
,Protobuf
,Nginx
,Docker
.
Component | Description |
---|---|
Web server |
handles clients' http requests |
Application server |
handling all the business logic such as authentication, payment and user accounts. |
MySQL Database |
Stores the user profile, credentials and transactions |
Redis |
Stores the cached user profile |
Nginx |
Used as a load balancer for the web server. |
Prerequisites: Install Docker
-
Build the required images
make build
-
Start the services
make prod
Available API Endpoints
📥 Import the postman collection using https://www.getpostman.com/collections/80407f7a54bcb47a552c
POST /v1/login
Request body:
{
"email": " <email>",
"password": " <password>"
}
Response:
The JWT token
is stored as a cookie in the response.
{
"status": {
"code": " <code>",
"message": " <message>"
}
}
POST /v1/signup
Request body:
{
"first_name": " <first_name>",
"middle_name": " <middle_name>",
"last_name": " <last_name>",
"email": " <email>",
"phone": " <phone>",
"password": " <password>"
}
Response:
{
"status": {
"code": " <code>",
"message": " <message>"
}
}
GET /v1/account
Response:
{
"user": {
"user_id": " <user_id>",
"first_name": " <first_name>",
"middle_name": " <middle_name>",
"last_name": " <last_name>",
"email": " <email>",
"phone": " <phone>",
"balance": " <balance>",
"created_at": " <created_at>",
"updated_at": " <updated_at>"
},
"status": {
"code": " <code>",
"message": " <message>"
}
}
POST /v1/pay
Request body:
{
"receiver_email": "<receiver_email>",
"amount": " <amount>"
}
Response:
{
"transaction_id": "<transaction_id>",
"status": {
"code": " <code>",
"message": " <message>"
}
}
POST /v1/topup
Request body:
{
"amount": " <amount>"
}
Response:
{
"status": {
"code": " <code>",
"message": " <message>"
}
}
GET /v1/transactions
Response:
{
"transactions": [
{
"transaction_id": " <transaction_id>",
"sender_email": " <sender_email>",
"receiver_email": " <receiver_email>",
"amount": " <amount>",
"created_at": " <created_at>"
}
],
"status": {
"code": " <code>",
"message": " <message>"
}
}
Tables: users
, transactions
, credentials
Table | Columns |
---|---|
users | user_id, first_name, middle_name, last_name, email, phone, balance, created_at, updated_at |
transactions | transaction_id, sender_id, sender_email, receiver_id, receiver_email, amount, created_at |
credentials | credential_id, user_id, email, password, updated_at, last_login |
Stack: MySQL
, Redis
Redis
is used to cache the user data of authenticated tokens.
API | Method | Description |
---|---|---|
/v1/login |
POST |
Authenticates the user and returns a token |
/v1/signup |
POST |
Register a new user |
/v1/account |
GET |
Gets a user profile |
/v1/pay |
POST |
Makes a payment to another user |
/v1/topup |
POST |
Tops up a user's account balance |
/v1/transactions |
GET |
Get all the transactions made by a user |
Stack: Golang
, Nginx
The following table list of commands supported by the protocol.
Command | Value | Function |
---|---|---|
LGN |
0 | Authenticates the user and returns a token |
SGN |
1 | Register a new user |
USR |
2 | Gets a user profile |
PAY |
3 | Makes a payment to another user |
TPU |
4 | Top up a user's balance |
TXQ |
5 | Get all the transactions made by a user |
The data is encoded in the following way:
-
The header is of size 4 bytes; This encodes the length of the payload.
- The payload is of size
<length>
bytes; This encodes the command and the data.
- The payload is of size
-
Allocate a buffer of size
<length>
bytes and read the data into it.
Furthermore, protocol buffers
were used to serialize the data.
Stack: Golang
, Protobuf
Application server cannot connect to the MySQL database
- It was caused by the difference in startup time between the application server and the database server.
- By the time database starts, the application server is already running and has pinged the database server.
- Solution: To solve it I used
wait-for
package to listen to SQL server until it is ready and then start the application server.
Redis set key issue
- Problem: Although the redis key is set, it was not found in later calls.
- I later realised that the
ttl
for the key was set to1 second
; This was because, ttl is set based on the expiry time of the jwt token, however, thetime.Duration(ExpiryTime)
was not being converted corrently. - Solution: I converted the
ExpiryTime
fromint64
to time stamp and later usedtime.Until(TimeStamp)
to set thettl
and