/Shillings

A backend system that mocks payment services. It provides a suite of APIs that can be used for user authentication, payment, and other services.

Primary LanguageGoMIT LicenseMIT

Shillings

A. Overview

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.

B. System components

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.

C. Installation

Prerequisites: Install Docker

  1. Build the required images

    make build
  2. Start the services

    make prod

D. Usage

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>"
    }
}

E. Technical details

1. Database

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.

2. Web Layer

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

3. Application Layer

3a. Custom communication protocol

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.
  • 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

F. Interesting Situations

  • 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 to 1 second; This was because, ttl is set based on the expiry time of the jwt token, however, the time.Duration(ExpiryTime) was not being converted corrently.
    • Solution: I converted the ExpiryTime from int64 to time stamp and later used time.Until(TimeStamp) to set the ttl and