Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
asgarzee committed Dec 14, 2017
1 parent ae8d098 commit 73f6280
Show file tree
Hide file tree
Showing 32 changed files with 558 additions and 706 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ __pycache__
.idea/
.DS_Store
*.log
.env
.env
*.key
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
Simple Implementation of logistics flow
Simple Implementation of Bank Transactions
---

We have separate APIs for all the states so that if we want to add more functionalities to each states in future.
This has APIs to create, list and retrieve bank accounts and perform money transfer across accounts and also debit and credit into an account.

It is using JWT for authentication

#### Requirements
```sh
1. Python 3.5
2. Django 2.0
3. PostgresSQL 9.6
2. Django 1.11
3. MySQL
```

### Create Virtual Environment
Expand All @@ -22,7 +24,11 @@ pip install -r requirements/base.txt
### Run Migrations
```sh
python manage.py migrate
python manage.py loaddata initial_data
```

### Create Superuser
```sh
python manage.py createsuperuser
```

### Run Server
Expand Down
8 changes: 4 additions & 4 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ chardet==3.0.4
coreapi==2.3.3
coreschema==0.0.4
decorator==4.1.2
Django==2.0
Django==1.11
django-appconf==1.0.2
django-environ==0.4.4
django-fsm==2.6.0
django-fsm-admin==1.2.4
django-fsm-log==1.5.0
django-rest-swagger==2.1.2
djangorestframework==3.7.3
djangorestframework-jwt==1.11.0
idna==2.6
ipdb==0.10.3
ipython==6.2.1
Expand All @@ -21,6 +19,7 @@ jedi==0.11.0
Jinja2==2.10
Markdown==2.6.9
MarkupSafe==1.0
mysqlclient==1.3.12
openapi-codec==1.3.2
parso==0.1.0
pexpect==4.3.0
Expand All @@ -29,6 +28,7 @@ prompt-toolkit==1.0.15
psycopg2==2.7.1
ptyprocess==0.5.2
Pygments==2.2.0
PyJWT==1.5.3
pytz==2017.3
requests==2.18.4
simplegeneric==0.8.1
Expand Down
1 change: 0 additions & 1 deletion src/SECRET.key

This file was deleted.

File renamed without changes.
6 changes: 6 additions & 0 deletions src/apps/accounts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from .models import BankAccount, Transaction

admin.site.register(BankAccount)
admin.site.register(Transaction)
203 changes: 203 additions & 0 deletions src/apps/accounts/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
from rest_framework import status, viewsets
from rest_framework.authentication import BasicAuthentication, SessionAuthentication, TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

from .models import BankAccount, Transaction
from .serializer import BankAccountSerializer, CreateAccountSerializer, CreateTransactionSerializer, \
TransactionSerializer
from .utils import get_api_response


class BankAccountViewSet(viewsets.ModelViewSet):
model = BankAccount
serializer_class = BankAccountSerializer
authentication_classes = (
JSONWebTokenAuthentication, SessionAuthentication, BasicAuthentication, TokenAuthentication,
)
permission_classes = (IsAuthenticated,)

def get_serializer_class(self):
serializer_action_classes = {
'list': BankAccountSerializer,
'retrieve': BankAccountSerializer,
'create': CreateAccountSerializer
}
if hasattr(self, 'action'):
return serializer_action_classes.get(self.action, self.serializer_class)
return self.serializer_class

def get_queryset(self):
return self.model.objects.order_by('-created_at')

def list(self, request, *args, **kwargs):
"""
Get list of all the accounts and its details
"""
queryset = self.filter_queryset(self.get_queryset())

page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response = self.get_paginated_response(serializer.data)
response_data = get_api_response(data=response.data)
return Response(response_data, status=status.HTTP_200_OK)

serializer = self.get_serializer(queryset, many=True)
response_data = get_api_response(data=serializer.data)
return Response(response_data)

def retrieve(self, request, *args, **kwargs):
"""
Get the details of a account
"""
account_number = kwargs.get('account_number')
try:
account_object = self.model.objects.get(account_number=account_number)
except self.model.DoesNotExist:
response_data = get_api_response(message='Account does not exist', success=False)
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(account_object)
response_data = get_api_response(data=serializer.data)
return Response(response_data, status=status.HTTP_200_OK)

