Skip to content

Instantly share code, notes, and snippets.

@beisong7
Last active July 21, 2020 15:14
Show Gist options
  • Save beisong7/2bdffeb226aad55a7c9b49905a9f0b7f to your computer and use it in GitHub Desktop.
Save beisong7/2bdffeb226aad55a7c9b49905a9f0b7f to your computer and use it in GitHub Desktop.
API to handle Funds Transfer Approach

Imagine you are asked to develop a transfer service with APIs to transfer money between two accounts You application is expected to have the following database structure

TABLE transactions

  • reference (unique)
  • amount
  • account nr

TABLE balances

  • account nr (unique)
  • balance The transaction table registers any transaction in an account (ie. today I paid N2000 for a movie with my card), the balances table represents the account balance of customers (ie. I have N50k in my bank account).

Assume that the database is a relational database like MYSQL

You are expected to have a near production ready code.

The API you are to develop should be able to handle a transfer request of the form {from: account, to: account, amount: money} and updates the transactions / balances table accordingly.

A few things to keep in mind:

you will receive requests from mobile app, that customers use to transfer money around can you deal with the fact that one customer might tap on the "transfer" button twice, by mistake? what happens if your database becomes unavailable in the middle of your logic? what happens if 2 users (A, B) transfer money to user C at the same time? how will you handle transaction validity and consistency the only thing you will not need to validate is that the user is authorized to perform this API request. let's assume the user is logged in and carries a cookie, with the transfer request, that ensures it comes from a legit source

<?php
namespace App\Http\Controllers;
use App\Services\ApiService;
use Illuminate\Http\Request;
class APIController
{
protected $service;
public function __construct(ApiService $service)
{
$this->service = $service;
}
public function HandleRequest(Request $request){
/**
* in a scenario where error happens between a transaction request,
* catch error and return response
*/
try{
//ensure request has valid reference
$transaction_id = $request->input('reference');
$account_id = $request->input('account_nr');
if(!empty($account_id) && !empty($transaction_id)){
return $this->service->sendTo($request, $transaction_id);
}
//return custom error response
return response()->json([
'success'=>false,
'error'=>"Invalid request"
]);
}catch (\Exception $e){
//return response with error
return response()->json([
'success'=>false,
'error'=>"Error occurred : ".$e->getMessage()." | Try again later"
]);
}
}
}
<?php
namespace App\Services;
use App\Repository\UserRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ApiService
{
protected $user;
public function __construct(UserRepository $repository)
{
$this->user = $repository;
}
public function sendTo(Request $request, $transaction_ref){
//get accounts
$sender = $this->user->withId($request->input('from'));
$receiver = $this->user->withId($request->input('to'));
//validate accounts
if(!empty($sender) && !empty($receiver)){
//accounts exist so handle transactions
$amount = $request->input('amount');
//check sender account if funds are available
if($this->checkBalance($sender, $amount)){
if($this->beginTransfer($sender, $amount, $receiver)){
return response()->json([
'success'=>true,
'error'=>"Funds transferred successfully"
]);
}
return response()->json([
'success'=>false,
'error'=>"Unable to complete, try again after some time"
]);
}
return response()->json([
'success'=>false,
'error'=>"Insufficient Funds"
]);
}
return response()->json([
'success'=>false,
'error'=>"Invalid request"
]);
}
private function checkBalance($account, $amount){
if($account->transaction->balance >= $amount){
return true;
}
return false;
}
private function beginTransfer($sender, $amount, $receiver){
//ensure all request are within the transactions so as to avoid an unfinished request
/**
* using DB:: to support rollback on error as against using $model->update() to update user accounts
*
*/
DB::beginTransaction();
$newSenderBalance = $sender->transaction->balance - $amount;
$newReceiverBalance = $receiver->transaction->balance + $amount;
$senderData['balance'] = $newSenderBalance;
$receiverData['balance'] = $newReceiverBalance;
$sender->transaction->update($newSenderBalance);
$receiver->transaction->update($newReceiverBalance);
DB::commit();
return true;
}
}

How to Understand the API Logic

  • The APIController.php relies on the APIService.php to function keeping the controller slim. The entry is the HandleRequest Method It uses the sendTo method to handle transactions. parameters are Request and transaction_id

  • The ApiService.php uses the UserRepository to read the database, and handles all actions that the APIController requires instead of having all business logic in the controller

  • The UserRepository.php reads from the database. this allows for custom logic on reading data in the most efficient way as desired.

  • The code is filled with comments to guide the reader on its usage.

<?php
namespace App\Repository;
use App\User;
class UserRepository
{
public function withId($user_id){
return User::where('account_nr', $user_id)->first();
}
public function having($object){
return User::where($object->key, $object->value)->first();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment