From 0251e674e16859dd23bb1497987c9f0cde1db208 Mon Sep 17 00:00:00 2001 From: falkecarlsen <11318702+falkecarlsen@users.noreply.github.com> Date: Tue, 17 May 2022 18:26:47 +0200 Subject: [PATCH 1/3] Refactor payment mails --- .../management/commands/autopayment.py | 2 +- stregsystem/models.py | 14 +++++- stregsystem/tests.py | 26 +++++------ stregsystem/utils.py | 43 +++++++++++++------ stregsystem/views.py | 4 +- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/stregsystem/management/commands/autopayment.py b/stregsystem/management/commands/autopayment.py index 4e691711..0b665723 100644 --- a/stregsystem/management/commands/autopayment.py +++ b/stregsystem/management/commands/autopayment.py @@ -24,7 +24,7 @@ def handle(self, *args, **options): # count, approve, and submit mobilepayments before_count = MobilePayment.objects.filter(status=MobilePayment.APPROVED).count() MobilePayment.approve_member_filled_mobile_payments() - MobilePayment.submit_processed_mobile_payments(auto_user) + MobilePayment.submit_correct_mobile_payments(auto_user) count = MobilePayment.objects.filter(status=MobilePayment.APPROVED).count() - before_count self.stdout.write( diff --git a/stregsystem/models.py b/stregsystem/models.py index 13bddf14..cd0e4f24 100644 --- a/stregsystem/models.py +++ b/stregsystem/models.py @@ -19,6 +19,7 @@ make_processed_mobilepayment_query, make_unprocessed_member_filled_mobilepayment_query, MobilePaytoolException, + EmailType, ) from stregsystem.utils import send_payment_mail @@ -353,9 +354,11 @@ def save(self, mbpayment=None, *args, **kwargs): self.member.make_payment(self.amount) super(Payment, self).save(*args, **kwargs) self.member.save() + """ if self.member.email != "" and self.amount != 0: if '@' in parseaddr(self.member.email)[1] and self.member.want_spam: send_payment_mail(self.member, self.amount, mbpayment.comment if mbpayment else None) + """ def log_from_mobile_payment(self, processed_mobile_payment, admin_user: User): LogEntry.objects.log_action( @@ -422,7 +425,7 @@ def delete(self, *args, **kwargs): @staticmethod @transaction.atomic - def submit_processed_mobile_payments(admin_user: User): + def submit_correct_mobile_payments(admin_user: User): processed_mobile_payment: MobilePayment # annotate iterated variable (PEP 526) for processed_mobile_payment in make_processed_mobilepayment_query(): @@ -435,12 +438,15 @@ def submit_processed_mobile_payments(admin_user: User): processed_mobile_payment.payment = payment processed_mobile_payment.save() + # send payment mail + send_payment_mail(processed_mobile_payment.member, processed_mobile_payment.amount, EmailType.STANDARD) + elif processed_mobile_payment.status == MobilePayment.IGNORED: processed_mobile_payment.log_mobile_payment(admin_user, "Ignored") @staticmethod @transaction.atomic - def process_submitted_mobile_payments(submitted_data, admin_user: User): + def submit_user_approved_mobile_payments(submitted_data, admin_user: User): """ Takes a cleaned_form from a MobilePayToolFormSet and processes them. The return value is the number of rows procesed. @@ -486,6 +492,10 @@ def process_submitted_mobile_payments(submitted_data, admin_user: User): processed_mobile_payment.payment = payment processed_mobile_payment.member = member processed_mobile_payment.log_mobile_payment(admin_user, "Approved") + + # send shame mail + send_payment_mail(member, payment_amount, EmailType.SHAME, processed_mobile_payment.comment) + # If ignored, we need to log who did it. elif row['status'] == MobilePayment.IGNORED: processed_mobile_payment.log_mobile_payment(admin_user, "Ignored") diff --git a/stregsystem/tests.py b/stregsystem/tests.py index 715baa59..bee0cc02 100644 --- a/stregsystem/tests.py +++ b/stregsystem/tests.py @@ -1382,7 +1382,7 @@ def test_approved_payment_balance(self): mobile_payment.status = MobilePayment.APPROVED mobile_payment.save() - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) self.assertEqual( Member.objects.get(username__exact="jdoe").balance, self.members["jdoe"]['balance'] + mobile_payment.amount @@ -1399,7 +1399,7 @@ def test_ignored_payment_balance(self): mobile_payment.status = MobilePayment.IGNORED mobile_payment.save() - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) self.assertEqual(Member.objects.get(username__exact="tester").balance, self.members["tester"]['balance']) @@ -1425,7 +1425,7 @@ def test_batch_submission_balance(self): bobby_tables_mobile_payment1.status = MobilePayment.APPROVED bobby_tables_mobile_payment1.save() - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) # assert that each member who has an approved mobile payment has their balance updated by the amount given for approved_mobile_payment in MobilePayment.objects.filter(status__exact=MobilePayment.APPROVED): @@ -1443,7 +1443,7 @@ def test_member_balance_on_delete_approved_mobilepayment(self): mobile_payment.status = MobilePayment.APPROVED mobile_payment.save() - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) # ensure new balance is mobilepayment amount self.assertEqual( @@ -1467,7 +1467,7 @@ def test_member_balance_on_delete_ignored_mobilepayment(self): mobile_payment.status = MobilePayment.IGNORED mobile_payment.save() - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) # ensure balance is still initial amount self.assertEqual(Member.objects.get(username__exact="jdoe").balance, self.members["jdoe"]['balance']) @@ -1484,7 +1484,7 @@ def test_member_balance_on_delete_unset_mobilepayment(self): # submit mobile payments self.client.login(username="superuser", password="hunter2") - MobilePayment.submit_processed_mobile_payments(self.super_user) + MobilePayment.submit_correct_mobile_payments(self.super_user) # ensure balance is still initial amount self.assertEqual(Member.objects.get(username__exact="jdoe").balance, self.members["jdoe"]['balance']) @@ -1524,21 +1524,23 @@ def test_esoteric_chars(self): def test_mobilepaytool_race_no_error(self): # do autopayment MobilePayment.approve_member_filled_mobile_payments() - MobilePayment.submit_processed_mobile_payments(self.autopayment_user) + MobilePayment.submit_correct_mobile_payments(self.autopayment_user) # assert that no changes have been made, also that MobilePaytoolException is not thrown self.assertEqual( - MobilePayment.process_submitted_mobile_payments(self.fixture_form_data_no_change, self.super_user), 0 + MobilePayment.submit_user_approved_mobile_payments(self.fixture_form_data_no_change, self.super_user), 0 ) def test_mobilepaytool_race_error_marx(self): # do autopayment MobilePayment.approve_member_filled_mobile_payments() - MobilePayment.submit_processed_mobile_payments(self.autopayment_user) + MobilePayment.submit_correct_mobile_payments(self.autopayment_user) # assert exception is thrown and values in exception are as expected with self.assertRaises(MobilePaytoolException): try: - MobilePayment.process_submitted_mobile_payments(self.fixture_form_data_marx_approved, self.super_user) + MobilePayment.submit_user_approved_mobile_payments( + self.fixture_form_data_marx_approved, self.super_user + ) except MobilePaytoolException as e: self.assertEqual(e.inconsistent_mbpayments_count, 1) self.assertEqual(e.inconsistent_transaction_ids, ["241E027449465355"]) @@ -1547,12 +1549,12 @@ def test_mobilepaytool_race_error_marx(self): def test_mobilepaytool_race_error_marx_jdoe(self): # do autopayment MobilePayment.approve_member_filled_mobile_payments() - MobilePayment.submit_processed_mobile_payments(self.autopayment_user) + MobilePayment.submit_correct_mobile_payments(self.autopayment_user) # assert exception is thrown and values in exception are as expected with self.assertRaises(MobilePaytoolException): try: - MobilePayment.process_submitted_mobile_payments( + MobilePayment.submit_user_approved_mobile_payments( self.fixture_form_data_marx_jdoe_approved, self.super_user ) except MobilePaytoolException as e: diff --git a/stregsystem/utils.py b/stregsystem/utils.py index 37e99bf5..5f89b59b 100644 --- a/stregsystem/utils.py +++ b/stregsystem/utils.py @@ -1,6 +1,8 @@ +import enum import logging import re import smtplib +from enum import Enum from django.utils.dateparse import parse_datetime from email.mime.multipart import MIMEMultipart @@ -93,7 +95,13 @@ def date_to_midnight(date): return timezone.make_aware(timezone.datetime(date.year, date.month, date.day, 0, 0)) -def send_payment_mail(member, amount, mobilepay_comment): +class EmailType(enum): + STANDARD = 0 + SHAME = 1 + LOW_AMOUNT = 2 + + +def send_payment_mail(member, amount, email_type: EmailType, comment=None): if hasattr(settings, 'TEST_MODE'): return msg = MIMEMultipart() @@ -103,7 +111,12 @@ def send_payment_mail(member, amount, mobilepay_comment): formatted_amount = money(amount) - normal_html = f""" + from django.utils.html import escape + + if email_type is EmailType.STANDARD: + msg.attach( + MIMEText( + f""" @@ -122,18 +135,21 @@ def send_payment_mail(member, amount, mobilepay_comment):