def create(self, request, *args, **kwargs):
"""
Create a bank account
Payload:
```
{
"balance": 10000,
"last_name": "ned",
"first_name": "stark"
}
```
Success Response:
```
{
"success": true,
"message": "Successful",
"data": {
"account_number": "1513248658030792"
}
}
```
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
account_object = serializer.save(user=request.user)
response_data = get_api_response(data={'account_number': account_object.account_number})
return Response(response_data, status=status.HTTP_201_CREATED)


class TransactionViewSet(viewsets.ModelViewSet):
model = Transaction
serializer_class = TransactionSerializer
authentication_classes = (
JSONWebTokenAuthentication, SessionAuthentication, BasicAuthentication, TokenAuthentication
)
permission_classes = (IsAuthenticated,)

def get_serializer_class(self):
serializer_action_classes = {
'list': TransactionSerializer,
'retrieve': TransactionSerializer,
'create': CreateTransactionSerializer,
}
if hasattr(self, 'action'):
return serializer_action_classes.get(self.action, self.serializer_class)
return self.serializer_class

def get_queryset(self):
return self.model.objects.order_by('-created_at')

def list(self, request, *args, **kwargs):
"""
Get list of all transactions
"""
queryset = self.filter_queryset(self.get_queryset())

page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response = self.get_paginated_response(serializer.data)
response_data = get_api_response(data=response.data)
return Response(response_data, status=status.HTTP_200_OK)

serializer = self.get_serializer(queryset, many=True)
response_data = get_api_response(data=serializer.data)
return Response(response_data)

def retrieve(self, request, *args, **kwargs):
"""
Get details of a transaction
"""
reference_number = kwargs.get('reference_number')
try:
transaction_object = self.model.objects.get(reference_number=reference_number)
except self.model.DoesNotExist:
response_data = get_api_response(message='Transaction does not exist', success=False)
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(transaction_object)
response_data = get_api_response(data=serializer.data)
return Response(response_data, status=status.HTTP_200_OK)

def create(self, request, *args, **kwargs):
"""
Debit, credit and transfer amount
Payload for transfer:
```
{
"amount": 1000,
"transaction_type": "transfer",
"credit_account_number": "42492489234923",
"debit_account_number": "87428378232389"
}
```
Payload for debit:
```
{
"amount": 1000,
"transaction_type": "debit",
"debit_account_number": "87428378232389"
}
```
Payload for credit:
```
{
"amount": 1000,
"transaction_type": "credit",
"credit_account_number": "42492489234923"
}
```
Success Response:
```
{
"message": "Successful",
"data": {
"amount": 1000,
"reference_number": "1513249372422531",
"modified_at": "2017-12-14T11:02:52.422928Z",
"created_at": "2017-12-14T11:02:52.422877Z",
"is_successful": true,
"credit_account": "2343243243243",
"debit_account": "1513240633758049"
},
"success": true
}
```
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
transaction_object, message = serializer.save(user=request.user)
data = transaction_object.__dict__
data.pop('_state', None)
data.pop('id', None)
response_data = get_api_response(data=data, message=message if message else 'Successful')
return Response(response_data, status=status.HTTP_200_OK)
5 changes: 5 additions & 0 deletions src/apps/accounts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AccountConfig(AppConfig):
name = 'accounts'
52 changes: 52 additions & 0 deletions src/apps/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-13 19:05
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='BankAccount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('account_number', models.CharField(max_length=100, unique=True)),
('balance', models.DecimalField(decimal_places=2, default=0.0, max_digits=12)),
('currency', models.CharField(blank=True, choices=[('Rs', 'Rupees')], default='Rs', max_length=30, null=True)),
('first_name', models.CharField(blank=True, max_length=130, null=True)),
('last_name', models.CharField(blank=True, max_length=130, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'bank_accounts',
},
),
migrations.CreateModel(
name='Transactions',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('reference_number', models.CharField(max_length=100, unique=True)),
('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12)),
('is_successful', models.BooleanField(default=False)),
('credit_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='credit_account', to='accounts.BankAccount')),
('debit_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='debit_account', to='accounts.BankAccount')),
],
options={
'db_table': 'bank_transactions',
},
),
]
23 changes: 23 additions & 0 deletions src/apps/accounts/migrations/0002_auto_20171213_1911.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-13 19:11
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('accounts', '0001_initial'),
]

operations = [
migrations.RenameModel(
old_name='Transactions',
new_name='Transaction',
),
migrations.AlterModelOptions(
name='transaction',
options={'verbose_name_plural': 'Transactions'},
),
]
25 changes: 25 additions & 0 deletions src/apps/accounts/migrations/0003_auto_20171213_1948.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-13 19:48
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('accounts', '0002_auto_20171213_1911'),
]

operations = [
migrations.AlterField(
model_name='transaction',
name='credit_account',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='transaction',
name='debit_account',
field=models.CharField(blank=True, max_length=100, null=True),
),
]
File renamed without changes.
Loading

0 comments on commit 73f6280

Please sign in to comment.