+# 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
+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. """
- 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
+ verbose_name = _('PayU card token')
+ verbose_name_plural = _('PayU card tokens')
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=[
- ('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
+ verbose_name = _('PayU order')
+ verbose_name_plural = _('PayU orders')
# These need to be provided in a subclass.
def get_notify_url(self):
raise NotImplementedError
+ def get_thanks_url(self):
+ raise NotImplementedError
+
def status_updated(self):
pass
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(),
"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(),
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_absolute_url())
+ return response.get('redirectUri', self.get_thanks_url())
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
+ verbose_name = _('PayU notification')
+ verbose_name_plural = _('PayU notifications')
def get_status(self):
return json.loads(self.body)['order']['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()