from flask import Blueprint, render_template, request, redirect, url_for, jsonify, make_response, abort from config import Config import json import logging from app.models.giftcard_key import GiftcardKey from app.enums.GiftcardType import GiftcardType from app.models.kofi_transaction import KofiTransaction from app.extensions import db, csrf import requests import datetime import secrets import string from requests.auth import HTTPBasicAuth from config import Config config = Config() KofiHandlerRoute = Blueprint("kofihandler", __name__, template_folder="pages") def GenerateCode(): Code = "" for i in range(0, 5): Chunk = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(5)) Code += Chunk if i != 4: Code += "-" return Code @KofiHandlerRoute.before_request def before_request(): if config.KOFI_ENABLED is False: return abort(404) @KofiHandlerRoute.route("/internal/kofi_handler", methods=["POST"]) @csrf.exempt def kofi_handler(): try: PurchaseData = json.loads( request.form.get(key="data", default=None, type=str) ) if PurchaseData is None: return abort(400) except: return abort(400) if PurchaseData["verification_token"] != Config.KOFI_VERIFICATION_TOKEN: return abort(401) if "email" not in PurchaseData: logging.error("KofiHandler: Email not in PurchaseData") return abort(400) UserEmail : str = PurchaseData["email"] logging.info(f"KofiHandler: Received donation from {UserEmail}, Transaction ID: {PurchaseData['kofi_transaction_id']}") TransactionObj : KofiTransaction = KofiTransaction.query.filter_by(kofi_transaction_id=PurchaseData["kofi_transaction_id"]).first() if TransactionObj is not None: logging.error(f"KofiHandler: Transaction ID {PurchaseData['kofi_transaction_id']} already exists") return abort(400) NewGiftcardCode : str = GenerateCode() NewGiftcardObj : GiftcardKey = GiftcardKey( key = NewGiftcardCode, type = GiftcardType.Outrageous_BuildersClub, value = 1 ) db.session.add(NewGiftcardObj) db.session.commit() NewTransactionObj : KofiTransaction = KofiTransaction( kofi_transaction_id = PurchaseData["kofi_transaction_id"], timestamp = datetime.datetime.strptime(PurchaseData["timestamp"], "%Y-%m-%dT%H:%M:%SZ"), donation_type = PurchaseData["type"], amount = float(PurchaseData["amount"]), currency = PurchaseData["currency"], is_subscription_payment = PurchaseData["is_subscription_payment"], message = PurchaseData["message"], from_name = PurchaseData["from_name"], from_email = PurchaseData["email"], assigned_key = NewGiftcardObj.key ) db.session.add(NewTransactionObj) db.session.commit() EmailData = { "Messages": [ { "From": { "Email": Config.MAILJET_NOREPLY_SENDER, "Name": "Vortexi Donation Processor" }, "To": [ { "Email": UserEmail, "Name": PurchaseData["from_name"] } ], "TemplateID": Config.MAILJET_DONATION_TEMPLATE_ID, "TemplateLanguage": True, "Subject": "Thank you for your donation!", "Variables": { "redeem_key": NewGiftcardObj.key, } } ] } EmailResponse = requests.post( url="https://api.mailjet.com/v3.1/send", data=json.dumps(EmailData), headers={ "Content-Type": "application/json" }, auth = HTTPBasicAuth( Config.MAILJET_APIKEY, Config.MAILJET_SECRETKEY ) ) if EmailResponse.status_code != 200: logging.error(f"KofiHandler: Failed to send email to {UserEmail}") logging.error(EmailResponse.json()) return "OK", 200 # We don't want to return an error to Ko-fi since we already processed the donation, so we just return OK logging.info(f"KofiHandler: Successfully sent email to {UserEmail} and processed donation") return "OK", 200