From cbc1040b5fff58485533d226baac995a87f1e572 Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 23 Dec 2023 15:16:34 -0500 Subject: [PATCH 01/38] add django-paypal package --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 1a1b387d..75047491 100644 --- a/environment.yml +++ b/environment.yml @@ -35,3 +35,4 @@ dependencies: - stripe==7.8.1 - celery==5.3.6 - mojang==1.1.0 + - django-paypal==2.0 From 84b7c091a04a916f44c42773cc79335ebe4998f3 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 17:50:04 -0500 Subject: [PATCH 02/38] logging definitions --- config/logging.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/logging.py b/config/logging.py index e941caef..d152d8aa 100644 --- a/config/logging.py +++ b/config/logging.py @@ -165,7 +165,12 @@ 'level': 'DEBUG', 'propagate': False, }, - 'donations.payments': { + 'donations.payments.stripe': { + 'handlers': ['console', 'log_file', 'mail_admins'], + 'level': 'DEBUG', + 'propagate': False, + }, + 'donations.payments.paypal': { 'handlers': ['console', 'log_file', 'mail_admins'], 'level': 'DEBUG', 'propagate': False, From e54d36083c12deaf843db6c59a7666569b2d0ae0 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:05:14 -0500 Subject: [PATCH 03/38] add app to settings and include new env variables Also document the new additions --- config/README.md | 17 ++++++++++++++--- config/settings.py | 6 +++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/config/README.md b/config/README.md index 7101c89a..1fc1d237 100644 --- a/config/README.md +++ b/config/README.md @@ -36,6 +36,9 @@ STRIPE_PUBLISHABLE_KEY='' STRIPE_SECRET_KEY='' STRIPE_WEBHOOK_SECRET='' +PAYPAL_RECEIVER_EMAIL='' +PAYPAL_DEV_WEBHOOK_DOMAIN='' + USE_CONSOLE_EMAIL= ERROR_LOG_EMAIL='' EMAIL_HOST='' @@ -103,13 +106,21 @@ A description for the Discord Bot, used when creating the Bot # *Stripe Payments* ### **STRIPE_PUBLISHABLE_KEY** -Your Stripe "Publishable Key". After signing in to Stripe, [you can find this by going to your Stripe Dashboard](https://dashboard.stripe.com/apikeys). +Your Stripe "Publishable Key". After signing in to Stripe, [you can find this by going to your Stripe Dashboard](https://dashboard.stripe.com/apikeys). This can be left blank if the Donations module will be disabled. ### **STRIPE_SECRET_KEY** -Your Stripe "Secret Key". After signing in to Stripe, [you can find this by going to your Stripe Dashboard](https://dashboard.stripe.com/apikeys). +Your Stripe "Secret Key". After signing in to Stripe, [you can find this by going to your Stripe Dashboard](https://dashboard.stripe.com/apikeys). This can be left blank if the Donations module will be disabled. ### **STRIPE_WEBHOOK_SECRET** -Your Stripe "Webhook Secret". [Create a Webhook Endpoint here](https://dashboard.stripe.com/webhooks/create), and set the URL to `https:///api/donations/payment/webhook`. After doing so, you will be able to get your Webhook Secret. +Your Stripe "Webhook Secret". [Create a Webhook Endpoint here](https://dashboard.stripe.com/webhooks/create), and set the URL to `https:///api/donations/payment/webhook`. After doing so, you will be able to get your Webhook Secret. This can be left blank if the Donations module will be disabled. + +# *Paypal Payments* + +### **PAYPAL_RECEIVER_EMAIL** +The email for the Paypal account that you wish to receive donation payments. This can be left blank if the Donations module will be disabled. + +### **PAYPAL_DEV_WEBHOOK_DOMAIN** +The publicly accessible domain for Paypal to send POST requests to on successful payments in a DEV environment. This is only required in a development environment, you should leave this empty in production. # *Email* diff --git a/config/settings.py b/config/settings.py index f02eb5fb..d32328e8 100644 --- a/config/settings.py +++ b/config/settings.py @@ -83,7 +83,8 @@ 'raptorWeb.gameservers', 'raptorWeb.donations', 'raptorWeb.raptorbot', - 'raptorWeb.panel' + 'raptorWeb.panel', + 'paypal.standard.ipn' ] MIDDLEWARE: list[str] = [ @@ -236,6 +237,9 @@ STRIPE_PUBLISHABLE_KEY = '' if str(getenv('STRIPE_PUBLISHABLE_KEY')) == '' else str(getenv('STRIPE_PUBLISHABLE_KEY')) STRIPE_SECRET_KEY = '' if str(getenv('STRIPE_SECRET_KEY')) == '' else str(getenv('STRIPE_SECRET_KEY')) STRIPE_WEBHOOK_SECRET = '' if str(getenv('STRIPE_WEBHOOK_SECRET')) == '' else str(getenv('STRIPE_WEBHOOK_SECRET')) +PAYPAL_RECEIVER_EMAIL = '' if getenv('PAYPAL_RECEIVER_EMAIL') == '' else getenv('PAYPAL_RECEIVER_EMAIL') +PAYPAL_DEV_WEBHOOK_DOMAIN = '' if getenv('PAYPAL_DEV_WEBHOOK_DOMAIN') == '' else getenv('PAYPAL_DEV_WEBHOOK_DOMAIN') +PAYPAL_TEST = True # Path to json file to import servers from IMPORT_JSON_LOCATION: str = join(BASE_DIR, 'server_data_full.json') From 9666b4e881adb8e4625ab55857ca0b21d6128bbe Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:05:25 -0500 Subject: [PATCH 04/38] form to choose payment gateway --- raptorWeb/donations/forms.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raptorWeb/donations/forms.py b/raptorWeb/donations/forms.py index 8e7c2f8c..3ad043d9 100644 --- a/raptorWeb/donations/forms.py +++ b/raptorWeb/donations/forms.py @@ -26,3 +26,14 @@ class DonationPriceForm(forms.Form): "above the package's price."), required=False ) + + +class DonationGatewayForm(forms.Form): + payment_gateway = forms.ChoiceField( + widget=forms.RadioSelect, + required=True, + choices=[ + ('stripe', 'Stripe'), + ('paypal', 'Paypal'), + ], + ) From 3034686f287949a57292b8a0f92d8f4b51b7d24b Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:05:57 -0500 Subject: [PATCH 05/38] Add paypal invoice field to completed donation models This will be used if paypal is used. Both this and checkout_id can be empty, as you will be using one or the other. --- ...mpleteddonation_paypal_invoice_and_more.py | 23 +++++++++++++++++++ raptorWeb/donations/models.py | 11 ++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 raptorWeb/donations/migrations/0020_completeddonation_paypal_invoice_and_more.py diff --git a/raptorWeb/donations/migrations/0020_completeddonation_paypal_invoice_and_more.py b/raptorWeb/donations/migrations/0020_completeddonation_paypal_invoice_and_more.py new file mode 100644 index 00000000..48470ea8 --- /dev/null +++ b/raptorWeb/donations/migrations/0020_completeddonation_paypal_invoice_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2023-12-24 20:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('donations', '0019_alter_donationpackage_variable_price'), + ] + + operations = [ + migrations.AddField( + model_name='completeddonation', + name='paypal_invoice', + field=models.CharField(blank=True, help_text='The unique ID for the Paypal Invoice generated for this donation.', max_length=1000, null=True, verbose_name='Paypal Invoice ID'), + ), + migrations.AlterField( + model_name='completeddonation', + name='checkout_id', + field=models.CharField(blank=True, help_text='The unique ID for the Stripe Checkout session this Donation utilized.', max_length=1000, null=True, verbose_name='Stripe Checkout ID'), + ), + ] diff --git a/raptorWeb/donations/models.py b/raptorWeb/donations/models.py index 42c5270b..6b58f3a3 100644 --- a/raptorWeb/donations/models.py +++ b/raptorWeb/donations/models.py @@ -184,12 +184,21 @@ class CompletedDonation(models.Model): ) checkout_id = models.CharField( - default="", max_length=1000, + blank=True, + null=True, verbose_name="Stripe Checkout ID", help_text=("The unique ID for the Stripe Checkout session this Donation utilized.") ) + paypal_invoice = models.CharField( + max_length=1000, + blank=True, + null=True, + verbose_name="Paypal Invoice ID", + help_text=("The unique ID for the Paypal Invoice generated for this donation.") + ) + sent_commands_count = models.IntegerField( default=0, verbose_name="Times commands were sent", From 0c883afd73adefc1721e35f67146e8ba2e1f0660 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:06:36 -0500 Subject: [PATCH 06/38] tasks use completed_donation PK rather than checkout_id --- raptorWeb/donations/tasks.py | 8 ++++---- raptorWeb/donations/views.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/raptorWeb/donations/tasks.py b/raptorWeb/donations/tasks.py index 62131bd9..ce1c7a44 100644 --- a/raptorWeb/donations/tasks.py +++ b/raptorWeb/donations/tasks.py @@ -55,27 +55,27 @@ def send_donation_email(completed_donation_checkout_id: CompletedDonation): ) @shared_task -def send_server_commands(completed_donation_checkout_id: CompletedDonation): +def send_server_commands(completed_donation_id: CompletedDonation): """ Given a completed/paid for donation, send all commands attached to the bought package to all the servers attached to the bought package. """ completed_donation = CompletedDonation.objects.get( - checkout_id=completed_donation_checkout_id + pk=completed_donation_id ) if completed_donation.bought_package.commands.all().count() > 0: completed_donation.send_server_commands() @shared_task -def add_discord_bot_roles(completed_donation_checkout_id: CompletedDonation): +def add_discord_bot_roles(completed_donation_id: CompletedDonation): """ Given a completed/paid for donation, give all discord roles attached to the bought package to the Discord tag attached to the completed donation. Will only run if the complete donation ahs a Discord tag. """ completed_donation = CompletedDonation.objects.get( - checkout_id=completed_donation_checkout_id + pk=completed_donation_id ) if completed_donation.bought_package.discord_roles.all().count() > 0: diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index 33b3a896..e341ac2f 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -326,11 +326,13 @@ def donation_payment_webhook(request: HttpRequest): completed_donation.completed = True if completed_donation.bought_package.servers.all().count() > 0: send_server_commands.apply_async( - args=(completed_donation.checkout_id,), + args=(completed_donation.pk,), countdown=10 ) + + if completed_donation.bought_package.discord_roles.all().count() > 0: add_discord_bot_roles.apply_async( - args=(completed_donation.checkout_id,), + args=(completed_donation.pk,), countdown=5 ) @@ -341,7 +343,7 @@ def donation_payment_webhook(request: HttpRequest): if site_info.send_donation_email: send_donation_email.apply_async( - args=(completed_donation.checkout_id,), + args=(completed_donation.pk,), countdown=5 ) From d7363f515486c435bd2c036f327e00a6350334b3 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:06:49 -0500 Subject: [PATCH 07/38] URL for paypal IPN endpoint --- raptorWeb/donations/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raptorWeb/donations/urls.py b/raptorWeb/donations/urls.py index 64244370..86478f95 100644 --- a/raptorWeb/donations/urls.py +++ b/raptorWeb/donations/urls.py @@ -1,4 +1,4 @@ -from django.urls import URLPattern, path +from django.urls import URLPattern, path, include from raptorWeb.donations import views @@ -18,6 +18,7 @@ path('donation/delete/', views.DonationDelete.as_view(), name="donation_delete"), path('payment/webhook', views.donation_payment_webhook, name="payment_webook"), path('payment/webhook/', views.donation_payment_webhook, name="payment_webook"), + path('payment/paypal_webhook', include('paypal.standard.ipn.urls')), # Admin path('donation/resend/', views.DonationBenefitResend.as_view(), name="resend"), ] \ No newline at end of file From 539adde25d2906aaf4302cb34a88a44e954f522b Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:07:23 -0500 Subject: [PATCH 08/38] stripe to payments module --- raptorWeb/donations/{payments.py => payments/stripe.py} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename raptorWeb/donations/{payments.py => payments/stripe.py} (93%) diff --git a/raptorWeb/donations/payments.py b/raptorWeb/donations/payments/stripe.py similarity index 93% rename from raptorWeb/donations/payments.py rename to raptorWeb/donations/payments/stripe.py index 5134fa5b..6510a325 100644 --- a/raptorWeb/donations/payments.py +++ b/raptorWeb/donations/payments/stripe.py @@ -1,5 +1,6 @@ from logging import getLogger +from django.http import HttpRequest from django.conf import settings import stripe @@ -7,7 +8,7 @@ from raptorWeb.donations.models import DonationPackage, CompletedDonation from raptorWeb.raptormc.models import SiteInformation -LOGGER = getLogger('donations.payments') +LOGGER = getLogger('donations.payments.stripe') DOMAIN_NAME: str = getattr(settings, 'DOMAIN_NAME') WEB_PROTO: str = getattr(settings, 'WEB_PROTO') STRIPE_PUBLISHABLE_KEY: str = getattr(settings, 'STRIPE_PUBLISHABLE_KEY') @@ -47,7 +48,7 @@ def retrieve_checkout_session(checkout_id: str): id=checkout_id ) -def get_checkout_url(request, bought_package: DonationPackage, minecraft_username: str, discord_username: str): +def get_checkout_url(request: HttpRequest, bought_package: DonationPackage, minecraft_username: str, discord_username: str): """ Return a checkout URL for the given request """ From 143e43b6c8fe52f4745bcb3c95b82293108f10e8 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:09:27 -0500 Subject: [PATCH 09/38] paypal module in payments This flow utilizes standard mechanisms from django-paypal. It creates a compelted donation marked as incomplete the same as the stripe flow, however we are returning the django-paybal "Buy it Now" form. We are not going to be using this in a standard way in the view This begs the question of why I did not just use Paypals API. This works is the answer. --- raptorWeb/donations/payments/paypal.py | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 raptorWeb/donations/payments/paypal.py diff --git a/raptorWeb/donations/payments/paypal.py b/raptorWeb/donations/payments/paypal.py new file mode 100644 index 00000000..b0b605de --- /dev/null +++ b/raptorWeb/donations/payments/paypal.py @@ -0,0 +1,75 @@ +from logging import getLogger +from uuid import uuid1 + +from django.http import HttpRequest +from django.shortcuts import redirect +from django.conf import settings + +from paypal.standard.forms import PayPalPaymentsForm +from paypal.standard.models import ST_PP_COMPLETED +from paypal.standard.ipn.signals import valid_ipn_received + +from raptorWeb.donations.models import DonationPackage, CompletedDonation +from raptorWeb.donations.tasks import send_server_commands, add_discord_bot_roles, send_donation_email +from raptorWeb.raptormc.models import SiteInformation + +LOGGER = getLogger('donations.payments.paypal') +DOMAIN_NAME: str = getattr(settings, 'DOMAIN_NAME') +WEB_PROTO: str = getattr(settings, 'WEB_PROTO') +DEBUG: str = getattr(settings, 'DEBUG') +PAYPAL_DEV_WEBHOOK_DOMAIN: str = getattr(settings, 'PAYPAL_DEV_WEBHOOK_DOMAIN') +PAYPAL_RECEIVER_EMAIL: str = getattr(settings, 'PAYPAL_RECEIVER_EMAIL') + +def get_paypal_checkout_button(request: HttpRequest, bought_package: DonationPackage, minecraft_username: str, discord_username: str): + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] + invoice_id = f"raptor-{uuid1()}" + + try: + CompletedDonation.objects.get( + minecraft_username=minecraft_username, + bought_package=bought_package, + completed=False + ).delete() + + except CompletedDonation.DoesNotExist: + pass + + new_donation = CompletedDonation.objects.create( + minecraft_username=minecraft_username, + bought_package=bought_package, + spent=bought_package.price, + session_id=request.session.session_key, + paypal_invoice=invoice_id, + completed=False + ) + + if discord_username != '': + new_donation.discord_username = discord_username + + if request.user.is_authenticated: + new_donation.donating_user = request.user + + new_donation.save() + + form_data = { + "business": PAYPAL_RECEIVER_EMAIL, + "amount": str(bought_package.price), + 'currency_code': site_info.donation_currency.upper(), + "item_name": f"{bought_package.name} for {minecraft_username}", + "invoice": invoice_id, + "return": f'{WEB_PROTO}://{DOMAIN_NAME}/donations/success', + "cancel_return": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/cancel', + } + + if DEBUG: + form_data.update({ + "notify_url": f'https://{PAYPAL_DEV_WEBHOOK_DOMAIN}/api/donations/payment/paypal_webhook', + }) + + else: + form_data.update({ + "notify_url": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/paypal_webhook', + }) + + return PayPalPaymentsForm(initial=form_data) + From 1b63070a89338a1670bee9d84ec55a23b6705648 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:10:30 -0500 Subject: [PATCH 10/38] IPN reciever listener This code is called when paypal sends the POST request on successful payment, and django-paypal verifies it as valid. We are checking if the invoice ID matches what we have in the database, as well as the package price and receiver email. We then complete the donation in the same way it is handled via Stripe. --- raptorWeb/donations/payments/paypal.py | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/raptorWeb/donations/payments/paypal.py b/raptorWeb/donations/payments/paypal.py index b0b605de..31b3cb9b 100644 --- a/raptorWeb/donations/payments/paypal.py +++ b/raptorWeb/donations/payments/paypal.py @@ -73,3 +73,53 @@ def get_paypal_checkout_button(request: HttpRequest, bought_package: DonationPac return PayPalPaymentsForm(initial=form_data) +def receive_paypal_ipn(sender, **kwargs): + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] + ipn_obj = sender + if ipn_obj.payment_status == ST_PP_COMPLETED: + + if ipn_obj.receiver_email != PAYPAL_RECEIVER_EMAIL: + return + + try: + completed_donation = CompletedDonation.objects.get( + paypal_invoice=ipn_obj.invoice + ) + + if (int(ipn_obj.mc_gross) != int(completed_donation.spent) + and ipn_obj.mc_currency != site_info.donation_currency.upper()): + return + + completed_donation.completed = True + + if completed_donation.bought_package.servers.all().count() > 0: + send_server_commands.apply_async( + args=(completed_donation.pk,), + countdown=10 + ) + + if completed_donation.bought_package.discord_roles.all().count() > 0: + add_discord_bot_roles.apply_async( + args=(completed_donation.pk,), + countdown=5 + ) + + completed_donation.save() + + if site_info.send_donation_email: + send_donation_email.apply_async( + args=(completed_donation.checkout_id,), + countdown=5 + ) + + site_info.donation_goal_progress += completed_donation.spent + site_info.save() + + except CompletedDonation.DoesNotExist: + return + + else: + return + +# Register receive_paypal_ipn function with paypal-django's IPN listener +valid_ipn_received.connect(receive_paypal_ipn) From e31c15bfa16d03afcfa0e191250b196dcbe0104e Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:11:10 -0500 Subject: [PATCH 11/38] use donation gateway form --- raptorWeb/donations/views.py | 11 +++++++---- raptorWeb/templates/donations/checkout.html | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index e341ac2f..22c218e3 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -1,12 +1,12 @@ from logging import getLogger from os.path import join from typing import Any -from django.db.models.query import QuerySet from django.views.generic import ListView, TemplateView, View from django.http import HttpResponseRedirect, HttpResponse, HttpRequest -from django.shortcuts import redirect +from django.shortcuts import redirect, render from django.views.decorators.csrf import csrf_exempt +from django.db.models.query import QuerySet from django.contrib import messages from django.conf import settings @@ -15,10 +15,11 @@ from raptorWeb.raptormc.models import DefaultPages, SiteInformation from raptorWeb.donations.models import DonationPackage, CompletedDonation -from raptorWeb.donations.forms import SubmittedDonationForm, DonationDiscordUsernameForm, DonationPriceForm +from raptorWeb.donations.forms import SubmittedDonationForm, DonationDiscordUsernameForm, DonationPriceForm, DonationGatewayForm from raptorWeb.donations.tasks import send_server_commands, add_discord_bot_roles, send_donation_email from raptorWeb.donations.mojang import verify_minecraft_username -from raptorWeb.donations.payments import get_checkout_url +from raptorWeb.donations.payments.stripe import get_checkout_url +from raptorWeb.donations.payments.paypal import get_paypal_checkout_button DONATIONS_TEMPLATE_DIR: str = getattr(settings, 'DONATIONS_TEMPLATE_DIR') BASE_USER_URL: str = getattr(settings, 'BASE_USER_URL') @@ -125,12 +126,14 @@ def get(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespons def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) + bought_package = DonationPackage.objects.get(name=str(self.kwargs['package'])) context['buying_package'] = bought_package context['base_user_url'] = BASE_USER_URL context['donation_price_form'] = DonationPriceForm({'chosen_price': bought_package.price}) context['discord_username_form'] = DonationDiscordUsernameForm() context['donation_details_form'] = SubmittedDonationForm() + context['donation_gateway_form'] = DonationGatewayForm() return context diff --git a/raptorWeb/templates/donations/checkout.html b/raptorWeb/templates/donations/checkout.html index c7ab95d4..6c7ca09a 100644 --- a/raptorWeb/templates/donations/checkout.html +++ b/raptorWeb/templates/donations/checkout.html @@ -63,6 +63,10 @@
{{buying_package.package_description|safe}}
+ +
+ {% bootstrap_field donation_gateway_form.payment_gateway %} +
{% if buying_package.variable_price %} @@ -116,6 +120,11 @@
{{buying_package.package_description|safe}}
+ +
+ {% bootstrap_field donation_gateway_form.payment_gateway %} +
+
{% if buying_package.variable_price %} {% bootstrap_field donation_price_form.chosen_price %} From 1ef75dc555a0eb7bb1cb66f25839c71b1703dcb2 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:11:45 -0500 Subject: [PATCH 12/38] check payment gateway choice and do the corresponding action --- raptorWeb/donations/views.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index 22c218e3..b750e430 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -145,6 +145,8 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon if not DefaultPages.objects.get_or_create(pk=1)[0].donations: return HttpResponseRedirect('/404') + payment_gateway_choice = request.POST.get('payment_gateway') + try: minecraft_username: str = request.POST.get('minecraft_username') discord_username: str = request.POST.get('discord_username') @@ -189,15 +191,33 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon ).count() > 0: return HttpResponseRedirect('/donations/previousdonation') - - return redirect( - get_checkout_url( + + if payment_gateway_choice == 'stripe': + return redirect( + get_checkout_url( + request, + bought_package, + minecraft_username, + discord_username + ) + ) + + if payment_gateway_choice == 'paypal': + paypal_button = get_paypal_checkout_button( request, bought_package, minecraft_username, discord_username ) - ) + + return render( + request, + join(DONATIONS_TEMPLATE_DIR, 'paypal_checkout_redirect.html'), + context={'form': paypal_button} + ) + + else: + return HttpResponseRedirect('/donations/failure') class DonationCancel(View): From 6911306f794b59d998f36acd8196d03b4d475278 Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:13:31 -0500 Subject: [PATCH 13/38] paypal redirect template This template renders the "Buy it Now" django-paypal button as hidden, and displays a simple message that you are being redirected. A script exists here, that will automatically submit the form. This is a simple way that I found to automatically direct the donator to the paypal payment page, without having separate buttons for Paypal/stripe checkout and adding a second click in between. As far as I can tell, there is not a downside to doing it this way. If a user has Javascript disabled, they're fucked anyway. --- .../donations/paypal_checkout_redirect.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 raptorWeb/templates/donations/paypal_checkout_redirect.html diff --git a/raptorWeb/templates/donations/paypal_checkout_redirect.html b/raptorWeb/templates/donations/paypal_checkout_redirect.html new file mode 100644 index 00000000..e18494dc --- /dev/null +++ b/raptorWeb/templates/donations/paypal_checkout_redirect.html @@ -0,0 +1,19 @@ +
+ {{ form.render }} +
+ +
+ You are being redirected, please wait. +
+ + From d28c427e48f8423573d1ed61c8387acc3fc0e31f Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:14:36 -0500 Subject: [PATCH 14/38] Show checkout_id or paypal_invoice depending on payment gateway used We are also using the donation PK rather than checkout_id in the benefit resending endpoints --- raptorWeb/donations/views.py | 7 ++++--- .../templates/donations/completeddonation_list.html | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index b750e430..1a3e28ba 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -284,7 +284,7 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon do_commands = request.GET.get('do_commands') do_roles = request.GET.get('do_roles') completed_donation = CompletedDonation.objects.get( - checkout_id=request.GET.get('checkout_id') + id=request.GET.get('id') ) if do_commands == 'true': @@ -296,12 +296,13 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon messages.error(request, f'Cannot re-send commands for this donation, the package has no commands to send!') if do_roles == 'true': - if completed_donation.bought_package.discord_roles.all().count() > 0: + if completed_donation.bought_package.discord_roles.all().count() > 0 and completed_donation.discord_username != None: completed_donation.give_discord_roles() messages.success(request, f'Re-gave Discord Roles for {completed_donation.discord_username}') else: - messages.error(request, f'Cannot re-give Discord Roles for this donation, the package has no roles to give!') + messages.error(request, f'Cannot re-give Discord Roles for this donation, the package has no roles to give ' + 'or the donation was not made with a Discord Username') return HttpResponse(status=200) diff --git a/raptorWeb/templates/donations/completeddonation_list.html b/raptorWeb/templates/donations/completeddonation_list.html index a3c7df41..0b912e70 100644 --- a/raptorWeb/templates/donations/completeddonation_list.html +++ b/raptorWeb/templates/donations/completeddonation_list.html @@ -117,9 +117,15 @@

Session ID: {{package.session_id}}

+ {% if package.checkout_id %}

Stripe Checkout ID: {{package.checkout_id}}

+ {% else %} +

+ Paypal Invoice ID: {{package.paypal_invoice}} +

+ {% endif %}

Times commands were sent: {{package.sent_commands_count}} @@ -135,7 +141,7 @@

{% csrf_token %}
\ No newline at end of file diff --git a/raptorWeb/templates/donations/successful_email.txt b/raptorWeb/templates/donations/successful_email.txt index df7810a9..9a5d7e39 100644 --- a/raptorWeb/templates/donations/successful_email.txt +++ b/raptorWeb/templates/donations/successful_email.txt @@ -3,4 +3,4 @@ Thank you so much for your donation, {{ donating_user }}! Package: {{ package }} We are sending this email to confirm we got your donation, and that your benefits should have already arrived. -This is not a receipt! Look for an email from Stripe to keep for your records. +This is not a receipt! Look for an email from your chosen Payment Processor to keep for your records. From 605875cddde5e264300b3a9dab598363a9e9bd5b Mon Sep 17 00:00:00 2001 From: zediious Date: Sun, 24 Dec 2023 18:14:59 -0500 Subject: [PATCH 16/38] payments module init --- raptorWeb/donations/payments/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 raptorWeb/donations/payments/__init__.py diff --git a/raptorWeb/donations/payments/__init__.py b/raptorWeb/donations/payments/__init__.py new file mode 100644 index 00000000..e69de29b From 6fe2f66879ffab77144cea225dca7484258b5385 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:14:14 -0500 Subject: [PATCH 17/38] Derive PAYPAL_TEST from DEBUG --- config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.py b/config/settings.py index d32328e8..cd3020d6 100644 --- a/config/settings.py +++ b/config/settings.py @@ -239,7 +239,7 @@ STRIPE_WEBHOOK_SECRET = '' if str(getenv('STRIPE_WEBHOOK_SECRET')) == '' else str(getenv('STRIPE_WEBHOOK_SECRET')) PAYPAL_RECEIVER_EMAIL = '' if getenv('PAYPAL_RECEIVER_EMAIL') == '' else getenv('PAYPAL_RECEIVER_EMAIL') PAYPAL_DEV_WEBHOOK_DOMAIN = '' if getenv('PAYPAL_DEV_WEBHOOK_DOMAIN') == '' else getenv('PAYPAL_DEV_WEBHOOK_DOMAIN') -PAYPAL_TEST = True +PAYPAL_TEST = True if DEBUG else False # Path to json file to import servers from IMPORT_JSON_LOCATION: str = join(BASE_DIR, 'server_data_full.json') From 1bbb91ad84b9f22bdfadac3e22e4e8f107ee5673 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:16:27 -0500 Subject: [PATCH 18/38] Use PK in email send arguemtn --- raptorWeb/donations/payments/paypal.py | 2 +- raptorWeb/donations/tasks.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raptorWeb/donations/payments/paypal.py b/raptorWeb/donations/payments/paypal.py index 31b3cb9b..e018e9fe 100644 --- a/raptorWeb/donations/payments/paypal.py +++ b/raptorWeb/donations/payments/paypal.py @@ -108,7 +108,7 @@ def receive_paypal_ipn(sender, **kwargs): if site_info.send_donation_email: send_donation_email.apply_async( - args=(completed_donation.checkout_id,), + args=(completed_donation.pk,), countdown=5 ) diff --git a/raptorWeb/donations/tasks.py b/raptorWeb/donations/tasks.py index ce1c7a44..86655520 100644 --- a/raptorWeb/donations/tasks.py +++ b/raptorWeb/donations/tasks.py @@ -20,13 +20,13 @@ EMAIL_HOST_USER: str = getattr(settings, 'EMAIL_HOST_USER') @shared_task -def send_donation_email(completed_donation_checkout_id: CompletedDonation): +def send_donation_email(completed_donation_id: CompletedDonation): """ Given a completed/paid for donation, send an email to the email associated with the donating user, if the donation has a donating user. """ completed_donation = CompletedDonation.objects.get( - checkout_id=completed_donation_checkout_id + pk=completed_donation_id ) if completed_donation.donating_user != None: From 31e725ad5b81388cbe959bb7da3b9d0d4f033e51 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:35:16 -0500 Subject: [PATCH 19/38] Hide Paypal IPN app in sidebar and add link from completed donations --- config/settings.py | 1 + raptorWeb/templates/panel/panel_donations.html | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/config/settings.py b/config/settings.py index cd3020d6..9027071a 100644 --- a/config/settings.py +++ b/config/settings.py @@ -315,6 +315,7 @@ # Sidebar "show_sidebar": True, "navigation_expanded": False, + "hide_apps": ['ipn'], "order_with_respect_to": ["raptormc", "gameservers", "raptorbot", "staffapps", "authprofiles"], "custom_links": { "raptorbot": [{ diff --git a/raptorWeb/templates/panel/panel_donations.html b/raptorWeb/templates/panel/panel_donations.html index 607e8780..67a8e513 100644 --- a/raptorWeb/templates/panel/panel_donations.html +++ b/raptorWeb/templates/panel/panel_donations.html @@ -9,6 +9,18 @@

+
From 1cc67459c731553a64e1cc8ea66b7faa61d618c3 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:55:29 -0500 Subject: [PATCH 20/38] jazzmin admin sidebar configuration changes re-ordering, and changing icons --- config/settings.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/config/settings.py b/config/settings.py index 9027071a..97b50922 100644 --- a/config/settings.py +++ b/config/settings.py @@ -316,7 +316,7 @@ "show_sidebar": True, "navigation_expanded": False, "hide_apps": ['ipn'], - "order_with_respect_to": ["raptormc", "gameservers", "raptorbot", "staffapps", "authprofiles"], + "order_with_respect_to": ["raptormc", "gameservers", "raptorbot", 'donations', "staffapps", "authprofiles"], "custom_links": { "raptorbot": [{ "name": "Discord Bot Control Panel", @@ -333,7 +333,13 @@ "donations": [{ "name": "Completed Donations", "url": "/panel/donations/", - "icon": "fa fa-credit-card", + "icon": "fa fa-check-double", + "permissions": ["raptormc.donations"] + }], + "donations": [{ + "name": "Completed Donations", + "url": "/panel/donations/", + "icon": "fa fa-check-double", "permissions": ["raptormc.donations"] }] }, @@ -360,9 +366,10 @@ "authprofiles.RaptorUser": "fas fa-user", "authprofiles.UserProfileInfo": "fas fa-user-tag", "authprofiles.DiscordUserInfo": "fas fa-user-tag", + "donations": "fa fa-coins", "donations.DonationPackage": "fa fa-archive", "donations.DonationServerCommand": "fa fa-terminal", - "donations.DonationDiscordRole": "fa fa-tags", + "donations.DonationDiscordRole": "fa fa-mask", }, "default_icon_parents": "fas fa-chevron-circle-right", @@ -389,7 +396,6 @@ "theme": "cyborg", "actions_sticky_top": False, "sidebar_nav_child_indent": True, - "related_modal_active": True } From 98d7cb7cf1bd3bb7acce246f2ded3269f12d8c72 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:55:42 -0500 Subject: [PATCH 21/38] add labels and help text to donation forms --- raptorWeb/donations/forms.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raptorWeb/donations/forms.py b/raptorWeb/donations/forms.py index 3ad043d9..114039c6 100644 --- a/raptorWeb/donations/forms.py +++ b/raptorWeb/donations/forms.py @@ -2,10 +2,14 @@ class SubmittedDonationForm(forms.Form): minecraft_username = forms.CharField( + label='Minecraft Username', + help_text=("Your Minecraft in-game name. This must be supplied, and " + "and it must be a real Minecraft username."), required=True, ) discord_username = forms.CharField( + label='Discord Username', help_text=("This is not required, but you must enter a Discord " "username if you want to receive Roles on Discord."), required=False, @@ -14,6 +18,7 @@ class SubmittedDonationForm(forms.Form): class DonationDiscordUsernameForm(forms.Form): discord_username = forms.CharField( + label='Discord Username', help_text=("This is not required, but you must enter a Discord " "username if you want to receive Roles on Discord."), required=False, @@ -22,6 +27,7 @@ class DonationDiscordUsernameForm(forms.Form): class DonationPriceForm(forms.Form): chosen_price = forms.IntegerField( + label='Donation Amount', help_text=("Enter any whole amount you would like to donate " "above the package's price."), required=False @@ -30,6 +36,8 @@ class DonationPriceForm(forms.Form): class DonationGatewayForm(forms.Form): payment_gateway = forms.ChoiceField( + label='Payment Gateway', + help_text='Choose your preferred way to pay.', widget=forms.RadioSelect, required=True, choices=[ From c630219827e0f0a063ac104ac55b188668bb2915 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 17:56:13 -0500 Subject: [PATCH 22/38] Add checkout redirect disclaimer, and combine info verification with this message Also added some horizontal line to better separate the sections in the checkout page. --- raptorWeb/templates/donations/checkout.html | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/raptorWeb/templates/donations/checkout.html b/raptorWeb/templates/donations/checkout.html index 6c7ca09a..0ccb1012 100644 --- a/raptorWeb/templates/donations/checkout.html +++ b/raptorWeb/templates/donations/checkout.html @@ -42,9 +42,6 @@ Go to Profile
-

- Make sure to confirm the above information before clicking Checkout! -


{% endif %}
@@ -63,7 +60,7 @@
{{buying_package.package_description|safe}}
- +
{% bootstrap_field donation_gateway_form.payment_gateway %}
@@ -75,6 +72,11 @@ +

+ Clicking the above button will redirect you to the payment processor + to complete this donation. Make sure to confirm the above information + before clicking Checkout! +

@@ -99,9 +101,6 @@ {% bootstrap_field donation_details_form.minecraft_username %} {% if buying_package.servers.count > 0 %} {% bootstrap_field donation_details_form.discord_username %} -

- Make sure to confirm the above information before clicking Checkout! -

{% endif %}
@@ -120,7 +119,7 @@
{{buying_package.package_description|safe}}
- +
{% bootstrap_field donation_gateway_form.payment_gateway %}
@@ -132,6 +131,11 @@ +

+ Clicking the above button will redirect you to the payment processor + to complete this donation. Make sure to confirm the above information + before clicking Checkout! +

From 5c8f436f32191889f3d0907cb5e5b24fdd01da35 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:12:22 -0500 Subject: [PATCH 23/38] donation pages/system disabled by default --- raptorWeb/raptormc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raptorWeb/raptormc/models.py b/raptorWeb/raptormc/models.py index 523be2a2..5ffbef30 100644 --- a/raptorWeb/raptormc/models.py +++ b/raptorWeb/raptormc/models.py @@ -736,7 +736,7 @@ class DefaultPages(models.Model): ) donations = models.BooleanField( - default=True, + default=False, verbose_name="Donations Pages", help_text=("Whether the Donations system/pages are enabled or not.") ) From d024c93499e55c6567297cdab25816bea1fd2e55 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:12:54 -0500 Subject: [PATCH 24/38] paypal and stripe booleans These will be used to configure which payment processors are enabled. --- ...siteinformation_paypal_enabled_and_more.py | 28 +++++++++++++++++++ raptorWeb/raptormc/models.py | 14 ++++++++++ 2 files changed, 42 insertions(+) create mode 100644 raptorWeb/raptormc/migrations/0070_siteinformation_paypal_enabled_and_more.py diff --git a/raptorWeb/raptormc/migrations/0070_siteinformation_paypal_enabled_and_more.py b/raptorWeb/raptormc/migrations/0070_siteinformation_paypal_enabled_and_more.py new file mode 100644 index 00000000..9d6e2b7f --- /dev/null +++ b/raptorWeb/raptormc/migrations/0070_siteinformation_paypal_enabled_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.7 on 2023-12-25 23:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('raptormc', '0069_siteinformation_donation_currency'), + ] + + operations = [ + migrations.AddField( + model_name='siteinformation', + name='paypal_enabled', + field=models.BooleanField(default=True, help_text='If this is checked, PayPal will be given as an option to pay when Donating. At least one Payment Gateway must be enabled.', verbose_name='PayPal Payment Gateway'), + ), + migrations.AddField( + model_name='siteinformation', + name='stripe_enabled', + field=models.BooleanField(default=True, help_text='If this is checked, Stripe will be given as an option to pay when Donating. At least one Payment Gateway must be enabled.', verbose_name='Stripe Payment Gateway'), + ), + migrations.AlterField( + model_name='defaultpages', + name='donations', + field=models.BooleanField(default=False, help_text='Whether the Donations system/pages are enabled or not.', verbose_name='Donations Pages'), + ), + ] diff --git a/raptorWeb/raptormc/models.py b/raptorWeb/raptormc/models.py index 5ffbef30..4aeb3815 100644 --- a/raptorWeb/raptormc/models.py +++ b/raptorWeb/raptormc/models.py @@ -524,6 +524,20 @@ class SiteInformation(models.Model): default=True ) + stripe_enabled = models.BooleanField( + verbose_name="Stripe Payment Gateway", + help_text=("If this is checked, Stripe will be given as an option " + "to pay when Donating. At least one Payment Gateway must be enabled."), + default=True + ) + + paypal_enabled = models.BooleanField( + verbose_name="PayPal Payment Gateway", + help_text=("If this is checked, PayPal will be given as an option " + "to pay when Donating. At least one Payment Gateway must be enabled."), + default=True + ) + donation_currency = models.CharField( verbose_name='Donation Currency', help_text='The currency used when accepting donations.', From 21c5adea578831731cb3cc32ad4578b6301ddd84 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:19:59 -0500 Subject: [PATCH 25/38] correct wrong render calls These were "working" but causing errors and only need to be simple responses. --- raptorWeb/panel/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raptorWeb/panel/views.py b/raptorWeb/panel/views.py index f73da417..a4ba8c6e 100644 --- a/raptorWeb/panel/views.py +++ b/raptorWeb/panel/views.py @@ -261,7 +261,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: changed_string += f'{change}, ' if changed == []: messages.error(request, 'You must change upload new files to update settings.') - return render(request, self.template_name, context=dictionary) + return HttpResponse(status=200) site_info.save() messages.success(request, @@ -312,7 +312,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: changed_string += f'{change}, ' if changed == []: messages.error(request, 'You must change the current settings before updating them.') - return render(request, self.template_name, context=dictionary) + return HttpResponse(status=200) default_pages.save() messages.success(request, From ff9ce35776b694258169f62c74f6f535852aa5e2 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:20:54 -0500 Subject: [PATCH 26/38] Add payment gateway settings and check that at least one is enabled --- raptorWeb/panel/views.py | 5 +++++ raptorWeb/templates/panel/panel_settings.html | 2 ++ 2 files changed, 7 insertions(+) diff --git a/raptorWeb/panel/views.py b/raptorWeb/panel/views.py index a4ba8c6e..a95d1b6a 100644 --- a/raptorWeb/panel/views.py +++ b/raptorWeb/panel/views.py @@ -190,6 +190,11 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: changed.append(field_string.title()) except KeyError: continue + + if (settings_form.cleaned_data['stripe_enabled'] == False + and settings_form.cleaned_data['paypal_enabled'] == False): + messages.error(request, 'You must have at least one Payment Gateway enabled.') + return HttpResponse(status=200) if changed == []: messages.error(request, 'You must change some values to update settings.') diff --git a/raptorWeb/templates/panel/panel_settings.html b/raptorWeb/templates/panel/panel_settings.html index 3d098cc5..59a8926b 100644 --- a/raptorWeb/templates/panel/panel_settings.html +++ b/raptorWeb/templates/panel/panel_settings.html @@ -120,6 +120,8 @@
+ {% bootstrap_field SettingsInformation.stripe_enabled %} + {% bootstrap_field SettingsInformation.paypal_enabled %} {% bootstrap_field SettingsInformation.donation_currency %} {% bootstrap_field SettingsInformation.donation_goal %} {% bootstrap_field SettingsInformation.show_donation_goal %} From a149fb619bfc3aab5f5810ad7623fa7e7733dc7e Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:39:36 -0500 Subject: [PATCH 27/38] supply form only if both gateways are enabled --- raptorWeb/donations/views.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index 1a3e28ba..b7d4c13e 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -126,14 +126,23 @@ def get(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespons def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) - + + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] bought_package = DonationPackage.objects.get(name=str(self.kwargs['package'])) + context['buying_package'] = bought_package context['base_user_url'] = BASE_USER_URL context['donation_price_form'] = DonationPriceForm({'chosen_price': bought_package.price}) context['discord_username_form'] = DonationDiscordUsernameForm() context['donation_details_form'] = SubmittedDonationForm() - context['donation_gateway_form'] = DonationGatewayForm() + + if site_info.paypal_enabled and site_info.stripe_enabled: + context['donation_gateway_form'] = DonationGatewayForm() + context['single_gateway'] = False + + else: + context['single_gateway'] = True + return context From 7ce731a72b379de5b0ed6a0b0def2b4ab660a245 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:39:59 -0500 Subject: [PATCH 28/38] derive choice from settings if both gateways are disabled --- raptorWeb/donations/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index b7d4c13e..e5811ee8 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -184,6 +184,17 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon except DonationPackage.DoesNotExist: return HttpResponseRedirect('/') + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] + + if site_info.paypal_enabled and site_info.stripe_enabled: + payment_gateway_choice = request.POST.get('payment_gateway') + + elif site_info.paypal_enabled == False and site_info.stripe_enabled: + payment_gateway_choice = 'stripe' + + elif site_info.paypal_enabled and site_info.stripe_enabled == False: + payment_gateway_choice = 'paypal' + if bought_package.variable_price: chosen_price = request.POST.get('chosen_price') if int(chosen_price) < bought_package.price: From ee8b3cff957bfcf628dc150d217150cb76c057ab Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:40:08 -0500 Subject: [PATCH 29/38] prev commit append --- raptorWeb/donations/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index e5811ee8..83f5a8f1 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -154,8 +154,6 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon if not DefaultPages.objects.get_or_create(pk=1)[0].donations: return HttpResponseRedirect('/404') - payment_gateway_choice = request.POST.get('payment_gateway') - try: minecraft_username: str = request.POST.get('minecraft_username') discord_username: str = request.POST.get('discord_username') From 9157c094b692d2ef1a8337a27ec1e54843b7566f Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:41:05 -0500 Subject: [PATCH 30/38] check for gateway settings in payments code --- raptorWeb/donations/payments/paypal.py | 163 +++++++++++++------------ raptorWeb/donations/payments/stripe.py | 99 ++++++++------- raptorWeb/donations/views.py | 51 +++++--- 3 files changed, 168 insertions(+), 145 deletions(-) diff --git a/raptorWeb/donations/payments/paypal.py b/raptorWeb/donations/payments/paypal.py index e018e9fe..c2f6426d 100644 --- a/raptorWeb/donations/payments/paypal.py +++ b/raptorWeb/donations/payments/paypal.py @@ -24,100 +24,107 @@ def get_paypal_checkout_button(request: HttpRequest, bought_package: DonationPac site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] invoice_id = f"raptor-{uuid1()}" - try: - CompletedDonation.objects.get( + if site_info.paypal_enabled: + + try: + CompletedDonation.objects.get( + minecraft_username=minecraft_username, + bought_package=bought_package, + completed=False + ).delete() + + except CompletedDonation.DoesNotExist: + pass + + new_donation = CompletedDonation.objects.create( minecraft_username=minecraft_username, bought_package=bought_package, + spent=bought_package.price, + session_id=request.session.session_key, + paypal_invoice=invoice_id, completed=False - ).delete() - - except CompletedDonation.DoesNotExist: - pass - - new_donation = CompletedDonation.objects.create( - minecraft_username=minecraft_username, - bought_package=bought_package, - spent=bought_package.price, - session_id=request.session.session_key, - paypal_invoice=invoice_id, - completed=False - ) - - if discord_username != '': - new_donation.discord_username = discord_username - - if request.user.is_authenticated: - new_donation.donating_user = request.user + ) - new_donation.save() - - form_data = { - "business": PAYPAL_RECEIVER_EMAIL, - "amount": str(bought_package.price), - 'currency_code': site_info.donation_currency.upper(), - "item_name": f"{bought_package.name} for {minecraft_username}", - "invoice": invoice_id, - "return": f'{WEB_PROTO}://{DOMAIN_NAME}/donations/success', - "cancel_return": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/cancel', - } - - if DEBUG: - form_data.update({ - "notify_url": f'https://{PAYPAL_DEV_WEBHOOK_DOMAIN}/api/donations/payment/paypal_webhook', - }) + if discord_username != '': + new_donation.discord_username = discord_username - else: - form_data.update({ - "notify_url": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/paypal_webhook', - }) - - return PayPalPaymentsForm(initial=form_data) + if request.user.is_authenticated: + new_donation.donating_user = request.user + + new_donation.save() + + form_data = { + "business": PAYPAL_RECEIVER_EMAIL, + "amount": str(bought_package.price), + 'currency_code': site_info.donation_currency.upper(), + "item_name": f"{bought_package.name} for {minecraft_username}", + "invoice": invoice_id, + "return": f'{WEB_PROTO}://{DOMAIN_NAME}/donations/success', + "cancel_return": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/cancel', + } + + if DEBUG: + form_data.update({ + "notify_url": f'https://{PAYPAL_DEV_WEBHOOK_DOMAIN}/api/donations/payment/paypal_webhook', + }) + + else: + form_data.update({ + "notify_url": f'{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/paypal_webhook', + }) + + return PayPalPaymentsForm(initial=form_data) def receive_paypal_ipn(sender, **kwargs): site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] - ipn_obj = sender - if ipn_obj.payment_status == ST_PP_COMPLETED: - - if ipn_obj.receiver_email != PAYPAL_RECEIVER_EMAIL: - return - - try: - completed_donation = CompletedDonation.objects.get( - paypal_invoice=ipn_obj.invoice - ) + + if site_info.paypal_enabled: + ipn_obj = sender + if ipn_obj.payment_status == ST_PP_COMPLETED: - if (int(ipn_obj.mc_gross) != int(completed_donation.spent) - and ipn_obj.mc_currency != site_info.donation_currency.upper()): + if ipn_obj.receiver_email != PAYPAL_RECEIVER_EMAIL: return - completed_donation.completed = True - - if completed_donation.bought_package.servers.all().count() > 0: - send_server_commands.apply_async( - args=(completed_donation.pk,), - countdown=10 + try: + completed_donation = CompletedDonation.objects.get( + paypal_invoice=ipn_obj.invoice ) - if completed_donation.bought_package.discord_roles.all().count() > 0: - add_discord_bot_roles.apply_async( - args=(completed_donation.pk,), - countdown=5 - ) + if (int(ipn_obj.mc_gross) != int(completed_donation.spent) + and ipn_obj.mc_currency != site_info.donation_currency.upper()): + return - completed_donation.save() - - if site_info.send_donation_email: - send_donation_email.apply_async( - args=(completed_donation.pk,), - countdown=5 - ) + completed_donation.completed = True - site_info.donation_goal_progress += completed_donation.spent - site_info.save() - - except CompletedDonation.DoesNotExist: - return + if completed_donation.bought_package.servers.all().count() > 0: + send_server_commands.apply_async( + args=(completed_donation.pk,), + countdown=10 + ) + + if completed_donation.bought_package.discord_roles.all().count() > 0: + add_discord_bot_roles.apply_async( + args=(completed_donation.pk,), + countdown=5 + ) + + completed_donation.save() + + if site_info.send_donation_email: + send_donation_email.apply_async( + args=(completed_donation.pk,), + countdown=5 + ) + + site_info.donation_goal_progress += completed_donation.spent + site_info.save() + + except CompletedDonation.DoesNotExist: + return + else: + return + else: return diff --git a/raptorWeb/donations/payments/stripe.py b/raptorWeb/donations/payments/stripe.py index 6510a325..f403a490 100644 --- a/raptorWeb/donations/payments/stripe.py +++ b/raptorWeb/donations/payments/stripe.py @@ -23,22 +23,24 @@ def create_checkout_session(package: DonationPackage, minecraft_username: str): """ site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] - return stripe.checkout.Session.create( - line_items = [ - { - 'price_data': { - 'currency': str(site_info.donation_currency), - 'product_data': { - 'name': f'{package.name} for {minecraft_username}', + if site_info.stripe_enabled: + + return stripe.checkout.Session.create( + line_items = [ + { + 'price_data': { + 'currency': str(site_info.donation_currency), + 'product_data': { + 'name': f'{package.name} for {minecraft_username}', + }, + 'unit_amount': package.price * 100, }, - 'unit_amount': package.price * 100, - }, - 'quantity': 1, - }], - mode="payment", - success_url=f"{WEB_PROTO}://{DOMAIN_NAME}/donations/success", - cancel_url=f"{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/cancel", - ) + 'quantity': 1, + }], + mode="payment", + success_url=f"{WEB_PROTO}://{DOMAIN_NAME}/donations/success", + cancel_url=f"{WEB_PROTO}://{DOMAIN_NAME}/api/donations/payment/cancel", + ) def retrieve_checkout_session(checkout_id: str): """ @@ -53,39 +55,42 @@ def get_checkout_url(request: HttpRequest, bought_package: DonationPackage, mine Return a checkout URL for the given request """ checkout_url: str = '' - - try: - incomplete_donation = CompletedDonation.objects.get( - minecraft_username=minecraft_username, - bought_package=bought_package, - completed=False - ) - - checkout_url = retrieve_checkout_session(incomplete_donation.checkout_id).url - - except CompletedDonation.DoesNotExist: - checkout_session = create_checkout_session( - bought_package, - minecraft_username - ) - - checkout_url = checkout_session.url + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] - new_donation = CompletedDonation.objects.create( - minecraft_username=minecraft_username, - bought_package=bought_package, - spent=bought_package.price, - session_id=request.session.session_key, - checkout_id=checkout_session.id, - completed=False - ) + if site_info.stripe_enabled: - if discord_username != '': - new_donation.discord_username = discord_username - - if request.user.is_authenticated: - new_donation.donating_user = request.user + try: + incomplete_donation = CompletedDonation.objects.get( + minecraft_username=minecraft_username, + bought_package=bought_package, + completed=False + ) + + checkout_url = retrieve_checkout_session(incomplete_donation.checkout_id).url - new_donation.save() + except CompletedDonation.DoesNotExist: + checkout_session = create_checkout_session( + bought_package, + minecraft_username + ) + + checkout_url = checkout_session.url - return checkout_url \ No newline at end of file + new_donation = CompletedDonation.objects.create( + minecraft_username=minecraft_username, + bought_package=bought_package, + spent=bought_package.price, + session_id=request.session.session_key, + checkout_id=checkout_session.id, + completed=False + ) + + if discord_username != '': + new_donation.discord_username = discord_username + + if request.user.is_authenticated: + new_donation.donating_user = request.user + + new_donation.save() + + return checkout_url \ No newline at end of file diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index 83f5a8f1..a88e6404 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -211,31 +211,38 @@ def post(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpRespon return HttpResponseRedirect('/donations/previousdonation') if payment_gateway_choice == 'stripe': - return redirect( - get_checkout_url( + if site_info.stripe_enabled: + return redirect( + get_checkout_url( + request, + bought_package, + minecraft_username, + discord_username + ) + ) + + else: + return HttpResponseRedirect('/donations/failure') + + if payment_gateway_choice == 'paypal': + if site_info.paypal_enabled: + paypal_button = get_paypal_checkout_button( request, bought_package, minecraft_username, discord_username ) - ) - - if payment_gateway_choice == 'paypal': - paypal_button = get_paypal_checkout_button( - request, - bought_package, - minecraft_username, - discord_username - ) - - return render( - request, - join(DONATIONS_TEMPLATE_DIR, 'paypal_checkout_redirect.html'), - context={'form': paypal_button} - ) + + return render( + request, + join(DONATIONS_TEMPLATE_DIR, 'paypal_checkout_redirect.html'), + context={'form': paypal_button} + ) + + else: + return HttpResponseRedirect('/donations/failure') - else: - return HttpResponseRedirect('/donations/failure') + return HttpResponseRedirect('/donations/failure') class DonationCancel(View): @@ -333,6 +340,11 @@ def donation_payment_webhook(request: HttpRequest): if STRIPE_WEBHOOK_SECRET == '': return HttpResponseRedirect('/404') + site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] + + if site_info.stripe_enabled == False: + return HttpResponseRedirect('/404') + if not DefaultPages.objects.get_or_create(pk=1)[0].donations: return HttpResponseRedirect('/404') @@ -359,7 +371,6 @@ def donation_payment_webhook(request: HttpRequest): # Passed signature verification if event['type'] == 'checkout.session.completed': - site_info: SiteInformation = SiteInformation.objects.get_or_create(pk=1)[0] completed_donation = CompletedDonation.objects.get( checkout_id=event['data']['object']['id'], From 474e1a86d3b1d21e2f39e34476c02876b7b3d9e4 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:41:19 -0500 Subject: [PATCH 31/38] Do not try to show gateway form if it won't exist --- raptorWeb/templates/donations/checkout.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raptorWeb/templates/donations/checkout.html b/raptorWeb/templates/donations/checkout.html index 0ccb1012..1884027e 100644 --- a/raptorWeb/templates/donations/checkout.html +++ b/raptorWeb/templates/donations/checkout.html @@ -61,9 +61,11 @@ {{buying_package.package_description|safe}}

+ {% if not single_gateway %}
{% bootstrap_field donation_gateway_form.payment_gateway %}
+ {% endif %}
{% if buying_package.variable_price %} From 16c8202f56ed991d9cb2e44d55071bf4419bc5dc Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 25 Dec 2023 18:45:44 -0500 Subject: [PATCH 32/38] check for single gateway in un-authenticated checkout page --- raptorWeb/templates/donations/checkout.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raptorWeb/templates/donations/checkout.html b/raptorWeb/templates/donations/checkout.html index 1884027e..b2daa720 100644 --- a/raptorWeb/templates/donations/checkout.html +++ b/raptorWeb/templates/donations/checkout.html @@ -122,9 +122,11 @@ {{buying_package.package_description|safe}}

+ {% if not single_gateway %}
{% bootstrap_field donation_gateway_form.payment_gateway %}
+ {% endif %}
{% if buying_package.variable_price %} From 6bd8112c473ffe45fd52912319c37e386c256e18 Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 17:58:29 -0500 Subject: [PATCH 33/38] Remove debug logging call --- raptorWeb/raptorbot/discordbot/util/task_check.py | 1 - 1 file changed, 1 deletion(-) diff --git a/raptorWeb/raptorbot/discordbot/util/task_check.py b/raptorWeb/raptorbot/discordbot/util/task_check.py index 6d493418..3f10f2a3 100644 --- a/raptorWeb/raptorbot/discordbot/util/task_check.py +++ b/raptorWeb/raptorbot/discordbot/util/task_check.py @@ -33,7 +33,6 @@ async def check_tasks(bot_instance): await messages.delete_messages(bot_instance, str(tasks[0].messages_to_delete)) if str(tasks[0].users_and_roles_to_give) != "": - LOGGER.debug('about to run give_role') await presence.give_role(bot_instance, str(tasks[0].users_and_roles_to_give)) await DiscordBotTasks.objects.aupdate( From b4813e2197cf7d4c697100158344655ddb93c5c8 Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 18:08:13 -0500 Subject: [PATCH 34/38] Do not delay loading server buttons view This was causing the server button spinners to not appear for a time during page load. --- raptorWeb/gameservers/models.py | 7 ++++--- raptorWeb/gameservers/views.py | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index e2f95d1e..b3d53172 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -166,13 +166,14 @@ def update_servers(self): ServerStatistic.objects.get_or_create(name="gameservers-stat")[0] ) - def get_servers(self): + def get_servers(self, wait=True): """ Return a list of servers that are not archived. Will check if a query is running, and wait to return servers until the query is finished. """ - while self._player_poller._is_running == True: - sleep(0.1) + if wait: + while self._player_poller._is_running == True: + sleep(0.1) return self.filter(archived=False).order_by('-pk') diff --git a/raptorWeb/gameservers/views.py b/raptorWeb/gameservers/views.py index 9d798456..0dea0683 100644 --- a/raptorWeb/gameservers/views.py +++ b/raptorWeb/gameservers/views.py @@ -26,7 +26,10 @@ class Server_List_Base(ListView): model: Server = Server def get_queryset(self) -> ServerManager: - return Server.objects.get_servers() + if 'loading' not in self.template_name: + return Server.objects.get_servers() + + return Server.objects.get_servers(wait=False) def get(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpResponse: if request.headers.get('HX-Request') == "true": From 8b4d7ebfe8064218a73bcc9075ce38c629844c5b Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 18:17:10 -0500 Subject: [PATCH 35/38] Use color picker fields on color settings These were reverted after the changes to the way the form fields were pulled. --- raptorWeb/panel/forms.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raptorWeb/panel/forms.py b/raptorWeb/panel/forms.py index a2e2f2b9..3113ed06 100644 --- a/raptorWeb/panel/forms.py +++ b/raptorWeb/panel/forms.py @@ -10,6 +10,10 @@ class PanelSettingsInformation(forms.ModelForm): class Meta(): model: SiteInformation = SiteInformation fields: str = "__all__" + widgets = { + 'main_color': forms.TextInput(attrs={'type': 'color'}), + 'secondary_color': forms.TextInput(attrs={'type': 'color'}), + } exclude: tuple[str] = ( 'branding_image', 'background_image', From b73c24774772e1778d30fefa6e3408078de90f50 Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 18:22:28 -0500 Subject: [PATCH 36/38] Catch LifetimeTimeout DNS exception in PlayerPoller --- raptorWeb/gameservers/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index b3d53172..a8401095 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -3,6 +3,7 @@ from time import sleep from logging import Logger, getLogger from typing import Optional +from dns.resolver import LifetimeTimeout from django.db import models from django.utils.timezone import localtime, now @@ -92,7 +93,7 @@ def _update_announcement_count(server: Server) -> None: ) new_player.save() - except TimeoutError: + except TimeoutError or LifetimeTimeout: _set_offline_server(server) else: From 624c4b83b15ba3ec6bffaf90e0e84b0e3cbf0dfd Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 18:39:36 -0500 Subject: [PATCH 37/38] Improve paypal checkout redirect Made the page a full HTML document, and improved the looks quite a bit. Added a loading spinner. --- .../donations/paypal_checkout_redirect.html | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/raptorWeb/templates/donations/paypal_checkout_redirect.html b/raptorWeb/templates/donations/paypal_checkout_redirect.html index e18494dc..a394a560 100644 --- a/raptorWeb/templates/donations/paypal_checkout_redirect.html +++ b/raptorWeb/templates/donations/paypal_checkout_redirect.html @@ -1,19 +1,54 @@ -
- {{ form.render }} -
+ -
- You are being redirected, please wait. -
+{% load static %} - +
+ +
+
+ Loading... +
+ +
+ You are being redirected, please wait. +
+
+ +
+ +
+ {{ form.render }} +
+ + + + + + + From e408626876174ab27f646f0ff11000e0a8e6119f Mon Sep 17 00:00:00 2001 From: zediious Date: Sat, 30 Dec 2023 20:05:30 -0500 Subject: [PATCH 38/38] Add priority field to donation packages, sort in listview by priority --- raptorWeb/donations/admin.py | 3 ++- .../0021_donationpackage_priority.py | 18 ++++++++++++++++++ raptorWeb/donations/models.py | 6 ++++++ raptorWeb/donations/views.py | 3 +++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 raptorWeb/donations/migrations/0021_donationpackage_priority.py diff --git a/raptorWeb/donations/admin.py b/raptorWeb/donations/admin.py index f97fb8cf..351c28bb 100644 --- a/raptorWeb/donations/admin.py +++ b/raptorWeb/donations/admin.py @@ -28,6 +28,7 @@ class DonationPackageAdmin(admin.ModelAdmin): 'price', 'variable_price', 'allow_repeat', + 'priority', 'package_picture', 'package_description') }), @@ -43,7 +44,7 @@ class DonationPackageAdmin(admin.ModelAdmin): 'name', ] - list_display: list[str] = ['name', 'allow_repeat', 'variable_price', 'price'] + list_display: list[str] = ['name', 'allow_repeat', 'variable_price', 'price', 'priority'] class DonationServerCommandAdmin(admin.ModelAdmin): diff --git a/raptorWeb/donations/migrations/0021_donationpackage_priority.py b/raptorWeb/donations/migrations/0021_donationpackage_priority.py new file mode 100644 index 00000000..07bdf669 --- /dev/null +++ b/raptorWeb/donations/migrations/0021_donationpackage_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-12-31 01:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('donations', '0020_completeddonation_paypal_invoice_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='donationpackage', + name='priority', + field=models.IntegerField(default=0, help_text='The order that this package will appear in the package list..', verbose_name='Priority'), + ), + ] diff --git a/raptorWeb/donations/models.py b/raptorWeb/donations/models.py index 6b58f3a3..913dfb3a 100644 --- a/raptorWeb/donations/models.py +++ b/raptorWeb/donations/models.py @@ -100,6 +100,12 @@ class DonationPackage(models.Model): help_text='If this is enabled, this package can be bought multiple times by the same Minecraft username' ) + priority = models.IntegerField( + default=0, + verbose_name='Priority', + help_text='The order that this package will appear in the package list..' + ) + servers = models.ManyToManyField( to=Server, blank=True, diff --git a/raptorWeb/donations/views.py b/raptorWeb/donations/views.py index a88e6404..9a21c2ed 100644 --- a/raptorWeb/donations/views.py +++ b/raptorWeb/donations/views.py @@ -79,6 +79,9 @@ class DonationPackages(ListView): """ paginate_by: int = 9 model: DonationPackage = DonationPackage + + def get_queryset(self) -> QuerySet[Any]: + return super().get_queryset().order_by('priority') def get(self, request: HttpRequest, *args: tuple, **kwargs: dict) -> HttpResponse: if not DefaultPages.objects.get_or_create(pk=1)[0].donations: