# 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 django.contrib import admin
+from django.utils.html import conditional_escape
+from django.utils.safestring import mark_safe
from modeltranslation.admin import TranslationAdmin
from . import models
class PayUOrderInline(admin.TabularInline):
model = models.PayUOrder
+ fields = ['order_id', 'status', 'customer_ip']
+ readonly_fields = fields
extra = 0
show_change_link = True
+ can_delete = False
+
+ def has_add_permission(self, request, obj):
+ return False
class PayUCardTokenInline(admin.TabularInline):
model = models.PayUCardToken
+ fields = ['created_at', 'disposable_token', 'reusable_token']
+ readonly_fields = fields
extra = 0
show_change_link = True
+ can_delete = False
+ show_change_link = True
+
+ def has_add_permission(self, request, obj):
+ return False
class ScheduleAdmin(admin.ModelAdmin):
- list_display = ['email', 'started_at', 'expires_at', 'plan', 'amount', 'is_cancelled']
+ list_display = ['email', 'started_at', 'payed_at', 'expires_at', 'plan', 'amount', 'is_cancelled']
list_search = ['email']
list_filter = ['is_cancelled']
date_hierarchy = 'started_at'
class ScheduleInline(admin.TabularInline):
model = models.Schedule
+ fields = ['email', 'plan', 'amount', 'method', 'is_cancelled', 'started_at', 'payed_at', 'expires_at', 'email_sent']
+ readonly_fields = fields
extra = 0
show_change_link = True
+ can_delete = False
+
+ def has_add_permission(self, request, obj):
+ return False
+
class MembershipAdmin(admin.ModelAdmin):
list_display = ['user']
admin.site.register(models.ReminderEmail, TranslationAdmin)
-admin.site.register(models.PayUNotification)
+class PayUNotificationAdmin(admin.ModelAdmin):
+ list_display = ['received_at', 'order']
+ fields = ['received_at', 'order', 'body_']
+ readonly_fields = ['received_at', 'body_']
+ raw_id_fields = ['order']
+
+ def body_(self, obj):
+ return mark_safe(
+ "<pre>" +
+ conditional_escape(json.dumps(json.loads(obj.body), indent=4))
+ + "</pre>")
+
+
+admin.site.register(models.PayUNotification, PayUNotificationAdmin)
+
+
+class PayUNotificationInline(admin.TabularInline):
+ model = models.PayUNotification
+ fields = ['received_at', 'body_']
+ readonly_fields = fields
+ extra = 0
+ show_change_link = True
+ can_delete = False
+
+ def body_(self, obj):
+ return mark_safe(
+ "<pre>" +
+ conditional_escape(json.dumps(json.loads(obj.body), indent=4))
+ + "</pre>")
+
+ def has_add_permission(self, request, obj):
+ return False
+
+
+class PayUOrderAdmin(admin.ModelAdmin):
+ list_display = ['schedule']
+ raw_id_fields = ['schedule']
+ inlines = [PayUNotificationInline]
+
+
+admin.site.register(models.PayUOrder, PayUOrderAdmin)
def get_active_schedule(user):
if not user.is_authenticated:
return None
- return Schedule.objects.filter(membership__user=user, expires_at__gt=now()).first()
+ return Schedule.objects.filter(
+ membership__user=user
+ ).exclude(payed_at=None).exclude(expires_at__lt=now()).first()
--- /dev/null
+# Generated by Django 2.2.5 on 2019-09-30 13:02
+
+from django.db import migrations
+
+
+def fix_notification_body(apps, schema_editor):
+ PayUNotification = apps.get_model('club', 'PayUNotification')
+ for n in PayUNotification.objects.filter(body__startswith='b'):
+ n.body = n.body[2:-1]
+ n.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0010_auto_20190529_0946'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ fix_notification_body,
+ migrations.RunPython.noop,
+ elidable=True),
+ ]
--- /dev/null
+# Generated by Django 2.2.5 on 2019-09-30 13:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0011_fix_notification_body'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='payucardtoken',
+ options={'verbose_name': 'PayU card token', 'verbose_name_plural': 'PayU card tokens'},
+ ),
+ migrations.AlterModelOptions(
+ name='payunotification',
+ options={'verbose_name': 'PayU notification', 'verbose_name_plural': 'PayU notifications'},
+ ),
+ migrations.AlterModelOptions(
+ name='payuorder',
+ options={'verbose_name': 'PayU order', 'verbose_name_plural': 'PayU orders'},
+ ),
+ migrations.AddField(
+ model_name='schedule',
+ name='payed_at',
+ field=models.DateTimeField(blank=True, null=True, verbose_name='payed at'),
+ ),
+ migrations.AlterField(
+ model_name='payucardtoken',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True, verbose_name='created_at'),
+ ),
+ migrations.AlterField(
+ model_name='payucardtoken',
+ name='disposable_token',
+ field=models.CharField(max_length=255, verbose_name='disposable token'),
+ ),
+ migrations.AlterField(
+ model_name='payucardtoken',
+ name='pos_id',
+ field=models.CharField(max_length=255, verbose_name='POS id'),
+ ),
+ migrations.AlterField(
+ model_name='payucardtoken',
+ name='reusable_token',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='reusable token'),
+ ),
+ migrations.AlterField(
+ model_name='payunotification',
+ name='body',
+ field=models.TextField(verbose_name='body'),
+ ),
+ migrations.AlterField(
+ model_name='payunotification',
+ name='received_at',
+ field=models.DateTimeField(auto_now_add=True, verbose_name='received_at'),
+ ),
+ migrations.AlterField(
+ model_name='payuorder',
+ name='customer_ip',
+ field=models.GenericIPAddressField(verbose_name='customer IP'),
+ ),
+ migrations.AlterField(
+ model_name='payuorder',
+ name='order_id',
+ field=models.CharField(blank=True, max_length=255, verbose_name='order ID'),
+ ),
+ migrations.AlterField(
+ model_name='payuorder',
+ name='pos_id',
+ field=models.CharField(max_length=255, verbose_name='POS id'),
+ ),
+ migrations.AlterField(
+ model_name='schedule',
+ name='method',
+ field=models.CharField(choices=[('payu-re', 'PayU (płatność odnawialna)'), ('payu', 'PayU')], max_length=255, verbose_name='method'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.5 on 2019-09-30 13:13
+import json
+from django.db import migrations
+
+
+def populate_payed_at(apps, schema_editor):
+ PayUNotification = apps.get_model('club', 'PayUNotification')
+ for notification in PayUNotification.objects.order_by('received_at'):
+ status = json.loads(notification.body)['order']['status']
+ schedule = notification.order.schedule
+ if status == 'COMPLETED' and schedule.payed_at is None:
+ schedule.payed_at = notification.received_at
+ schedule.save()
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0012_auto_20190930_1510'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ populate_payed_at,
+ migrations.RunPython.noop,
+ elidable=True)
+ ]
amount = models.DecimalField(_('amount'), max_digits=10, decimal_places=2)
method = models.CharField(_('method'), max_length=255, choices=[(method.slug, method.name) for method in methods])
is_cancelled = models.BooleanField(_('cancelled'), default=False)
+ payed_at = models.DateTimeField(_('payed at'), null=True, blank=True)
started_at = models.DateTimeField(_('started at'), auto_now_add=True)
expires_at = models.DateTimeField(_('expires_at'), null=True, blank=True)
email_sent = models.BooleanField(default=False)
return self.expires_at is not None and self.expires_at <= now()
def is_active(self):
- return self.expires_at is not None and self.expires_at > now()
+ return self.payed_at is not None and (self.expires_at is None or self.expires_at > now())
def send_email(self):
ctx = {'schedule': self}
def status_updated(self):
if self.status == 'COMPLETED':
- since = self.schedule.expires_at or now()
+ since = self.schedule.expires_at
+ if since is None or since < self.received_at:
+ since = self.received_at
new_exp = self.schedule.plan.get_next_installment(since)
+ if self.schedule.payed_at is None:
+ self.schedule.payed_at = self.received_at
if self.schedule.expires_at is None or self.schedule.expires_at < new_exp:
self.schedule.expires_at = new_exp
self.schedule.save()
from django.contrib.sites.models import Site
from django.db import models
from django.urls import reverse
+from django.utils.translation import ugettext_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')),
])
class Meta:
abstract = True
+ verbose_name = _('PayU order')
+ verbose_name_plural = _('PayU orders')
# These need to be provided in a subclass.
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']
return http.HttpResponseBadRequest('wrong')
notification = order.notification_set.create(
- body=request.body
+ body=request.body.decode('utf-8')
)
notification.apply()
{% else %}
+ {% if not schedule.payed_at %}
Składka nie została jeszcze opłacona.
{% if schedule.payuorder_set.exists %}
Czekamy na potwierdzenie płatności.
</form>
{% endif %}
+ {% endif %}
{% endif %}
{% endif %}