Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor payment mails #309

Draft
wants to merge 3 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stregsystem/management/commands/autopayment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 4 additions & 3 deletions stregsystem/management/commands/importmobilepaypayments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}')
23 changes: 20 additions & 3 deletions stregsystem/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
make_processed_mobilepayment_query,
make_unprocessed_member_filled_mobilepayment_query,
MobilePaytoolException,
EmailType,
)
from stregsystem.utils import send_payment_mail

Expand Down Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the code instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, mistakenly didn't mark this PR draft

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(
Expand Down Expand Up @@ -383,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(
Expand Down Expand Up @@ -422,7 +427,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():

Expand All @@ -435,12 +440,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.
Expand Down Expand Up @@ -474,8 +482,9 @@ def process_submitted_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)

Expand All @@ -486,6 +495,14 @@ 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")

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:
processed_mobile_payment.log_mobile_payment(admin_user, "Ignored")
Expand Down
26 changes: 14 additions & 12 deletions stregsystem/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'])

Expand All @@ -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):
Expand All @@ -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(
Expand All @@ -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'])
Expand All @@ -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'])
Expand Down Expand Up @@ -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"])
Expand All @@ -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:
Expand Down
76 changes: 63 additions & 13 deletions stregsystem/utils.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,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'
)

Expand Down Expand Up @@ -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()
Expand All @@ -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"""
<html>
<head></head>
<body>
Expand All @@ -122,18 +135,21 @@ def send_payment_mail(member, amount, mobilepay_comment):
</p>
</body>
</html>
"""

from django.utils.html import escape

shame_html = f"""
""",
'html',
)
)
elif email_type is EmailType.SHAME:
msg.attach(
MIMEText(
f"""
<html>
<head></head>
<body>
<p>
Hej {member.firstname}!<br><br>
Vi har med stort besvær indsat pokkers {formatted_amount} stregdollars på din konto: "{member.username}". <br><br>
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.

Expand All @@ -143,7 +159,7 @@ def send_payment_mail(member, amount, mobilepay_comment):
====================================================================<br>
Hello {member.firstname}!<br><br>
We've had great trouble inserting {formatted_amount} stregdollars on your account: "{member.username}". <br><br>
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.

Expand All @@ -153,9 +169,43 @@ def send_payment_mail(member, amount, mobilepay_comment):
</p>
</body>
</html>
"""

msg.attach(MIMEText(shame_html if mobilepay_comment else normal_html, 'html'))
""",
'html',
)
)
elif email_type is EmailType.LOW_AMOUNT:
msg.attach(
MIMEText(
f"""
<html>
<head></head>
<body>
<p>
Hej {member.firstname}!<br><br>
Vi har indsat {formatted_amount} stregdollars på din konto: "{member.username}". <br><br>
<b>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.</b>
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: <a href="mailto:[email protected]?Subject=Klage" target="_top">[email protected]</a><br><br>
Mvh,<br>
TREOen<br>
====================================================================<br>
Hello {member.firstname}!<br><br>
We've inserted {formatted_amount} stregdollars on your account: "{member.username}". <br><br>
<b> 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.</b>
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: <a href="mailto:[email protected]?Subject=Klage" target="_top">[email protected]</a><br><br>
Kind regards,<br>
TREOen
</p>
</body>
</html>
""",
'html',
)
)
else:
logger.error("Could not match type of payment mail to send user. Skipping")
return

try:
smtpObj = smtplib.SMTP('localhost', 25)
Expand Down
4 changes: 2 additions & 2 deletions stregsystem/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down