)
emails.update(
Funding.objects.exclude(email='').filter(
- payed_at__year=year
+ completed_at__year=year
).order_by('email').values_list(
'email', flat=True
).distinct()
"language": get_language(),
}
- def get_continue_url(self):
- return "https://{}{}".format(
- Site.objects.get_current().domain,
- self.schedule.get_thanks_url())
-
def get_description(self):
return 'Wolne Lektury'
Site.objects.get_current().domain,
reverse('club_payu_notify', args=[self.pk]))
+ def get_thanks_url(self):
+ return self.schedule.get_thanks_url()
+
def status_updated(self):
if self.status == 'COMPLETED':
self.schedule.set_payed()
except Contact.DoesNotExist:
funding = Funding.objects.filter(
email=email,
- payed_at__year=year,
- notifications=True).order_by('payed_at').first()
+ completed_at__year=year,
+ notifications=True).order_by('completed_at').first()
if funding is None:
print('no notifications')
return
fundings = Funding.objects.filter(
email=email,
- payed_at__year=year
- ).order_by('payed_at')
+ completed_at__year=year
+ ).order_by('completed_at')
for funding in fundings:
payments.append({
- 'timestamp': funding.payed_at,
+ 'timestamp': funding.completed_at,
'amount': funding.amount,
})
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(),
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):
DEFAULT_AMOUNT = 20
MIN_AMOUNT = 1
DAYS_NEAR = 2
+ PAYU_POS = '300746'
app_settings = Settings('FUNDING')
def queryset(self, request, queryset):
if self.value() == 'yes':
- return queryset.exclude(payed_at=None)
+ return queryset.exclude(completed_at=None)
elif self.value() == 'no':
- return queryset.filter(payed_at=None)
+ return queryset.filter(completed_at=None)
class PerksFilter(admin.SimpleListFilter):
class FundingAdmin(admin.ModelAdmin):
model = Funding
- list_display = ['payed_at', 'offer', 'amount', 'name', 'email', 'perk_names']
+ list_display = ['created_at', 'completed_at', 'offer', 'amount', 'name', 'email', 'perk_names']
search_fields = ['name', 'email', 'offer__title', 'offer__author']
list_filter = [PayedFilter, 'offer', PerksFilter]
search_fields = ['user']
actions = [export_as_csv_action(
fields=[
- 'id', 'offer', 'name', 'email', 'amount', 'payed_at',
+ 'id', 'offer', 'name', 'email', 'amount', 'completed_at',
'notifications', 'notify_key', 'wl_optout_url'
]
)]
from django.utils.translation import ugettext_lazy as _, ugettext, get_language
from newsletter.forms import NewsletterForm
+from club.payment_methods import PayU
from .models import Funding
from .widgets import PerksAmountWidget
from . import app_settings
+payment_method = PayU(app_settings.PAYU_POS)
+
+
class FundingForm(NewsletterForm):
required_css_class = 'required'
def __init__(self, request, offer, *args, **kwargs):
self.offer = offer
self.user = request.user if request.user.is_authenticated else None
+ self.client_ip = request.META['REMOTE_ADDR']
super(FundingForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.form_instance = self
amount=self.cleaned_data['amount'],
language_code=get_language(),
user=self.user,
+ pos_id=payment_method.pos_id,
+ customer_ip=self.client_ip,
)
funding.perks.set(funding.offer.get_perks(funding.amount))
return funding
--- /dev/null
+# Generated by Django 2.2.28 on 2022-10-03 10:30
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('funding', '0006_funding_user'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='funding',
+ options={'ordering': ['-completed_at', 'pk'], 'verbose_name': 'funding', 'verbose_name_plural': 'fundings'},
+ ),
+ migrations.RenameField(
+ model_name='funding',
+ old_name='payed_at',
+ new_name='completed_at',
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.28 on 2022-10-03 10:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('funding', '0007_auto_20221003_1230'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='funding',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='funding',
+ name='customer_ip',
+ field=models.GenericIPAddressField(null=True, verbose_name='customer IP'),
+ ),
+ migrations.AddField(
+ model_name='funding',
+ name='order_id',
+ field=models.CharField(blank=True, max_length=255, verbose_name='order ID'),
+ ),
+ migrations.AddField(
+ model_name='funding',
+ name='pos_id',
+ field=models.CharField(default='', max_length=255, verbose_name='POS id'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='funding',
+ name='status',
+ field=models.CharField(blank=True, choices=[('PENDING', 'Pending'), ('WAITING_FOR_CONFIRMATION', 'Waiting for confirmation'), ('COMPLETED', 'Completed'), ('CANCELED', 'Canceled'), ('REJECTED', 'Rejected'), ('ERR-INVALID_TOKEN', 'Invalid token')], max_length=128),
+ ),
+ migrations.AlterField(
+ model_name='funding',
+ name='completed_at',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.CreateModel(
+ name='PayUNotification',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('body', models.TextField(verbose_name='body')),
+ ('received_at', models.DateTimeField(auto_now_add=True, verbose_name='received_at')),
+ ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_set', to='funding.Funding')),
+ ],
+ options={
+ 'verbose_name': 'PayU notification',
+ 'verbose_name_plural': 'PayU notifications',
+ 'abstract': False,
+ },
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.28 on 2022-10-03 10:55
+from django.conf import settings
+from django.db import migrations
+
+
+def move_from_getpaid(apps, schema_editor):
+ try:
+ G = settings.GETPAID_BACKENDS_SETTINGS
+ except AttributeError:
+ G = {}
+ getpaid_conf = False
+ else:
+ getpaid_conf = True
+
+ Funding = apps.get_model('funding', 'Funding')
+ for f in Funding.objects.filter(status=''):
+ payment = f.payment.first()
+ # TODO: what happens when no payments any more?
+ if payment is None:
+ continue
+ f.created_at = payment.created_on
+ f.order_id = payment.external_id
+ f.pos_id = G.get(payment.backend, {}).get('pos_id', '')
+ assert getpaid_conf, 'Getpaid configuration removed prematurely.'
+ f.status = {
+ 'paid': 'COMPLETED',
+ 'failed': 'REJECTED',
+ 'in_progress': 'CANCELLED',
+ }[payment.status]
+ f.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('funding', '0008_auto_20221003_1235'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ move_from_getpaid,
+ migrations.RunPython.noop
+ )
+ ]
from django.utils.html import mark_safe
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _, override
-import getpaid
from catalogue.models import Book
from catalogue.utils import get_random_hash
from polls.models import Poll
+import club.payu.models
from wolnelektury.utils import cached_render, clear_cached_renders
from . import app_settings
+from . import utils
class Offer(models.Model):
self.notify_published()
return retval
+ def get_payu_payment_title(self):
+ return utils.sanitize_payment_title(str(self))
+
def clear_cache(self):
clear_cached_renders(self.top_bar)
clear_cached_renders(self.list_bar)
return Funding.payed().filter(offer=self)
def funders(self):
- return self.funding_payed().order_by('-amount', 'payed_at')
+ return self.funding_payed().order_by('-amount', 'completed_at')
def sum(self):
""" The money gathered. """
return "%s (%s%s)" % (self.name, self.price, " for %s" % self.offer if self.offer else "")
-class Funding(models.Model):
+class Funding(club.payu.models.Order):
""" A person paying in a fundraiser.
- The payment was completed if and only if payed_at is set.
+ The payment was completed if and only if completed_at is set.
"""
offer = models.ForeignKey(Offer, models.PROTECT, verbose_name=_('offer'))
+ customer_ip = models.GenericIPAddressField(_('customer IP'), null=True)
+
name = models.CharField(_('name'), max_length=127, blank=True)
email = models.EmailField(_('email'), blank=True, db_index=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, blank=True, null=True)
amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10)
- payed_at = models.DateTimeField(_('payed at'), null=True, blank=True, db_index=True)
perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True)
language_code = models.CharField(max_length=2, null=True, blank=True)
notifications = models.BooleanField(_('notifications'), default=True, db_index=True)
class Meta:
verbose_name = _('funding')
verbose_name_plural = _('fundings')
- ordering = ['-payed_at', 'pk']
+ ordering = ['-completed_at', 'pk']
@classmethod
def payed(cls):
""" QuerySet for all completed payments. """
- return cls.objects.exclude(payed_at=None)
+ return cls.objects.exclude(completed_at=None)
def __str__(self):
return str(self.offer)
- def get_absolute_url(self):
- return reverse('funding_funding', args=[self.pk])
+ def get_amount(self):
+ return self.amount
+
+ def get_buyer(self):
+ return {
+ "email": self.email,
+ "language": self.language_code,
+ }
+
+ def get_description(self):
+ return self.offer.get_payu_payment_title()
+
+ def get_thanks_url(self):
+ return reverse('funding_thanks')
+
+ def status_updated(self):
+ if self.status == 'COMPLETED':
+ if self.email:
+ self.notify(
+ _('Thank you for your support!'),
+ 'funding/email/thanks.txt'
+ )
+
+ def get_notify_url(self):
+ return "https://{}{}".format(
+ Site.objects.get_current().domain,
+ reverse('funding_payu_notify', args=[self.pk]))
def perk_names(self):
return ", ".join(perk.name for perk in self.perks.all())
def notify_funders(cls, subject, template_name, extra_context=None, query_filter=None, payed_only=True):
funders = cls.objects.exclude(email="").filter(notifications=True)
if payed_only:
- funders = funders.exclude(payed_at=None)
+ funders = funders.exclude(completed_at=None)
if query_filter is not None:
funders = funders.filter(query_filter)
emails = set()
type(self).objects.filter(email=self.email).update(notifications=False)
-# Register the Funding model with django-getpaid for payments.
-getpaid.register_to_payment(Funding, unique=False, related_name='payment')
+class PayUNotification(club.payu.models.Notification):
+ order = models.ForeignKey(Funding, models.CASCADE, related_name='notification_set')
class Spent(models.Model):
def __str__(self):
return "Spent: %s" % str(self.book)
-
-@receiver(getpaid.signals.new_payment_query)
-def new_payment_query_listener(sender, order=None, payment=None, **kwargs):
- """ Set payment details for getpaid. """
- payment.amount = order.amount
- payment.currency = 'PLN'
-
-
-@receiver(getpaid.signals.user_data_query)
-def user_data_query_listener(sender, order, user_data, **kwargs):
- """ Set user data for payment. """
- user_data['email'] = order.email
-
-
-@receiver(getpaid.signals.payment_status_changed)
-def payment_status_changed_listener(sender, instance, old_status, new_status, **kwargs):
- """ React to status changes from getpaid. """
- if old_status != 'paid' and new_status == 'paid':
- instance.order.payed_at = datetime.utcnow().replace(tzinfo=utc)
- instance.order.save()
- if instance.order.email:
- instance.order.notify(
- _('Thank you for your support!'),
- 'funding/email/thanks.txt'
- )
<table class="wlfund">
{% for funding in fundings %}
<tr class="funding-plus">
- <td class="oneline">{{ funding.payed_at.date }}</td>
+ <td class="oneline">{{ funding.completed_at.date }}</td>
<td>
{% if funding.name %}
{{ funding.name }}
{% endfor %}
</table>
{% endspaceless %}
-{% paginate %}
\ No newline at end of file
+{% paginate %}
from django.core.paginator import Paginator, InvalidPage
from ..models import Offer
-from ..utils import sanitize_payment_title
register = template.Library()
return offer.top_bar() if offer is not None else ''
-register.filter(sanitize_payment_title)
-
-
@register.simple_tag(takes_context=True)
def fundings(context, offer):
fundings = offer.funding_payed()
path('wylacz_email/', banner_exempt(views.DisableNotifications.as_view()), name='funding_disable_notifications'),
path('przylacz/<key>/', banner_exempt(views.claim), name='funding_claim'),
- path('getpaid/', include('getpaid.urls')),
+ path('notify/<int:pk>/', views.PayUNotifyView.as_view(), name='funding_payu_notify'),
]
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView, FormView, ListView
-from getpaid.models import Payment
+import club.payu.views
from . import app_settings
from .forms import FundingForm
from .models import Offer, Spent, Funding
class OfferDetailView(FormView):
form_class = FundingForm
template_name = "funding/offer_detail.html"
- backend = 'getpaid.backends.payu'
@csrf_exempt
def dispatch(self, request, slug=None):
def form_valid(self, form):
funding = form.save()
- # Skip getpaid.forms.PaymentMethodForm, go directly to the broker.
- payment = Payment.create(funding, self.backend)
- gateway_url_tuple = payment.get_processor()(payment).get_gateway_url(self.request)
- payment.change_status('in_progress')
- return redirect(gateway_url_tuple[0])
+ return redirect(funding.put())
class CurrentView(OfferDetailView):
else funding.offer.get_absolute_url()
)
+
+class PayUNotifyView(club.payu.views.NotifyView):
+ order_model = Funding
+
'getpaid': 'wolnelektury.migrations.getpaid',
}
-GETPAID_ORDER_DESCRIPTION = "{% load funding_tags %}{{ order|sanitize_payment_title }}"
-
GETPAID_BACKENDS = (
'getpaid.backends.payu',
)