[+] Added payments.

Created a subscription system, and added different permission levels to the users depending on their subscription
This commit is contained in:
Charles Le Maux 2024-09-27 10:23:32 +02:00
parent 04a62f41f8
commit 15059a7063
5 changed files with 164 additions and 53 deletions

22
app.py
View File

@ -13,6 +13,7 @@ from werkzeug.datastructures import ImmutableMultiDict, FileStorage
from werkzeug.utils import secure_filename
import file_manager
import finances
import firebase_manager
import generic_executor
import modpacks_manager
@ -20,7 +21,7 @@ import modpacks_manager
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 * 1000 #15.28MB~
cors = CORS(app, origins="*")
apiBP = Blueprint('apiBP', 'BPapi')
mc_api = Blueprint('mc_api_bp', 'bp_mc_api')
def generic_response_maker(status_code: http.HTTPStatus, _message: str = None) -> tuple[Response, int]:
@ -50,12 +51,6 @@ def generic_response_maker(status_code: http.HTTPStatus, _message: str = None) -
return message, status_code.value
'''
valid, user_id = firebase_manager.verify_jwt_token(data['token'])
TODO : replace 53 by the given statement.
'''
def authenticate_request(token: str):
valid, user_id = firebase_manager.verify_jwt_token(token)
if not valid:
@ -102,7 +97,7 @@ route_handlers = {
}
@apiBP.route('/<path:path>', methods=['POST'])
@mc_api.route('/<path:path>', methods=['POST'])
def dynamic_route_handler(path):
if path not in route_handlers:
return generic_response_maker(http.HTTPStatus.METHOD_NOT_ALLOWED)
@ -128,7 +123,7 @@ def dynamic_route_handler(path):
# [!] This route is specific and has to remain out of the dynamic route handler.
@apiBP.route('/Upload', methods=['POST'])
@mc_api.route('/Upload', methods=['POST'])
def upload():
form = request.form
token: str or None = request.headers.get('SST')
@ -180,12 +175,12 @@ def upload():
@app.route('/modpacks', methods=['GET'])
@mc_api.route('/modpacks', methods=['GET'])
def get_modpacks():
return modpacks_manager.get_modpacks()
@app.route('/modpacks/image/<path:filename>', methods=['GET'])
@mc_api.route('/modpacks/image/<path:filename>', methods=['GET'])
def get_modpack_image(filename):
return modpacks_manager.get_modpack_image(filename)
@ -195,7 +190,8 @@ def api_cleanup() -> None:
return
app.register_blueprint(apiBP)
app.register_blueprint(mc_api)
app.register_blueprint(finances.payment_api)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Background Scheduler")
@ -211,4 +207,4 @@ if __name__ == '__main__':
scheduler.start()
run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False)
api_cleanup()
#api_cleanup()

View File

@ -1,13 +1,74 @@
import stripe
from flask import Flask, request, jsonify
from flask import request, jsonify, Blueprint
from stripe import StripeClient
app = Flask(__name__)
import firebase_manager
from firebase_manager import log_exception_to_firestore
payment_api = Blueprint('payment_api_bp', 'bp_payment_api')
#stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
endpoint_secret = "whsec_2a1cadb771f7acfdeaac6720fdd56d3353cca5d38bdc2ed88336932968531457"
stripe_api_key: str = ("sk_live_51PyIYTP3VLLeb9GlKRfsTMh4olzrJxkT1PwXGlW2G"
"HPUeFZmTFHe5ITI0L2tZPBnJd2lHarQifIV5TMh505KFuL300mkItQuBo")
endpoint_secret: str = "whsec_2a1cadb771f7acfdeaac6720fdd56d3353cca5d38bdc2ed88336932968531457"
@app.route('/finances', methods=['POST'])
stripe.api_key = stripe_api_key
client: StripeClient = StripeClient(api_key=stripe_api_key)
products: [str] = ["",
"price_1Q3Ji5P3VLLeb9GlMOZuafV9", "price_1Q3Ji3P3VLLeb9GlqNStquBo", "price_1Q3Ji1P3VLLeb9Gl5uqH23js"]
@payment_api.route('/get-checkout-session', methods=['GET'])
def create_checkout_session():
try:
permission: int = int(request.headers.get('permission'))
email: str = request.headers.get('email')
except ValueError as err:
print(err)
permission: None = None
email: None = None
if not permission:
return jsonify(message="No product query provided."), 404
if not email:
return jsonify(message="No customer email provided."), 404
try:
session = stripe.checkout.Session.create(
ui_mode = 'embedded',
line_items=[
{
'price': products[permission],
'quantity': 1,
},
],
allow_promotion_codes=True,
mode='subscription',
customer_email= email,
return_url= "https://app.servii.fr" + '/return?session_id={CHECKOUT_SESSION_ID}',
automatic_tax={'enabled': True},
)
except Exception as err:
firebase_manager.log_exception_to_firestore(exception=err, user_id="Stripe")
return jsonify(message="Unable to get associated product."), 500
return jsonify(clientSecret=session.client_secret), 200
@payment_api.route('/get-session-status', methods=['GET'])
def session_status():
try:
session = stripe.checkout.Session.retrieve(request.args.get('session_id'))
return jsonify(status=session.status, customer_email=session.customer_details.email), 200
except Exception as err:
firebase_manager.log_exception_to_firestore(exception=err, user_id="Stripe")
return jsonify(message="Unable to retrieve payment session status."), 500
@payment_api.route('/finances', methods=['POST'])
def webhook():
payload = request.get_data(as_text=True)
sig_header = request.headers.get('Stripe-Signature')
@ -16,30 +77,61 @@ def webhook():
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
print(f"Invalid payload: {e}")
return jsonify({'error': 'Invalid payload'}), 400
except stripe.error.SignatureVerificationError as e:
print(f"Invalid signature: {e}")
return jsonify({'error': 'Invalid signature'}), 400
except ValueError as err:
print(f"Invalid payload: {err}")
return jsonify({'message': 'Invalid payload'}), 400
except stripe.error.SignatureVerificationError as err:
print(f"Invalid signature: {err}")
return jsonify({'message': 'Invalid signature'}), 400
try:
match event['type']:
case 'customer.subscription.created' | 'customer.subscription.updated':
firestore_metadata: int = extract_firestore_data(event)
user_id: str = extract_user_id(event)
firebase_manager.update_firestore(user_id=user_id, data={"subscription": firestore_metadata})
case 'customer.subscription.deleted':
user_id: str = extract_user_id(event)
firebase_manager.update_firestore(user_id=user_id, data={"subscription": 0})
return jsonify({'status': 'success'}), 200
except Exception as err:
log_exception_to_firestore(exception=err, user_id="Stripe")
return jsonify({'status': 'error'}), 500
# Handle the event based on its type
match event['type']:
case 'customer.subscription.deleted':
customer = event['data']['object']
print(event)
print(f"Customer stopped subscription: {customer['id']}")
case 'customer.subscription.created':
print(event)
customer = event['data']['object']
print(f"Customer started subscription: {customer['id']}")
case 'customer.subscription.updated':
print(event)
customer = event['data']['object']
print(f"Customer started subscription: {customer['id']}")
def extract_firestore_data(event):
try:
firestore_metadata = (
event.get('data', {})
.get('object', {})
.get('items', {})
.get('data', [{}])[0]
.get('price', {})
.get('metadata', {})
.get('firestore', 0)
)
return int(firestore_metadata)
return jsonify({'status': 'success'}), 200
except (IndexError, KeyError, ValueError) as e:
return None
if __name__ == '__main__':
app.run(port=3400)
def extract_user_id(event):
try:
customer_id = event.get('data', {}).get('object', {}).get('customer', None)
customer = client.customers.retrieve(customer_id)
mail: str = customer.get("email", None)
user_id: str = firebase_manager.get_user_from_mail(mail)
return user_id
except (IndexError, KeyError) as e:
return None
except Exception as err:
log_exception_to_firestore(exception=err, user_id="Stripe")
return None
if __name__ == "__main__":
pass

