Suraj Vijay
Suraj's Blog

Suraj's Blog

Everything about Project Payzer

Everything about Project Payzer

Payzer - Cross platform mobile app for payments

Suraj Vijay's photo
Suraj Vijay
·May 22, 2022·

15 min read

Play this article

Table of contents

  • Tech Stack
  • Database
  • REST API Server
  • File and Web Socket Server
  • Cross Platform App
  • Source Code
  • The Team

Payzer is a cross platform mobile app operating an online payments system in the majority of countries that support online money transfers, and serves as an electronic alternative to traditional paper methods such as checks and money orders. Payzer allows user to send money from one country to another country using phone number without involving any banks directly and payout fees

image.png

Upon creating an account in Payzer the user can deposit money to his/her Payzer account directly from their bank accounts and crypto wallets. In case of crypto wallets, the crypto currency is converted into user’s respective currency. The user can have up to three cards associated with their Payzer account thereby managing the money with three cards rather than having all the money in one single card. Every single transaction made with Payzer is Secure, Fast and Reliable

Tech Stack

Talking about the tech stack, Payzer has got three hearts, namely the REST API Server, File and Web Sockets Server and the Cross Platform Mobile App and Survival of Payzer would be difficult even if one of the service fails. So we made that either of the services are having a strong foundation. The tech stack is as follows:

  • Typescript
  • Java
  • Objective-C
  • JavaScript
  • React Native
  • NodeJs
  • ExpressJs
  • JOI
  • Cloudinary API
  • Multer
  • Morgan
  • SocketIO
  • Redis
  • PostgreSQL
  • JSONWebToken
  • KnexJs
  • HelmetJs
  • BcryptJs
  • PlpgSQL
  • Redux
  • Nodemon
  • Twilio API

The UI using Adobe Photoshop and UX using LucidChart. Followed by designing the Database models. We then started working with the REST API Server and tested each endpoint with the help of Postman. After successful testing of the REST API Server, we considered developing the File and Web Sockets Server before getting started with the React Native App. After completion and testing of the File and Web Sockets Server, we planned to create the cross platform app using React Native

image.png

We shall now take a look at each of the hearts of Payzer in depth

Database

Payzer uses PostgreSQL and Redis for managing user’s data apart from user’s media. We never regret choosing SQL over NoSQL because we felt that what the data seen by users has to be consistent, moreover we wanted the data to be available all time to the user regardless of the place and location. In a nutshell, we preferred Availability and Consistence there by trading off Partition Tolerance. When a user’s transactions fails due to some unavoidable circumstances we would like to roll back the entire transaction. This rolling back feature is not provided by NoSQL, so this adds to another reason why we preferred SQL over NoSQL. We also have some complex queries to be executed in fraction of time, each table has at least one trigger that updates the values in the other table thereby reducing the burden at our server side code. Due to these two reasons we chose PostgreSQL over MySQL. Moreover PostgreSQL provides event streaming that helps us to LISTEN to changes in data and NOTIFY us about the query. With the help of this we were able to send the user a text message whenever they receive or send money by listening to the event through web socket connection from our REST API Server which would emit a signal to our File and Web Socket Server that sends the text message to client with the help of TWILIO API. Below is Payer’s Database model

image.png

Every single table other than Redis are connected to each other with an relation, so we had to create at least one trigger for each table that will mutate the data that has been related to other data in another table. For example, one of our triggers does the following, when a row is added to the transactions table, a trigger would update the users balance and card balance in the users and cards table respectively. We also have user defined functions that helps us while working with triggers, as well as when querying the data. Obviously we have functions for every table that increases performance when querying and also reduces the code in the server. For example, one of our function helps us to query all transactions between two users. Similarly we have functions to query all transactions made with a specific card, made by a specific user and etc. You can find the set of all functions, triggers and foundational queries from this file - psql.config.sql

Now let’s see why we have Redis even though it does not contribute to storing the user’s data. Yes, you heard it right, REDIS has got nothing to do with user’s data. When we designed our database we didn’t have Redis at first. We also finished our work with REST API Server and File and Web Sockets Server and we still didn’t have Redis. So when did Redis enter the game?

Before starting to code the cross platform app with React Native we wanted to ensure that both of our REST API Server as well as our File and Web Sockets Server is secure from DOS attacks, DDOS attacks, XSS attacks and SQL injections. We first ensured that responses from our servers doesn’t include any information other than what the client requested for. We prevented DOS attacks by server level rate limiting both of our servers. But that wasn’t enough, we had to secure the servers from DDOS attacks as well. Server level rate limiting failed when the requests are coming from NATED-IP. So we had to token level rate limit each user in order to secure from DDOS attacks. We first had a table for token level rate limiting within PostgreSQL that had three columns namely user_id, count and created_at. It was good at first, we were able to benchmark and find the perfect window time and no of requests per window to allow. But we had an issue by having the table within PostgreSQL. The lookup was not that fast and updating and checking the created_at column was burden in our server side codes. So we moved the table for token level rate limiting outside PostgreSQL. In Redis, the key was the id of the user and value was requests left for the user for the given window time. Using the time to live feature provided by Redis we were able to reset the requests count whenever the window time expires. Thus we reduced the burden at our PostgreSQL instance and also reduced the look up at time thereby increasing the performance massively

