New payment retry policy.
authorRadek Czajka <rczajka@rczajka.pl>
Tue, 19 Apr 2022 23:24:31 +0000 (01:24 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Tue, 19 Apr 2022 23:24:31 +0000 (01:24 +0200)
src/club/migrations/0038_payuorder_created_at.py [new file with mode: 0644]
src/club/models.py
src/club/payment_methods.py
src/club/payu/models.py
src/club/templates/club/email/failed_recurring.txt [new file with mode: 0644]
src/wolnelektury/settings/custom.py

diff --git a/src/club/migrations/0038_payuorder_created_at.py b/src/club/migrations/0038_payuorder_created_at.py
new file mode 100644 (file)
index 0000000..9fd692e
--- /dev/null
@@ -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),
+        ),
+    ]
index f09b68e..0d75ec4 100644 (file)
@@ -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
index 0c187a8..e74ae83 100644 (file)
@@ -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'
index 6ed329d..6305e2e 100644 (file)
@@ -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 (file)
index 0000000..72ddf35
--- /dev/null
@@ -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 %}
index 814289b..e9449cf 100644 (file)
@@ -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