View File

@ -5,7 +5,8 @@ from typing import Union
import firebase_admin
import jwt
from firebase_admin import auth, credentials, firestore
from google.api_core.exceptions import Aborted, DataLoss, NotFound, OutOfRange, PermissionDenied, ResourceExhausted
from google.api_core.exceptions import Aborted, DataLoss, NotFound, OutOfRange, PermissionDenied, ResourceExhausted, \
GoogleAPICallError
from google.cloud.firestore_v1 import FieldFilter, DocumentReference
import file_manager
@ -16,10 +17,35 @@ app = firebase_admin.initialize_app(cred)
firestore_database = firestore.client()
def get_user_from_id(user_id):
def get_user_from_id(user_id: str):
return auth.get_user(user_id)
def get_user_from_mail(mail: str) -> str or None:
try:
users_ref = firestore_database.collection('users')
query = users_ref.where(filter=FieldFilter(field_path='mail', op_string='==', value=mail)).limit(1).stream()
user_id = None
for doc in query:
user_id = doc.id
break
return user_id
except NotFound:
print("No such document!")
return None
except GoogleAPICallError as e:
print(f"API call error: {e}")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
def verify_jwt_token(token):
try:
decoded_token = auth.verify_id_token(token, app=app, check_revoked=True)

View File

@ -188,6 +188,8 @@ def server_create(user: UserRecord, name: str, version: str, framework: str = "p
if framework not in allowed_frameworks:
return HTTPStatus.METHOD_NOT_ALLOWED, f"Framework {framework} not recognized."
user_id = user.uid
if not firebase_manager.get_user_field(user_id, "subscription"):
return HTTPStatus.FORBIDDEN, "You haven't yet subscribed to Servii."
server_path: str = f"users/{user_id}/{name}"
server_template_path: str = f"servers/{framework}/{version}"
try:
@ -234,6 +236,8 @@ def server_delete(name: str, user: UserRecord) -> tuple[HTTPStatus, Union[str, N
def server_run(user: UserRecord, name: str) -> tuple[HTTPStatus, Union[str, None]]:
user_id = user.uid
if not firebase_manager.get_user_field(user_id, "subscription"):
return HTTPStatus.FORBIDDEN, "You haven't yet subscribed to Servii."
mc_manager.set_cooldown(user_id=user_id)
try:
port: int = firebase_manager.get_server_port(user_id)

View File

@ -2,6 +2,7 @@ import os
import shutil
import firebase_manager
from firebase_manager import firestore_database
def ban_user(user_id: str):
@ -38,14 +39,6 @@ if __name__ == '__main__':
listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"),
"/home/hapso/Desktop/Personal/servii-backend/servers/paper")
'''
users_ref = firebase_manager.firestore_database.collection('users')
# Stream all documents in the users collection
users = users_ref.stream()
# Iterate over each user document
for user in users:
# Update each document to add the 'subscription' field
user_ref = users_ref.document(user.id)
user_ref.update({'subscription': 0})
print(firebase_manager.get_user_from_mail("technoprod25458565@gmail.com"))
print(firebase_manager.get_user_field("MpkbDMOO8PQddQgB5VgBQdTMWF53", "test"))
pass