LLD | Design a Payment Gateway

Design a Payment Gateway

Payments is an integral component of any e-commerce or fintech. With the advent of digital india, we have seen different types of payments ecosystem rising up - Payments gateways, UPI, Rupay Network etc. These are easy to integrate and any org can get started with this in a matter of days.

With the onset of different payment players, organizations integrate different payment gateways and shuffle between them to suit their use cases best.

Based on use cases, customers can opt for payment via card / upi / net banking etc. Below is the general runbook for making a payment.

  • Select a pay mode
  • Enter details
    • For netbanking - enter username and password
    • For credit / debit card - enter card details ( number, expiry, cvv etc )
    • UPI - enter vpa

Implement a payments gateway which help facilitate a payment for its client, below points should be kept in mind while implementation

Real world example

Flipkart has integrated multiple PGs like Razorpay, Citrus, PaySafe, CCAvenue etc. They use these PGs based on different use cases. For example, divert all credit card transaction to RazorPay while Netbanking goes to CCAvenue.

These PGs internally have direct integration with different banks which facilitate the payments.

Expectation from this code is to build PG like a Razorpay / CCAvenue which allows onboarding clients like Flipkart and process a transaction.

Client in this case is Flipkart (PG can have more than one clients )PG is what candidate has to implement PG can have more than one bank, candidates are free to implement a mock or full fledged class

Feature Requirement:

  • PG should support onboarding multiple clients.
  • PG should have multiple bank integrations (like HDFC, ICICI, SBI etc.)
  • PG should have facility to support different payment methods - UPI, Credit / Debit Card, Netbanking etc
  • PG should have facility to divert to specific bank based on certain criteria - a router basically - (for e.g. all credit card transaction goes to HDFC and net banking is redirected to ICICI)
  • PG should have facility to allocate specific percentage between different banks - say bank1 takes 30% of total traffic while remaining 70% go to bank2
  • Clients should have an option to opt for specific payment methods. - only UPI, everything except netbanking etc

**Assumptions: **

  • Banks can randomly return success / failure - candidates can create a random function to mock this behavior.
  • Payments should be processed using an instrument only if specific parameter for that payment is passed - net banking might need user id / password but credit card will only work with card details
  • Banks require OTP verification after instrument details are verified (applicable for net banking / card transaction ) for sake of simplicity, transactions will go through without OTP.

Code Expectation:

  • Everything has to be in memory.
  • Candidate can opt for any language for implementation
  • Simple and basic function are expected as entry point - no marks for providing fancy restful API or framework implementation
  • Because this is a machine coding round, heavy focus would be given on code quality, candidate should not focus too much time on algo which compromises with implementation time

