/online-store-billing-system

A full-stack online store model that gives the store owner the ability to add products and their prices, the ability to add promocodes which their customers can use, and the ability of a customer to send their bill to the owner's WhatsApp, The store uses Flask as its backend, PostgreSQL as database, and CSS, HTML, JS/TS and Bootstrap as its frontend.

Primary LanguageJavaScript

New 4.0v Update Available !!!

Online Store Billing System

Overview

A full-stack online store model that gives the store owner the ability to add products and their prices, the ability to add promocodes which their customers can use, and the ability of a customer to send their bill to the owner's WhatsApp, The store uses Flask as its backend, PostgreSQL as database, and CSS, HTML, JS/TS and Bootstrap as its Frontend

The application is deployed on Heroku and can be accessed at
br19-onlinestore.herokuapp.com

Running the Project Locally

Project Dependencies


Python 3.9.10

Install Here: python docs


Virtual Environment

  • virtualenv as a tool to create isolated Python environments

  • It is recommended to work with a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organized.

  • Instructions for setting up a virtual environment for your platform can be found in the python docs


Installing Dependencies

Once you have your virtual environment setup and running, install dependencies by running:

pip install -r requirements.tx

This will install all the required packages as mentioned within the requirements.txt file.

Key Dependencies

  • Flask is a lightweight backend microservices framework. Flask is required to handle requests and responses.

  • Flask-CORS is the extension to use for handling cross-origin requests from our frontend server when running the project locally.

    • after installing this dependency, uncomment the following line in app/main.py for the app to work locally:
# cors = CORS(app)

Database Setup

  • SQLite is the database for local running and spdb.db is the database file

  • PostgreSQL is the database for production environment, because of that, you have to comment out this line of code in all of app/main.py:

self.conn = psycopg2.connect(DATABASE_URL, sslmode='require')

and uncomment the following line in all of app/main.py:

# self.conn = sqlite3.connect("spdb.db")

for database connections to work locally

Environment variables

  • Create .env file in the root directory and put the following values in:
USERNAME = xxxx

PASSWORD = xxxx

Running the server

  • From within the project directory first ensure you are working using your created virtual environment, then type the following:
flask run

or in PowerShell for Windows

$env:FLASK_APP  =  "app/main.py"

$env:FLASK_ENV  =  "development"

flask run

API Reference

Getting Started

  • Base URL: This app can be run locally at the default URL http://127.0.0.1:5000/

  • External URL: The app is also hosted on Heroku and can be accessed at br19-onlinestore.herokuapp.com

Error Handling and Status Codes

The API will return the following error codes and status codes

when requests fail or success:

  • 200: Entity is Updated

  • 201: Entity is Created

  • 202: Accepted for Processing

  • 204: Deleted Successfully or No Content to Return

  • 400: Bad Request

  • 401: Unauthorized Access

  • 403: Entity Exists

  • 404: Resource Not Found

  • 405: Method Used Not Allowed

  • 429: Rate-Limit of Requests Exceeded

  • 500: Internal Server Error

  • 503: Server is Down Due to Maintenance

An example of 401 error due to PASSWORD and USERNAME provided by the frontend user not matching what is in .env:

msg =  '{"msg": f"Error 401: unauthrized access", "statCode": 401}'

return render_template('err/err401.html', msg=msg) # it will render 401 error page and pass msg to be consuled in frontend

Other errors and status codes are returned as JSON in the following format:

{"msg": "Bad Request 400: storeNum was not updated, even the provided storeNum is not integer, or it contains illegal form of characters", "statCode": 400}

API Endpoints

GET '/'
  • Renders app/templates/main.html.

GET '/main'
  • Renders app/templates/main.html.

GET '/login'
  • Renders app/templates/login.html.

GET '/admin'
  • Renders app/templates/admin.html.

GET '/verify/<username>/<password>'
  • Checks if <username> = USERNAME in .env and <password> = PASSWORD in .env then redirect to app/templates/admin.html.

GET '/products/'
  • This route returns all products

Example:

{

"0":{

"id":1,

"price":10,

"title":"Product A",

"img":"/* an image in form of base64 encoding */"

},

"1":{

"id":2,

"price":20,

"title":"Product B",

"img":"/* an image in form of base64 encoding */"

},

"2":{

"id":3,

"price":30,

"title":"Product C",

"img":"/* an image in form of base64 encoding */"

}

}

GET '/promocodes/'
  • This route returns all promocodes

Example:

{
"0":{

"amount":0.5,

"code":"promo1",

"id":1

},

"1":{

"amount":0.3,

"code":"promo2",

"id":2

}

}

POST'/product'
  • This route will add a product to the database using the following encodeURIComponent()'ed header:
{

"id" : "value",

"title" : "value1",

"price" : "value2",

}

with a body containing the uploaded image,

and returns if success:

{

"msg": "Success 201: product_id:{id} is recorded, ...",

"statCode": 201

}

or some forms of JSON if fails according to the error, like if the header was not formed correctly:

{

"msg": "Bad Request 400: product was not added, ...",

"statCode": 400

}

