From 3b0dd45f80df3512dabe75506e635a0f6e3a87e3 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 20 Apr 2022 01:24:31 +0200 Subject: [PATCH] New payment retry policy. --- .../migrations/0038_payuorder_created_at.py | 18 ++++++++++++ src/club/models.py | 26 +++++++++++++++++ src/club/payment_methods.py | 29 +++++++++++++++++++ src/club/payu/models.py | 1 + .../templates/club/email/failed_recurring.txt | 15 ++++++++++ src/wolnelektury/settings/custom.py | 6 ++++ 6 files changed, 95 insertions(+) create mode 100644 src/club/migrations/0038_payuorder_created_at.py create mode 100644 src/club/templates/club/email/failed_recurring.txt diff --git a/src/club/migrations/0038_payuorder_created_at.py b/src/club/migrations/0038_payuorder_created_at.py new file mode 100644 index 000000000..9fd692eb2 --- /dev/null +++ b/src/club/migrations/0038_payuorder_created_at.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.27 on 2022-04-19 23:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0037_delete_directdebit'), + ] + + operations = [ + migrations.AddField( + model_name='payuorder', + name='created_at', + field=models.DateTimeField(auto_now_add=True, null=True), + ), + ] diff --git a/src/club/models.py b/src/club/models.py index f09b68ec7..0d75ec465 100644 --- a/src/club/models.py +++ b/src/club/models.py @@ -150,6 +150,16 @@ class Schedule(models.Model): return utils.add_year(date) return utils.add_month(date) + def get_other_active_recurring(self): + schedules = type(self).objects.exclude( + monthly=False, yearly=False + ).filter(is_cancelled=False, expires_at__gt=now()).exclude(pk=self.pk) + mine_q = models.Q(email=self.email) + if self.membership is not None: + mine_q |= models.Q(membership__user=self.membership.user) + schedules = schedules.filter(mine_q) + return schedules.order_by('-expires_at').first() + def send_email(self): ctx = {'schedule': self} send_mail( @@ -159,6 +169,17 @@ class Schedule(models.Model): self.email_sent = True self.save() + def send_email_failed_recurring(self): + ctx = { + 'schedule': self, + 'other': self.get_other_active_recurring(), + } + send_mail( + 'Darowizna na Wolne Lektury — problem z płatnością', + template.loader.render_to_string('club/email/failed_recurring.txt', ctx), + settings.CONTACT_EMAIL, [self.email], fail_silently=False + ) + def update_contact(self): Contact = apps.get_model('messaging', 'Contact') if not self.payed_at: @@ -294,6 +315,11 @@ class PayUOrder(payu_models.Order): def status_updated(self): if self.status == 'COMPLETED': self.schedule.set_payed() + + elif self.status == 'CANCELED': + if self.is_recurring() and self.schedule.expires_at: + self.schedule.send_email_failed_recurring() + self.report_activity() @property diff --git a/src/club/payment_methods.py b/src/club/payment_methods.py index 0c187a826..e74ae83ca 100644 --- a/src/club/payment_methods.py +++ b/src/club/payment_methods.py @@ -3,6 +3,7 @@ # from django.conf import settings from django.urls import reverse +from django.utils.timezone import now from paypal.rest import agreement_approval_url @@ -60,13 +61,41 @@ class PayURe(PaymentMethod): ip = request.META['REMOTE_ADDR'] else: ip = '127.0.0.1' + + if request is None: + if not self.needs_retry(schedule): + return + order = PayUOrder.objects.create( pos_id=self.pos_id, customer_ip=ip, schedule=schedule, ) return order.put() + + def needs_retry(self, schedule): + retry_last = schedule.payuorder_set.exclude( + created_at=None).order_by('-created_at').first() + if retry_last is None: + return True + + n = now().date() + days_since_last = (n - retry_last.created_at.date()).days + + retry_start = max( + schedule.expires_at.date(), + settings.CLUB_RETRIES_START + ) + retry_days = (n - retry_start).days + if retry_days > settings.CLUB_RETRY_DAYS_MAX: + print('expired') + return False + if retry_days > settings.CLUB_RETRY_DAYS_DAILY: + print('retry less often now') + return days_since_last > settings.CLUB_RETRY_LESS + return days_since_last > 0 + class PayPal(PaymentMethod): slug = 'paypal' diff --git a/src/club/payu/models.py b/src/club/payu/models.py index 6ed329da8..6305e2ed5 100644 --- a/src/club/payu/models.py +++ b/src/club/payu/models.py @@ -37,6 +37,7 @@ class Order(models.Model): ('CANCELED', _('Canceled')), ('REJECTED', _('Rejected')), ]) + created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True) completed_at = models.DateTimeField(null=True, blank=True) class Meta: diff --git a/src/club/templates/club/email/failed_recurring.txt b/src/club/templates/club/email/failed_recurring.txt new file mode 100644 index 000000000..72ddf350b --- /dev/null +++ b/src/club/templates/club/email/failed_recurring.txt @@ -0,0 +1,15 @@ +Darowizna na Wolne Lektury — problem z płatnością. + +Coś poszło nie tak. Prawdopodobnie Twoja karta jest nieaktywna – być może skończył się termin jej ważności. +{% if other %}Dotyczy to płatności ustawionej {{ schedule.payed_at.date }} na {{ schedule.amount }} zł. +{% endif %}Kliknij link i podaj numer nowej karty: +https://wolnelektury.pl{% url 'club_payu_rec_payment' schedule.key %} + +Być może chwilowo zabrakło środków na koncie – nic nie musisz robić, ponowimy próbę płatności. + +Masz pytania dotyczące Twojej płatności? Zajrzyj tu: +https://wolnelektury.pl/info/pytania-platnosci/ + +{% if other %} +PS Masz także inna płatność na {{ other.amount }} zł ustawioną {{ other.payed_at.date }}{% if other.is_active %} - tutaj wszystko działa, dziękujemy{% endif %}! +{% endif %} diff --git a/src/wolnelektury/settings/custom.py b/src/wolnelektury/settings/custom.py index 814289b50..e9449cf77 100644 --- a/src/wolnelektury/settings/custom.py +++ b/src/wolnelektury/settings/custom.py @@ -1,6 +1,7 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from datetime import date import os from .paths import VAR_DIR @@ -39,6 +40,11 @@ CLUB_PAYU_POS = '300746' CLUB_PAYU_RECURRING_POS = '300746' CLUB_APP_HOST = None +CLUB_RETRIES_START = date(2022, 4, 20) +CLUB_RETRY_DAYS_MAX = 90 +CLUB_RETRY_DAYS_DAILY = 3 +CLUB_RETRY_LESS = 7 + MESSAGING_MIN_DAYS = 2 NEWSLETTER_PHPLIST_SUBSCRIBE_URL = None -- 2.20.1