Requirements - below are various functions that should be supported with necessary parameters passed

  • addClient() - add a client in PG
  • removeClinet() - remove client from PG
  • hasClient() - does a client exist in the PG?
  • listSupportedPaymodes() - show paymodes support by PG. if a client is passed as parameter, all supported paymodes for the clients should be shown.
  • addSupportForPaymode() - add paymodes support in the PG. If a client is passed as parameter, add paymode for the clients.
  • removePaymode() - remove paymode ( both from PG or client basis parameter)
  • showDistribution() - show active distribution percentage of router
  • makePayment( //necessary payment details )

Evaluation criteria:

  • Working code
  • Code readability
  • Implementation of OOPs / OOD principles with proper Separation of concerns
  • Testability - a TDD approach ( not to be mixed with test cases )
  • Language proficiency
  • Test Cases ( bonus points )

Bank Class

class Bank:
    def __init__(self, bankName, support):
        self.name = bankName
        self.support = support.split(" ")
    def runTransaction(self):
        from random import randint
        return randint(0,1)
    ```
	
**Client Class**
	```
	class Clients:
    def __init__(self,clientName):
        self.clientName = clientName
        self.paymentModes = []
        self.balance = 0
    def listSupportedPayModes(self):
        #function to show payment modes supported by a client
        print("Payment modes supported by ", self.clientName, " are : ", self.paymentModes)
        
    def addSupportForPayModes(self, mode):
        #function to add payment modes to a client
        self.paymentModes.append(mode)
        print("Payment mode ", mode, " added to client: ", self.clientName)

    def removePayModeForClient(self,mode):
        #function to remove payment modes from a client
        self.paymentModes.remove(self.paymentModes.index(mode))
        print("Payment mode ", mode, " removed from client: ", self.clientName)

    def addAmount(self, amount):
        self.balance += amount
    
    def balanceAmount(self):
        return self.balance
		```
	
**Payment Gateway Class**
	

class paymentGateway:

def __init__(self):
    self.clients = {}
    self.transactions = []
    
def addClient(self, clientName):
    #function to create new client in the payment gateway
    c1 = Clients(clientName)
    self.clients[clientName] = c1
def showClients(self):
    print("==============================================")
    print("Clients of the Payment Gateway")
    print("==============================================")
    print(self.clients.keys())
    print("==============================================")
    print("self.clients: ", self.clients)
    
def removeClient(self, clientName):
    #function ot remove client from the payment gateway
    del self.clients[clientName]
    
def listSupportedPayModesClient(self, clientName):
    temp = self.clients[clientName]
    temp.listSupportedPayModes()
def addSupportedPayModesClient(self, clientName,pyMode):
    temp = self.clients[clientName]
    temp.addSupportForPayModes(pyMode)
def removePayModeForClient1(self, clientName, pyMode):
    temp = self.clients[clientName]
    temp.removePayModeForClient(pyMode)

def makePayment(self,details):
    #function to perform payments
    pType = details['type']
    if pType == "UPI":
        temp = {
            "pType": "UPI",
            "for_client": details['client_name'],
            "amount": details['amount'],
            "bank_name": "SBI"
        }
        
        if details['client_name'] in self.clients.keys() and pType in self.clients[details['client_name']].paymentModes:
            bankName = Bank("SBI", "UPI")
            status = bankName.runTransaction()
            details['bank_name'] = bankName.name
            details['temp'] = temp
            if status == 1:
                self.UPITransaction(details)
            else:
                print("Transaction failed due to bank issue")
            
        else:
            print("Transaction Could not be completed because client name was invalid")

    if pType == "Netbanking":
        temp = {
            "pType": "Netbanking",
            "for_client": details['client_name'],
            "amount": details['amount'],
            "bank_name": "HDFC"
        }
        
        if details['client_name'] in self.clients.keys() and pType in self.clients[details['client_name']].paymentModes:
            bankName = Bank("HDFC", "Netbanking")
            status = bankName.runTransaction()
            details['bank_name'] = bankName.name
            details['temp'] = temp
            if status == 1:
                self.netBankingTransaction(details)
            else:
                print("Transaction failed due to bank issue")
        else:
            print("Transaction Could not be completed because client name or payment mode was invalid")

def UPITransaction(self, details):
    if 'upi_pin' in details.keys():
        self.clients[details['client_name']].addAmount(details['amount'])
        print("---------------------------------------")
        print("UPI Transaction of amount ", details['amount'], " performed by ", details['bank_name'], " for client ", details['client_name'])
        print("---------------------------------------")

        print("===========================================")
        print("Updated balance of client ", details['client_name'], " is : ", self.clients[details['client_name']].balanceAmount())
        print("===========================================")
        self.transactions.append(details['temp'])
    else:
        print("TRANSACTION FAILED DUE TO INCORRECT DETAILS SUPPLIED TO UPI Transaction")

def netBankingTransaction(self, details):
    if 'user_id' in details.keys() and 'password' in details.keys():
        self.clients[details['client_name']].addAmount(details['amount'])
        print("---------------------------------------")
        print("Netbanking Transaction of amount ", details['amount'], " performed by ", details['bank_name'], " for client ", details['client_name'])
        print("---------------------------------------")

        print("===========================================")
        print("Updated balance of client ", details['client_name'], " is : ", self.clients[details['client_name']].balanceAmount())
        print("===========================================")
        self.transactions.append(details['temp'])
    else:
        print("INCORRECT DETAILS SUPPLIED TO Netbanking Transaction")

def viewTransactions(self):
    if len(self.transactions) > 0:
        for i in self.transactions:
            print("--------------------------------------------")
            print(i)
            print("--------------------------------------------")
    else:
        print("No transactions are performed on the gateway")

**Main Class**

from Clients import Clients
from Bank import Bank

from pg import paymentGateway

import time
start = time.time()
print("Program execution has started")

pg = paymentGateway()
pg.addClient("Flipkart")
pg.addClient("Amazon")
pg.showClients()

import sys


pg.addSupportedPayModesClient("Flipkart","UPI")
pg.addSupportedPayModesClient("Amazon","Netbanking")
pg.addSupportedPayModesClient("Amazon","UPI")
pg.listSupportedPayModesClient("Flipkart")
pg.listSupportedPayModesClient("Amazon")

details = {
    'type': 'UPI',
    'client_name': 'Flipkart',
    'amount': 120,
    'upi_pin': 1231
}

pg.makePayment(details)

details1 = {
    'type': 'UPI',
    'client_name': 'Flipkart',
    'amount': 1300
}
pg.makePayment(details1)

details2 = {
    'type': 'Netbanking',
    'client_name': 'Flipkart',
    'amount': 12,
    'user_id': "ab1",
    'password': 'ab123'
}
pg.makePayment(details2)

details3 = {
    'type': 'UPI',
    'client_name': 'Amazon',
    'amount': 2000
}
pg.makePayment(details3)

details4 = {
    'type': 'Netbanking',
    'client_name': 'Amazon',
    'amount': 100,
    'password': 'ab123'
}
pg.makePayment(details4)

pg.viewTransactions()

print("==========================================================")
print("Program Execution took: ", (time.time() - start))
print("==========================================================")
Comments (2)