- """ - - from django.utils.html import escape - - shame_html = f""" + """, + 'html', + ) + ) + elif email_type is EmailType.SHAME: + msg.attach( + MIMEText( + f"""

Hej {member.firstname}!

Vi har med stort besvær indsat pokkers {formatted_amount} stregdollars på din konto: "{member.username}".

- Da du ikke skrev dit brugernavn korrekt, men i stedet skrev '{escape(mobilepay_comment)}' var de stakkels TREOer desværre nødt til at tage flere minutter ud af deres dag for at indsætte dine penge manuelt. + Da du ikke skrev dit brugernavn korrekt, men i stedet skrev '{escape(comment)}' var de stakkels TREOer desværre nødt til at tage flere minutter ud af deres dag for at indsætte dine penge manuelt. Vil du nyde godt af vores automatiske indbetaling kan du i fremtiden med fordel skrive dit brugernavn korrekt i MobilePay kommentaren: '{member.username}'. Udnytter du vores QR-kode generator klarer den også denne komplicerede process for dig. @@ -143,7 +159,7 @@ def send_payment_mail(member, amount, mobilepay_comment): ====================================================================
Hello {member.firstname}!

We've had great trouble inserting {formatted_amount} stregdollars on your account: "{member.username}".

- This is due to you not you not writing your username correctly, and instead writing '{escape(mobilepay_comment)}'. The poor TREOs had to take multiple minutes out of their day to insert your money manually. + This is due to you not you not writing your username correctly, and instead writing '{escape(comment)}'. The poor TREOs had to take multiple minutes out of their day to insert your money manually. If you want to utilise our automatic fill-up in future, you can write your username correctly in the MobilePay comment: '{member.username}' Our QR-code generator also handles this very complicated process for you. @@ -153,9 +169,12 @@ def send_payment_mail(member, amount, mobilepay_comment):

