Add missing constraint.
[wolnelektury.git] / src / club / payu / models.py
index 4fe4d94..b025bfc 100644 (file)
@@ -1,38 +1,51 @@
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
 import json
 from urllib.parse import urlencode
 from urllib.request import HTTPError
 from django.contrib.sites.models import Site
 from django.db import models
 from django.urls import reverse
 import json
 from urllib.parse import urlencode
 from urllib.request import HTTPError
 from django.contrib.sites.models import Site
 from django.db import models
 from django.urls import reverse
+from django.utils.timezone import now
+from django.utils.translation import gettext_lazy as _
 from . import POSS
 
 
 class CardToken(models.Model):
     """ This should be attached to a payment schedule. """
 from . import POSS
 
 
 class CardToken(models.Model):
     """ This should be attached to a payment schedule. """
-    pos_id = models.CharField(max_length=255)
-    disposable_token = models.CharField(max_length=255)
-    reusable_token = models.CharField(max_length=255, null=True, blank=True)
-    created_at = models.DateTimeField(auto_now_add=True)
+    pos_id = models.CharField(_('POS id'), max_length=255)
+    disposable_token = models.CharField(_('disposable token'), max_length=255)
+    reusable_token = models.CharField(_('reusable token'), max_length=255, null=True, blank=True)
+    created_at = models.DateTimeField(_('created_at'), auto_now_add=True)
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
+        verbose_name = _('PayU card token')
+        verbose_name_plural = _('PayU card tokens')
 
 
 class Order(models.Model):
 
 
 class Order(models.Model):
-    pos_id = models.CharField(max_length=255)   # TODO: redundant?
-    customer_ip = models.GenericIPAddressField()
-    order_id = models.CharField(max_length=255, blank=True)
+    pos_id = models.CharField(_('POS id'), max_length=255)   # TODO: redundant?
+    customer_ip = models.GenericIPAddressField(_('customer IP'))
+    order_id = models.CharField(_('order ID'), max_length=255, blank=True)
 
     status = models.CharField(max_length=128, blank=True, choices=[
 
     status = models.CharField(max_length=128, blank=True, choices=[
-        ('PENDING', 'Pending'),
-        ('WAITING_FOR_CONFIRMATION', 'Waiting for confirmation'),
-        ('COMPLETED', 'Completed'),
-        ('CANCELED', 'Canceled'),
-        ('REJECTED', 'Rejected'),
+        ('PENDING', _('Pending')),
+        ('WAITING_FOR_CONFIRMATION', _('Waiting for confirmation')),
+        ('COMPLETED', _('Completed')),
+        ('CANCELED', _('Canceled')),
+        ('REJECTED', _('Rejected')),
+
+        ('ERR-INVALID_TOKEN', _('Invalid token')),
     ])
     ])
+    created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True)
+    completed_at = models.DateTimeField(null=True, blank=True)
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
+        verbose_name = _('PayU order')
+        verbose_name_plural = _('PayU orders')
 
     # These need to be provided in a subclass.
 
 
     # These need to be provided in a subclass.
 
@@ -64,6 +77,9 @@ class Order(models.Model):
     def get_notify_url(self):
         raise NotImplementedError
 
     def get_notify_url(self):
         raise NotImplementedError
 
+    def get_thanks_url(self):
+        raise NotImplementedError
+
     def status_updated(self):
         pass
 
     def status_updated(self):
         pass
 
@@ -72,6 +88,11 @@ class Order(models.Model):
     def get_pos(self):
         return POSS[self.pos_id]
 
     def get_pos(self):
         return POSS[self.pos_id]
 
+    def get_continue_url(self):
+        return "https://{}{}".format(
+            Site.objects.get_current().domain,
+            self.get_thanks_url())
+
     def get_representation(self, token=None):
         rep = {
             "notifyUrl": self.get_notify_url(),
     def get_representation(self, token=None):
         rep = {
             "notifyUrl": self.get_notify_url(),
@@ -79,7 +100,7 @@ class Order(models.Model):
             "merchantPosId": self.pos_id,
             "currencyCode": self.get_pos().currency_code,
             "totalAmount": str(int(self.get_amount() * 100)),
             "merchantPosId": self.pos_id,
             "currencyCode": self.get_pos().currency_code,
             "totalAmount": str(int(self.get_amount() * 100)),
-            "extOrderId": "wolne-lektury-rcz-%d" % self.pk,
+            "extOrderId": "wolne-lektury-%d" % self.pk,
 
             "buyer": self.get_buyer() or {},
             "continueUrl": self.get_continue_url(),
 
             "buyer": self.get_buyer() or {},
             "continueUrl": self.get_continue_url(),
@@ -116,20 +137,30 @@ class Order(models.Model):
                 token.save()
             # else?
 
                 token.save()
             # else?
 
-        self.order_id = response['orderId']
-        self.save()
+        if 'orderId' not in response:
+            code = response.get('status', {}).get('codeLiteral', '')
+            if code:
+                self.status = 'ERR-' + str(code)
+                self.save()
+                self.status_updated()
+            else:
+                raise ValueError("Expecting dict with `orderId` key, got: %s" % response)
+        else:
+            self.order_id = response['orderId']
+            self.save()
 
 
-        
-        return response.get('redirectUri', self.schedule.get_thanks_url())
+        return response.get('redirectUri', self.get_thanks_url())
 
 
 class Notification(models.Model):
     """ Add `order` FK to real Order model. """
 
 
 class Notification(models.Model):
     """ Add `order` FK to real Order model. """
-    body = models.TextField()
-    received_at = models.DateTimeField(auto_now_add=True)
+    body = models.TextField(_('body'))
+    received_at = models.DateTimeField(_('received_at'), auto_now_add=True)
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
+        verbose_name = _('PayU notification')
+        verbose_name_plural = _('PayU notifications')
 
     def get_status(self):
         return json.loads(self.body)['order']['status']
 
     def get_status(self):
         return json.loads(self.body)['order']['status']
@@ -138,5 +169,7 @@ class Notification(models.Model):
         status = self.get_status()
         if self.order.status not in (status, 'COMPLETED'):
             self.order.status = status
         status = self.get_status()
         if self.order.status not in (status, 'COMPLETED'):
             self.order.status = status
+            if status == 'COMPLETED':
+                self.order.completed_at = now()
             self.order.save()
             self.order.status_updated()
             self.order.save()
             self.order.status_updated()