Skip to content

Commit 9896b26

Browse files
committed
Added working store functionality and basic Stripe checkout. Added 404
page, module versions and added functionality to admin dashboard!
1 parent 0769bc6 commit 9896b26

File tree

22 files changed

+658
-40
lines changed

22 files changed

+658
-40
lines changed

CREDITS.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://github.com/smartboyathome/Cheshire-Engine/blob/master/ScoringServer/utils.py
2+
https://www.w3schools.com/

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ A WIP Open-source WHMCS alternative.
77
(Not yet recommended for use in a production environment)
88

99
# Features
10+
- [x] Built with Flask
1011
- [x] [Stripe](https://stripe.com) Support
1112
- [x] Modular Design
1213
- [x] Custom Module Support

configs/store/config.py

-3
This file was deleted.

configs/stripe/config.py

-3
This file was deleted.

core/management/products.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from flask import Blueprint, render_template, abort
2+
from flask import *
23
from jinja2 import TemplateNotFound
34
import stripe
45
import config
5-
import os
6+
import sys, os
7+
from colorama import Fore, Back, Style
8+
from colorama import init
9+
import imp
10+
import json
611
module = Blueprint('Loona Products', __name__)
712
module.hasAdminPage = True
813
module.moduleDescription = 'The Core Product Management Module for LoonaBilling'
14+
module.version = '1.1'
915

1016
def cf(folder):
1117
try:
@@ -14,12 +20,53 @@ def cf(folder):
1420
except Exception as e:
1521
#print(e)
1622
pass
23+
mods = {}
24+
for path, dirs, files in os.walk("core/payments", topdown=False):
25+
for fname in files:
26+
try:
27+
name, ext = os.path.splitext(fname)
28+
if ext == '.py' and not name == '__init__':
29+
f, filename, descr = imp.find_module(name, [path])
30+
mods[fname] = imp.load_module(name, f, filename, descr)
31+
#print(getattr(mods[fname]))
32+
print(Fore.GREEN + '[Product Module] ' + Style.RESET_ALL + 'Imported', mods[fname].module.name)
33+
#globals()
34+
except Exception as e:
35+
exc_type, exc_obj, exc_tb = sys.exc_info()
36+
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
37+
print(Fore.RED, '[ERROR]', path, name, e, exc_type, fname, exc_tb.tb_lineno, Style.RESET_ALL)
1738

1839
def checks():
1940
cf('products')
41+
cf('products/default')
2042

2143
checks()
2244

2345
@module.route('/admin/{}'.format(module.name))
2446
def adminPage():
2547
return render_template('core/LoonaProducts/admin.html', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)
48+
49+
@module.route('/admin/{}/createProduct'.format(module.name), methods=['GET', 'POST'])
50+
def createProduct():
51+
if request.method == 'POST':
52+
print(request.form)
53+
description = ''
54+
if 'description' in request.form:
55+
description = request.form['description']
56+
if 'title' not in request.form:
57+
return render_template('core/LoonaProducts/adminCreateProduct.html', categories=os.listdir('products'), msg='Title is missing', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)
58+
elif 'price' not in request.form:
59+
return render_template('core/LoonaProducts/adminCreateProduct.html', categories=os.listdir('products'), msg='Price is missing', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)
60+
else:
61+
data = {}
62+
data['Config'] = []
63+
data['Config'].append({
64+
'title': request.form['title'],
65+
'description': description,
66+
'price': request.form['price'],
67+
'automation': None
68+
})
69+
with open('products/{}/{}.json'.format(request.form['category'], len(os.listdir('products/{}'.format(request.form['category'])))), 'w+') as of:
70+
json.dump(data, of)
71+
return render_template('core/LoonaProducts/adminCreateProduct.html', categories=os.listdir('products'), msg=f'Created Product: {request.fprm["title"]}', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)
72+
return render_template('core/LoonaProducts/adminCreateProduct.html', categories=os.listdir('products'), businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)

core/pages/pages.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
module = Blueprint('Pages', __name__)
66
module.hasAdminPage = True
77
module.moduleDescription = 'Simple pages for LoonaBilling'
8+
module.version = '1.1'
89

910
def cf(folder):
1011
try:
@@ -26,8 +27,8 @@ def main():
2627

2728
@module.route('/about')
2829
def about():
29-
return render_template('core/about.html', aboutText=config.aboutText, businessName=config.businessName) # CHANGE TO ABOUT PAGE
30+
return render_template('core/about.html', aboutText=config.aboutText, businessName=config.businessName)
3031

3132
@module.route('/contact', methods=['GET', 'POST'])
3233
def contact():
33-
return render_template('core/contact.html', aboutText=config.aboutText, businessName=config.businessName) # CHANGE TO CONTACT PAGE
34+
return render_template('core/contact.html', aboutText=config.aboutText, businessName=config.businessName)

core/payments/stripe.py

+36-17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
module = Blueprint('Stripe', __name__)
88
module.hasAdminPage = True
99
module.moduleDescription = 'The Core Stripe Billing Module for LoonaBilling (Unofficial)'
10+
module.version = '1.1'
1011

1112
def cf(folder):
1213
try:
@@ -51,22 +52,31 @@ def startSession():
5152

5253
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
5354
print(request.args)
54-
ssession = stripe.checkout.Session.create(
55-
success_url=config.domain_url + "success?session_id={CHECKOUT_SESSION_ID}",
56-
cancel_url=config.domain_url + "cancelled",
57-
#payment_method_types=["card"],
58-
mode="payment",
59-
line_items=[
60-
{
61-
"name": "Test Payment",
62-
"quantity": 1,
63-
"currency": "usd",
64-
"amount": "2000",
65-
}
66-
],
67-
metadata={request.args}
68-
)
69-
return redirect(ssession.url, code=303)
55+
if 'category' not in request.args:
56+
return 'Needs category'
57+
if 'item' not in request.args:
58+
return 'Needs item'
59+
if os.path.isdir('products/{}'.format(request.args['category'])):#, request.args['item'])):
60+
with open('products/{}/{}.json'.format(request.args['category'], request.args['item'])) as of:
61+
data = json.load(of)
62+
for p in data['Config']:
63+
64+
ssession = stripe.checkout.Session.create(
65+
success_url=config.domain_url + "success?session_id={CHECKOUT_SESSION_ID}",
66+
cancel_url=config.domain_url + "cancelled",
67+
#payment_method_types=["card"],
68+
mode="payment",
69+
line_items=[
70+
{
71+
"name": str(p['title']),
72+
"quantity": 1,
73+
"currency": "usd",
74+
"amount": int(p['price'].replace('.', '')),
75+
}
76+
],
77+
#metadata={request.args}
78+
)
79+
return redirect(ssession.url, code=303)
7080
except Exception as e:
7181
return jsonify(error=str(e)), 403
7282

@@ -81,4 +91,13 @@ def cancelled():
8191

8292
@module.route('/admin/{}'.format(module.name))
8393
def adminPage():
84-
return 'a'
94+
return render_template('core/Stripe/admin.html', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)
95+
96+
@module.route('/admin/{}/manageKeys'.format(module.name), methods=['GET', 'POST'])
97+
def adminManageKeys():
98+
if 'publishableKey' in request.form:
99+
open('configs/stripe/publishableKey.txt', 'w+').write(request.form['publishableKey'].strip())
100+
if 'privateKey' in request.form:
101+
open('configs/stripe/privateKey.txt', 'w+').write(request.form['privateKey'].strip())
102+
stripe.api_key = request.form['privateKey'].strip()
103+
return render_template('core/Stripe/adminManageKeys.html', businessName=config.businessName, moduleName=module.name, moduleDescription=module.moduleDescription)

core/store/store.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
module = Blueprint('LoonaStore', __name__)
88
module.hasAdminPage = False
99
module.moduleDescription = 'The Core Store Module for LoonaBilling'
10+
module.version = '1.1'
1011

1112
def cf(folder):
1213
try:
@@ -44,4 +45,18 @@ def store():
4445
category = request.args['category']
4546
if 'item' in request.args:
4647
item = request.args['item']
47-
return 'Welcome to the store!' #<br>{} | {}'.format(category, item) # Removed due to CWE-79 & CWE-116
48+
#if category == '':
49+
# return str(os.listdir('products'))
50+
categories = []
51+
items = []
52+
for f in os.listdir('products/{}'.format(category)):
53+
if os.path.isdir(os.path.join('products/', category, f)):
54+
print('folder')
55+
categories += [f]
56+
if os.path.isfile(os.path.join('products/', category, f)):
57+
with open(os.path.join('products/', category, f)) as of:
58+
data = json.load(of)
59+
for p in data['Config']:
60+
items += [(p['title'], p['price'], p['description'], f.split('.')[0])]
61+
62+
return render_template('core/LoonaStore/index.html', businessName=config.businessName, categories=categories, items=items, category=category)

logs/README.md

Whitespace-only changes.

modules/admin/testadmin.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module = Blueprint('TestAdmin', __name__)
55
module.hasAdminPage = False
66
module.moduleDescription = 'A test module'
7+
module.version = '1.1'
78

89
@module.route('/testadmin')
910
def show():

modules/user/testuser.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module = Blueprint('TestUser', __name__)
55
module.hasAdminPage = False
66
module.moduleDescription = 'A test module'
7+
module.version = '1.1'
78

89
@module.route('/testuser')
910
def show():

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
colorama==0.4.4
2+
configs==3.0.3
23
cryptography==3.3.2
34
Flask==1.1.2
45
Jinja2==3.0.1
56
psutil==5.8.0
6-
pyminizip==0.2.6

run.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import time
12
import logging
2-
logging.basicConfig(level=logging.DEBUG)
3+
logging.basicConfig(filename='logs/'+str(time.time()),
4+
filemode='a',
5+
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
6+
datefmt='%H:%M:%S',
7+
level=logging.DEBUG)
38
import sys, os
49
import json
510
from flask import Flask
@@ -8,14 +13,12 @@
813
import config
914
import string
1015
import random
11-
import importlib
1216
import imp
1317
from colorama import Fore, Back, Style
1418
from colorama import init
1519
import psutil
1620
import base64
1721
from uuid import getnode
18-
import pyminizip
1922
from cryptography.fernet import Fernet
2023
from cryptography.hazmat.backends import default_backend
2124
from cryptography.hazmat.primitives import hashes
@@ -25,6 +28,7 @@
2528

2629
app = Flask(__name__)
2730
app.adminModules = []
31+
app.version = '1.1'
2832

2933
def cf(folder):
3034
try:
@@ -47,9 +51,6 @@ def checks():
4751
checks()
4852

4953
def load_blueprints():
50-
"""
51-
https://github.com/smartboyathome/Cheshire-Engine/blob/master/ScoringServer/utils.py
52-
"""
5354
mods = {}
5455
#path = 'modules'
5556
#dir_list = os.listdir(path)
@@ -85,7 +86,6 @@ def load_blueprints():
8586
print(Fore.RED, '[ERROR]', path, name, e, exc_type, fname, exc_tb.tb_lineno, Style.RESET_ALL)
8687
return mods
8788

88-
8989
mods = load_blueprints()
9090
print(app.url_map)
9191
print(mods)
@@ -97,7 +97,14 @@ def load_blueprints():
9797

9898
@app.route('/admin')
9999
def admin():
100-
return render_template('core/admin.html', tabs=app.adminModules, cpuUsage=int(psutil.cpu_percent()), ramUsage=int(psutil.virtual_memory().percent))
100+
return render_template('core/admin.html', tabs=app.adminModules, cpuUsage=int(psutil.cpu_percent()), ramUsage=int(psutil.virtual_memory().percent), storageUsage=int(psutil.disk_usage('/').percent))
101+
102+
@app.errorhandler(404)
103+
def page_not_found(e):
104+
# note that we set the 404 status explicitly
105+
return render_template('404.html', businessName=config.businessName), 404
101106

102-
app.config['SERVER_NAME'] = config.domain
103-
app.run(host=config.ip, port=config.port, debug=config.debug, ssl_context=config.ssl)
107+
if __name__ == '__main__':
108+
app.config['SERVER_NAME'] = config.domain
109+
print('** LoonaBilling has started **')
110+
app.run(host=config.ip, port=config.port, debug=config.debug, ssl_context=config.ssl)

templates/404.html

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>{{ businessName }} - Store</title>
5+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
6+
<style>
7+
* {box-sizing: border-box;}
8+
9+
body {
10+
margin: 0;
11+
font-family: Arial, Helvetica, sans-serif;
12+
}
13+
14+
.topnav {
15+
overflow: hidden;
16+
background-color: #e9e9e9;
17+
}
18+
19+
.topnav a {
20+
float: left;
21+
display: block;
22+
color: black;
23+
text-align: center;
24+
padding: 14px 16px;
25+
text-decoration: none;
26+
font-size: 17px;
27+
}
28+
29+
.topnav a:hover {
30+
background-color: #ddd;
31+
color: black;
32+
}
33+
34+
.topnav a.active {
35+
background-color: #2196F3;
36+
color: white;
37+
}
38+
39+
.topnav .search-container {
40+
float: right;
41+
}
42+
43+
.topnav input[type=text] {
44+
padding: 6px;
45+
margin-top: 8px;
46+
font-size: 17px;
47+
border: none;
48+
}
49+
50+
.topnav .search-container button {
51+
float: right;
52+
padding: 6px 10px;
53+
margin-top: 8px;
54+
margin-right: 16px;
55+
background: #ddd;
56+
font-size: 17px;
57+
border: none;
58+
cursor: pointer;
59+
}
60+
61+
.topnav .search-container button:hover {
62+
background: #ccc;
63+
}
64+
65+
@media screen and (max-width: 600px) {
66+
.topnav .search-container {
67+
float: none;
68+
}
69+
.topnav a, .topnav input[type=text], .topnav .search-container button {
70+
float: none;
71+
display: block;
72+
text-align: left;
73+
width: 100%;
74+
margin: 0;
75+
padding: 14px;
76+
}
77+
.topnav input[type=text] {
78+
border: 1px solid #ccc;
79+
}
80+
}
81+
</style>
82+
</head>
83+
<body>
84+
<div style="padding-left:16px">
85+
<h2>Error 404</h2>
86+
<p>You are not visiting the correct page....</p>
87+
</div>
88+
89+
</body>
90+
{% include 'divs/footer.html' %}
91+
</html>

0 commit comments

Comments
 (0)