In the future, when our active users increase, we have planned to move the transactions table outside PostgreSQL to Cassandra. The main reason is that when there are more and more users, there are going to be more and more transactions. So in order to query the transactions for any specific user or card using primary key as one column will not be sufficient. So we consider creating a tabular database that is partitioned by sender_id and receiver_id and clustered by created_at. So when we choose Cassandra, we are opting for Availability and Partition Tolerance thereby trading off Consistency. We are not completely loosing Consistency here, we still have eventual consistence since we are using Cassandra combined with PostgreSQL

REST API Server

Payzer’s backend is written in JavaScript with NodeJs. We created everything from the scratch including middlewares for verifying the users via tokens with the help of JSON Web Token package. We had to write the code for rate limiting as well, since we wanted it be a flexible in the future. Apart from custom middlewares, we had to use some libraries like Morgan, Helmet and ExpressJSON. Morgan helped us in logging the foundational information on the server side for each request, Helmet secured our response headers and ExpressJSON parsed every single incoming requests with JSON payload. Without simply putting in Morgan and Hemet into use, we had to configure them a bit according to our requirements. Since our end product, Payzer, is a cross platform mobile app, we wanted to ensure that our servers responds only to requests from mobile apps. We were able to block requests from any site by not including any site in our CORS (Cross Origin Resource Sharing) policy. But we still had to make sure that the requests are coming from Payzer and not from any other mobile app. In order to ensure this we put to use the SSL pinning in our React Native App which was provided as one of the security measures from React Native

image.png

Our tokenRateLimiter middleware uses Redis client to communicate with the Redis server. The Redis client provided us with a query builder, which prevented us from writing queries. verifyUser middleware checks whether the whitelisted routes have authorization token, which is generated once the user creates an account or login to their account. If the token is missing or not valid or expired, we cancel the request with appropriate message. Moreover, with the help of this token we were able to verify user’s session, thereby ensuring that the user is using the app only from one device. We had to do this, in order to avoid concurrency caused by multiple devices of user trying to perform different actions that would make the user’s data ambiguous in our database