PUT'/product/<int:idIn>'
  • This route will update a product in the database using the following encodeURIComponent()'ed header:
{

"title" : "value1",

"price" : "value2",

}

with a body containing the uploaded image,

and returns if success:

{

"msg": "Success 200: product_idIn:{idIn} is updated, ...",

"statCode": 200}

//(id == idIn)

or some forms of JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: product_idIn:{idIn} was not updated because they didn't have a record before... ",

"statCode": 404

}

DELETE'/product/<int:idIn>'
  • This route will delete a product in the database using the id, and returns if success:
{

"msg": "Success 204: product_idIn:{idIn} is deleted successfully, product_idIn:{idIn} doesn't exist anymore",

"statCode": 204

}

or some forms of JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: product_idIn:{idIn} was not found, it may not exist",

"statCode": 404

}

GET'/product/<int:idIn>'
  • This route will get a product from the database using the id to check if it can be added, and returns if successful:
{

"msg": "Success 202: the product_idIn {idIn} doesn't exist, so it can be added",

"statCode": 202

}

or some forms of JSON if fails according to the error, like if the id is found:

{

"msg": "Status Code 403: the product_idIn {idIn} exists, {newObj.search(idIn)}",

"statCode": 403

}

//newObj.search(idIn) is the record of id

DELETE'/product/<int:idIn>'
  • This route will delete a product in the database using the id, and returns if success:
{

"msg": "Success 204: product_idIn:{idIn} is deleted successfully, product_idIn:{idIn} not exist anymore",

"statCode": 204

}

or some forms of JSON if fails according to the error, like if the id is not found:

{

"msg": "Error 404: product_idIn:{idIn} was not found, it may not exist",

"statCode": 404

}

//id == idIn

POST'/promocodes'
  • This route will add a promocode to the database using the following body:
{

"id": "value",

"code": "value1",

"amount": "value2"

}

and returns if success:

{

"msg": "Success 201: code_id:{id} is recorded, ...",

"statCode": 201

}

or some forms of JSON if fails according to the error, like if the body was not formed correctly:

{

"msg": "Bad Request 400: promocode was not added, ...",

"statCode": 400

}

PUT'/promocode/<int:idIn>'
  • This route will update a promocode in the database using the following body:
{

"code": "value1",

"amount": "value2"

}

and returns if success:

{

"msg": "Success 200: code_idIn:{idIn} is updated, ...",

"statCode": 200}

//(id == idIn)

or some forms of JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: code_idIn:{idIn} was not updated because they didn't have a record before... ",

"statCode": 404

}

DELETE'/promocode/<int:idIn>'
  • This route will delete a promocode in the database using the id, and returns if success:
{

"msg": "Success 204: code_idIn:{idIn} is deleted successfully, code_idIn:{idIn} doesn't exist anymore",

"statCode": 204

}

or some forms of JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: code_idIn:{idIn} was not found, it may not exist",

"statCode": 404

}

GET'/promocode/<int:idIn>'
  • This route will get a promocode in the database using the id to check if it can be added, and returns if success:
{

"msg": "Success 202: the code_idIn {idIn} doesn't exist, so it can be added",

"statCode": 202

}

or some forms of JSON if fails according to the error, like if the id is found:

{

"msg": "Status Code 403: the code_idIn {idIn} exists, {newObj.search(idIn)}",

"statCode": 403

}

//newObj.search(idIn) is the record of id

DELETE'/promocode/<int:idIn>'
  • This route will delete a promocode in the database using the id, and returns if success:
{

"msg": "Success 204: code_idIn:{idIn} is deleted successfully, code_idIn:{idIn} not exist anymore",

"statCode": 204

}

or some forms of JSON if fails according to the error, like if the id is not found:

{

"msg": "Error 404: code_idIn:{idIn} was not found, it may not exist",

"statCode": 404

}

//id == idIn

POST'/storeName'
  • This route will add a name for the store in the database using the following body:
{

"storeName":"value"

}

, and returns if success:

{

"msg": "Success 201: storeName:{storeName} is recorded, the storeName matches {(newObj.search())[0]}",

"statCode": 201

}

or some forms of JSON if fails according to the error, like if an unknown error happens:

{

"msg": "Unkown Error 500: storeName:{storeName} was not recorded, the storeName doesn't match {(newObj.search())[0]}",

"statCode": 500

}

PUT'/storeName/'
  • This route will update the name of the store in the database using the following body:
{

"storeName":"value"

}

and returns if success:

{

"msg":"Success 200: storeName:{storeName} is updated, old data:{result}, new data:{newObj.search()}",

"statCode": 200

}

or some forms JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: storeName:{storeName} was not updated because it didn't have a record before (maybe first time adding?) ",

"statCode": 404

}

DELETE'/storeName/'
  • This route will delete the name of the store in the database:
{

"storeName":"value"

}

, and returns if success:

{

"msg": "Success 204: store name is deleted successfully", "statCode": 204}

or some forms JSON if fails according to the error, like if the name was not found:

{

"msg": "Error 404: storeName:{result} was not found, it may not exist",

"statCode": 404

}