- """ - - msg.attach(MIMEText(shame_html if mobilepay_comment else normal_html, 'html')) + """, + 'html', + ) + ) + elif email_type is EmailType.LOW_AMOUNT: + msg.attach(MIMEText("TODO", 'html')) try: smtpObj = smtplib.SMTP('localhost', 25) diff --git a/stregsystem/views.py b/stregsystem/views.py index 216890b8..1e1f1ced 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -445,7 +445,7 @@ def mobilepaytool(request): elif request.method == "POST" and request.POST['action'] == "Submit matched payments": before_count = MobilePayment.objects.filter(status=MobilePayment.APPROVED).count() MobilePayment.approve_member_filled_mobile_payments() - MobilePayment.submit_processed_mobile_payments(request.user) + MobilePayment.submit_correct_mobile_payments(request.user) count = MobilePayment.objects.filter(status=MobilePayment.APPROVED).count() - before_count data['submitted_count'] = count @@ -457,7 +457,7 @@ def mobilepaytool(request): if form.is_valid(): try: # Do custom validation on form to avoid race conditions with autopayment - count = MobilePayment.process_submitted_mobile_payments(form.cleaned_data, request.user) + count = MobilePayment.submit_user_approved_mobile_payments(form.cleaned_data, request.user) data['submitted_count'] = count except MobilePaytoolException as e: data['error_count'] = e.inconsistent_mbpayments_count From 0eb8dd84ae466633413f768fd9388fbaafd9b200 Mon Sep 17 00:00:00 2001 From: falkecarlsen <11318702+falkecarlsen@users.noreply.github.com> Date: Wed, 18 May 2022 12:55:11 +0200 Subject: [PATCH 2/3] Enum --- stregsystem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stregsystem/utils.py b/stregsystem/utils.py index 5f89b59b..05f0a670 100644 --- a/stregsystem/utils.py +++ b/stregsystem/utils.py @@ -95,7 +95,7 @@ def date_to_midnight(date): return timezone.make_aware(timezone.datetime(date.year, date.month, date.day, 0, 0)) -class EmailType(enum): +class EmailType(Enum): STANDARD = 0 SHAME = 1 LOW_AMOUNT = 2 From 5a6b5733a28485e3fecdcf5c9c632553e3de9b81 Mon Sep 17 00:00:00 2001 From: falkecarlsen <11318702+falkecarlsen@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:41:02 +0200 Subject: [PATCH 3/3] Handle autopayment, mails, and mobilepaytool form for low amount --- .../commands/importmobilepaypayments.py | 7 ++-- stregsystem/models.py | 13 +++++-- stregsystem/utils.py | 35 +++++++++++++++++-- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/stregsystem/management/commands/importmobilepaypayments.py b/stregsystem/management/commands/importmobilepaypayments.py index 0e4f04d2..bd1df5ed 100644 --- a/stregsystem/management/commands/importmobilepaypayments.py +++ b/stregsystem/management/commands/importmobilepaypayments.py @@ -147,19 +147,20 @@ def import_mobilepay_payment(self, transaction): self.write_warning(f'Does ONLY support DKK (Transaction ID: {trans_id}), was {currency_code}') return - amount = transaction['amount'] + # convert to streg-ører + amount = transaction['amount'] * 100 comment = strip_emoji(transaction['senderComment']) payment_datetime = parse_datetime(transaction['timestamp']) MobilePayment.objects.create( - amount=amount * 100, # convert to streg-ører + amount=amount, member=mobile_payment_exact_match_member(comment), comment=comment, timestamp=payment_datetime, transaction_id=trans_id, - status=MobilePayment.UNSET, + status=MobilePayment.UNSET if amount >= 5000 else MobilePayment.LOW_AMOUNT, ) self.write_info(f'Imported transaction id: {trans_id} for amount: {amount}') diff --git a/stregsystem/models.py b/stregsystem/models.py index cd0e4f24..389483e5 100644 --- a/stregsystem/models.py +++ b/stregsystem/models.py @@ -386,11 +386,13 @@ class Meta: UNSET = 'U' APPROVED = 'A' + LOW_AMOUNT = 'L' IGNORED = 'I' STATUS_CHOICES = ( (UNSET, 'Unset'), (APPROVED, 'Approved'), + (LOW_AMOUNT, 'Low amount'), (IGNORED, 'Ignored'), ) member = models.ForeignKey( @@ -480,8 +482,9 @@ def submit_user_approved_mobile_payments(submitted_data, admin_user: User): for row in cleaned_data: processed_mobile_payment = MobilePayment.objects.get(id=row['id'].id) + status = row['status'] # If approved, we need to create a payment and relate said payment to the mobilepayment. - if row['status'] == MobilePayment.APPROVED: + if status == MobilePayment.APPROVED or status == MobilePayment.LOW_AMOUNT: payment_amount = processed_mobile_payment.amount member = Member.objects.get(id=row['member'].id) @@ -493,8 +496,12 @@ def submit_user_approved_mobile_payments(submitted_data, admin_user: User): processed_mobile_payment.member = member processed_mobile_payment.log_mobile_payment(admin_user, "Approved") - # send shame mail - send_payment_mail(member, payment_amount, EmailType.SHAME, processed_mobile_payment.comment) + if status is MobilePayment.APPROVED: + # send shame mail + send_payment_mail(member, payment_amount, EmailType.SHAME, processed_mobile_payment.comment) + elif status is MobilePayment.LOW_AMOUNT: + # send low amount mail + send_payment_mail(member, payment_amount, EmailType.LOW_AMOUNT, processed_mobile_payment.comment) # If ignored, we need to log who did it. elif row['status'] == MobilePayment.IGNORED: diff --git a/stregsystem/utils.py b/stregsystem/utils.py index 05f0a670..7d585d54 100644 --- a/stregsystem/utils.py +++ b/stregsystem/utils.py @@ -62,7 +62,7 @@ def make_room_specific_query(room) -> QuerySet: def make_unprocessed_mobilepayment_query() -> QuerySet: from stregsystem.models import MobilePayment # import locally to avoid circular import - return MobilePayment.objects.filter(Q(payment__isnull=True) & Q(status__exact=MobilePayment.UNSET)).order_by( + return MobilePayment.objects.filter(Q(payment__isnull=True) & Q(status__in=[MobilePayment.UNSET, MobilePayment.LOW_AMOUNT])).order_by( '-timestamp' ) @@ -174,7 +174,38 @@ def send_payment_mail(member, amount, email_type: EmailType, comment=None): ) ) elif email_type is EmailType.LOW_AMOUNT: - msg.attach(MIMEText("TODO", 'html')) + msg.attach( + MIMEText( + f""" + + + +

+ Hej {member.firstname}!

+ Vi har indsat {formatted_amount} stregdollars på din konto: "{member.username}".

+ Bemærk at du har sendt mindre end minimumsgrænsen (50 DKK) i din indbetaling , hvilket vi ikke tillader på grund af høje gebyrer. + Din indbetaling er ekstraordinært blevet manuelt accepteret, men ved gentagne overførsler under minimumsgrænsen vil vi afvise din indbetaling. + Hvis du ikke ønsker at modtage flere mails som denne kan du skrive en mail til: treo@fklub.dk

+ Mvh,
+ TREOen
+ ====================================================================
+ Hello {member.firstname}!

+ We've inserted {formatted_amount} stregdollars on your account: "{member.username}".

+ Notice that you have sent less than the lower bound for top-ups (50 DKK) in you payment, which is against our rules due to high fees. + Your payment has been extraordinarily manually accepted, but with repeat transactions under the lower bound we may decline your payment. + If you do not desire to receive any more mails of this sort, please file a complaint to: treo@fklub.dk

+ Kind regards,
+ TREOen +

+ + + """, + 'html', + ) + ) + else: + logger.error("Could not match type of payment mail to send user. Skipping") + return try: smtpObj = smtplib.SMTP('localhost', 25)