Now we shall take a look at how we validate the user’s payload, i.e. the header and the body bounded in the request that came from the app. We had to validate every single request, regardless of which route they were trying to reach. We put to use the JOI library, which helped us in creating schemas and validating the schemas for all the routes. We‘ll first provide JOI with the schema (shape of data) which we are expecting to be present in the body, JOI then compares both, the payload provided by the user and our schema and binds whether the payload is valid or not to the incoming requests. You can find the set of all schemas that we wrote for JOI from this directory - payzer-v2-rest-api/src/utils/joi/*.joi.js

We have five parent routes that serves different purposes. We have the validation route, which will be used by our team as well as server monitoring software to ensure whether our service is available or not. If the service is not available, we stop taking requests from users by blocking their requests in the Payzer app itself. The server monitors also help us to know more about what went wrong

Through auth route, we perform authorizations such as verifying the user, which will send a token if they are trying to login, if they have already logged in, we send an error response. The auth route also provides routes for generating refresh token, which is done once a day by the app whenever the user opens the app. The logout route validates the user and then removes their session from our database, thereby invalidating any request sent with the old token. Apart from these, auth has two other routes which talks with the Twilio SMS service for sending and validating the OTP (One Time Password) sent to the mobile number from which the user requested

Via the user route, we handle requests that directly involves with the users table, such as creating a new user (i.e. inserting a new row into the users table), querying the user with their unique ID or with their phone number. Updating the user is also done via this route, since we let the user to update their name and profile picture. Querying the users with a keyword such as phone number and name was the last route which we added, it serves the user with array of objects containing user_id, name, profile_picture and phone_number which matched the given keyword

With the help of cards route, we manage to handle requests from the user that deals with the cards table, such as querying all the cards of the user, adding a new card (i.e. inserting a row into the cards table). This route also aids in updating the user’s credit and debit balance. Getting details of a specific card was the last route we added to the cards route

As we discussed earlier about triggers for each table, transactions table has got more triggers and each trigger is pretty complex than the rest of the triggers of other tables. It allowed us to deduce the burden at the transactions route, since all the routes in the transaction route were just working with the functions and triggers that we wrote in PlpgSQL. So yes, transaction route handle requests such as querying all the transactions made by a card, a user and also the transactions between two users. These routes just queries from the transaction. But there is this route that mutates the transaction table as well as the cards and users tables. To know how we handle the requests tailored for our needs, feel free to take a look at the routes folder - payzer-v2-rest-api/src/routes/*.route.js

We talked more about the middlewares, routes and payload validation and we indeed talked about querying and updating data in the PostgreSQL. So how do we handle these querying and updating? We first used node-postgres which takes query from us and delivers it to the PostgreSQL instance. It was good. But we found it tedious to perform rollbacks when a transaction failed and also considered having a query builder that just takes query parameters and efficiently queries data within the PostgreSQL instance providing a schemaful output than just providing an array of rows like node-postgres. Taking into consideration that we would dockerise our entire backend, we wanted to have a pool where list of clients (connection clients and not the end user) can join and exit. Considering all these factors we added Knex to our project which works with help of node-postgres under the hood

Whenever a new transaction is made (i.e. a row is inserted into the transactions table), PostgreSQL NOTIFY us with an event, which we LISTEN and send to the File and Web Sockets Server via an web socket connection, which will receive the necessary details to send an SMS to the end user’s mobile phone with the help of Twilio API. At first we thought of sending a push notification to the user, but sending a text message was more formal and cheaper compared with the push notification expense

In the future, when our active users increase, we will considering adding new features and new security measures to our existing code base. So, we would consider moving to Typescript from JavaScript, as it helps in code maintainability and provides a good intelisense, thereby reducing bugs in the production version and also makes debugging easy

File and Web Socket Server

Payzer deals with media such as user profile pictures and also use web sockets along with traditional fetch calls to talk within the servers (i.e. Communication between the File and Web Sockets Server and the REST API Server). Apart from this, the codebase is similar to our REST API Server. The major difference is that we have not token level rate limited and verified our user because the requests are coming from the REST API not from our cross platform app. But we still have server level rate limited the server due to some obvious reason which we are not comfortable sharing it here (since we really worked hard in benchmarking some stuff). In addition we have, the Multer middleware which extracts the image from form data and provides us as object. We then use one of our function that converts the image into URI string. Once we have the URI string, we upload it to the Cloudinary server and we return back the secure public URL of the uploaded image through response. If the user is planning to update their profile picture, we add their new image to the CDN and delete their old image. By doing this we were able to save some space

image.png

There two parent routes, namely validation route and images route. As we mentioned earlier the validation route works the same as how it works in the REST API Server. The key route here is the images route, which handles requests such as uploading and deleting the user from the CDN

Since Payzer is handling few users, we have planned to stick with Cloudinary. Once the user base grows and our servers are running in the cloud such as AWS or Digital Ocean (we have decided not to go with Azure and GCP as well), we will consider shifting the images from Cloudinary to the respective cloud provider we have opted for

Cross Platform App

Payzer’s frontend (i.e. the end user app) is made with bare React Native template (not EXPO managed) and written in Typescript. We preferred Typescript over JavaScript for multiple reasons. One of the main reason is we have wrote a lot of components that are reusable in various parts of the app. As we add more and more components remembering the props for each of the components was difficult. With the help of Typescript we were able to overcome this problem as Typescript provided us with out of the box integration to use the types which we wrote for each components. Apart from this, we were able to provide schema for the responses that came from API call

image.png

We manage the navigation between the screens using React Navigation library. For managing the state of the app, we have put together both Redux as well as Context API in order to avoid boilerplate code as well as to manage and persist the state comfortably. In order to store the authorization token securely, we preferred using react-native-encrypted storage library rather than writing it on our own from the scratch. We still wrote some Java and Objective-C code in order to make use of the native features provided by Android and IOS

Upon bundling, the styles get combined and some of the styles might overlap with each other. We used styled-components to avoid this. One of our main Context API is the Theme Context API which detects the user’s system theme and sets that as the default theme for the app. It also helped us in toggling the theme manually. We also have written few more Contexts which you can find from this directory - payzer/src/context/*.context.tsx

We used the community built package react-native-image-picker which allows the user to pick image from their gallery or take a picture using their camera and returns the metadata of the image along with the temporary location of the image in the phone. In order to validate the phone number provided by the user, we made use of the react-native-phone-number-input (which is a community built package)

Before deploying into production, we would go about obfuscating the code base to ensure that sensitive parts of the app is not revealed when the bundled file is analyzed. In order to securely talk with our servers, we will go about SSL pinning provided by the React Native

In the future, we would go about ejecting the code into two code bases, where one would be for Android and another for IOS which would help us in adding more and more native features and also increases the app performance too. In this process we would no longer be able to use typescript or JavaScript in our code, but yes, the tradeoff is worth the benefit

Source Code

REST API Server – github/payzer-v2-rest-api File and Web Sockets Server – github/payzer-v2-server Cross Platform App - github/payzer

The Team

 
Share this