GET'/storeName/show'
  • This route will return the store name as:
{

"storeName":"value"

}

and if there's no name for the store it will return:

{

"storeName":"none/لايوجد"

}

POST'/storeNum'
  • This route will add a number for the store in the database using the following body:
{

"storeNum":"value"

}

, and returns if success:

{

"msg": "Success 201: storeNum:{storeNum} is recorded, the storeNum matches {(newObj.search())[0]}",

"statCode": 201

}

or some forms of JSON if fails according to the error, like unknown error happens:

{

"msg": "Unkown Error 500: storeNum:{storeNum} was not recorded, the storeNum doesn't match {(newObj.search())[0]}",

"statCode": 500

}

PUT'/storeNum/'
  • This route will update the number of the store in the database using the following body:
{

"storeNum":"value"

}

, and returns if success:

{

"msg":"Success 200: storeNum:{storeNum} is updated, old data:{result}, new data:{newObj.search()}",

"statCode": 200

}

or some form of JSON if fails according to the error, like if the number was not found:

{

"msg": "Error 404: storeNum:{storeNum} was not updated because it didn't have a record before (maybe first time adding?) ",

"statCode": 404

}

DELETE'/storeNum/'
  • This route will delete the number of the store in the database:
{

"storeNum":"value"

}

, and returns if success:

{

"msg": "Success 204: store number is deleted successfully", "statCode": 204}

or some form of JSON if fails according to the error, like if the number was not found:

{

"msg": "Error 404: storeNum:{result} was not found, it may not exist",

"statCode": 404

}

GET'/storeNum/show'
  • This route will return the store number as:
{

"storeNum":"value"

}

and if there's no number for the store it will return:

{

"storeNum":"none/لايوجد"

}

POST'/storeTheme'
  • This route will add a theme for the store in the database using the following body:
{

"storeTheme":"value"

}

, and returns if success:

{

"msg": "Success 201: storeTheme:{storeTheme} is recorded, the storeTheme matches {(newObj.search())[0]}",

"statCode": 201

}

or some forms of JSON if fails according to the error, like if an unknown error happens:

{

"msg": "Unkown Error 500: storeTheme:{storeTheme} was not recorded, the storeTheme doesn't match {(newObj.search())[0]}",

"statCode": 500

}

PUT'/storeTheme/'
  • This route will update the theme of the store in the database using the following body:
{

"storeTheme":"value"

}

and returns if success:

{

"msg":"Success 200: storeTheme:{storeTheme} is updated, old data:{result}, new data:{newObj.search()}",

"statCode": 200

}

or some forms JSON if fails according to the error, like if the id was not found:

{

"msg": "Error 404: storeTheme:{storeTheme} was not updated because it didn't have a record before (maybe first time adding?) ",

"statCode": 404

}

DELETE'/storeTheme/'
  • This route will delete the theme of the store in the database:
{

"storeTheme":"value"

}

, and returns if success:

{

"msg": "Success 204: store name is deleted successfully", "statCode": 204}

or some forms JSON if fails according to the error, like if the name was not found:

{

"msg": "Error 404: storeTheme:{result} was not found, it may not exist",

"statCode": 404

}

GET'/storeTheme/show'
  • This route will return the store theme as:
{

"storeTheme":"value"

}

and if there's no theme selected for the store it will return:

{

"storeTheme":"none/لايوجد"

}

Frontend

There are 8 HTML files in app/templates folder 5 of them are pages for Error Handling and Status Codes and the other 3 is the main pages with 3 CSS files and 3 JavaScript files:

  • main.html

    • in this page, all products which been added by admin will be shown to the customer: main.html main.html
  • admin.html

    • in this page, admin can add, delete, or updates products and promocodes, and can change the name and WhatsApp number of their store: admin.html admin.html admin.html admin.html
  • login.html

    • in this page, the admin can verify their identity by providing <username> and <password> , then via /verify/<username>/<password> route, the user will be verified: login.html

Additional UI Features

  • The frontend follows a fully responsive design, below are examples of some pages in an 1170×2532 iOS device: main.html main.html admin.html login.html

  • A loading spinner will be shown in every page for short time to make the content load smoothly: loading spinner

  • Below are some error pages that will be shown to the user: err404.html err401

Updates

Update 2.0v

Now the store owner can change the store theme! There are 5 themes for now (red 🔴, blue 🔵, gray 🩶, green 🟢 and original "yellow" 🟡), more will be added later:

Themes Section

Update 3.5v

New features has been added, which are:

  • Brand-new theme for the store! Pink 🦩.
  • New billing system. Save, browse and delete all bills that've been issued.
  • Enhancing the user experience with better UI and more accessibility.

Update 3.5v Update 3.5v-Biling system Update 3.5v-Biling system Update 3.5v-Biling system

Update 4.0v

New features has been added, which are:

  • Now you can edit the currency used in the store!! Head to تعديل معلومات المتجر (edit store info), then type the currency you want to use in عملة المتجر (store currency).
  • Brand-new theme for the store! Black ⚫.
  • UI enhancements in Admin Page.