From 74f7584b18b4386433b4c02336f5adafcae530c5 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 22 Jun 2021 10:30:51 +0200 Subject: [PATCH] paypal --- src/club/admin.py | 7 +++- src/club/forms.py | 17 +++++++- src/club/migrations/0029_schedule_method.py | 19 +++++++++ src/club/migrations/0030_populate_method.py | 23 +++++++++++ .../migrations/0031_auto_20210622_0945.py | 18 +++++++++ src/club/models.py | 37 +++++++++++------- src/club/payment_methods.py | 23 +++++++++-- src/club/static/club/club.scss | 14 +++++++ src/club/static/club/paypal2.png | Bin 0 -> 17255 bytes src/club/templates/club/payment/payu-re.html | 13 ++++-- src/club/templates/club/payment_form.html | 8 +++- src/paypal/admin.py | 7 ++++ .../migrations/0004_auto_20210622_0945.py | 25 ++++++++++++ src/paypal/models.py | 2 +- src/paypal/rest.py | 19 +++++---- src/paypal/urls.py | 4 +- src/paypal/views.py | 13 ++++-- src/wolnelektury/static/js/base.js | 7 ++++ src/wolnelektury/urls.py | 2 +- 19 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 src/club/migrations/0029_schedule_method.py create mode 100644 src/club/migrations/0030_populate_method.py create mode 100644 src/club/migrations/0031_auto_20210622_0945.py create mode 100644 src/club/static/club/paypal2.png create mode 100644 src/paypal/admin.py create mode 100644 src/paypal/migrations/0004_auto_20210622_0945.py diff --git a/src/club/admin.py b/src/club/admin.py index 4e85d4dc1..4d65c3c46 100644 --- a/src/club/admin.py +++ b/src/club/admin.py @@ -55,9 +55,12 @@ class ExpiredFilter(YesNoFilter): class ScheduleAdmin(admin.ModelAdmin): - list_display = ['email', 'started_at', 'payed_at', 'expires_at', 'amount', 'monthly', 'yearly', 'is_cancelled'] + list_display = [ + 'email', 'started_at', 'payed_at', 'expires_at', 'amount', 'monthly', 'yearly', 'is_cancelled', + 'method' + ] search_fields = ['email'] - list_filter = ['is_cancelled', 'monthly', 'yearly', PayedFilter, ExpiredFilter, 'source'] + list_filter = ['is_cancelled', 'monthly', 'yearly', 'method', PayedFilter, ExpiredFilter, 'source'] date_hierarchy = 'started_at' raw_id_fields = ['membership'] inlines = [PayUOrderInline, PayUCardTokenInline] diff --git a/src/club/forms.py b/src/club/forms.py index 2664fa353..d1bebe85a 100644 --- a/src/club/forms.py +++ b/src/club/forms.py @@ -5,7 +5,7 @@ from decimal import Decimal from django import forms from django.utils.translation import ugettext as _ from newsletter.forms import NewsletterForm -from . import models +from . import models, payment_methods from .payu.forms import CardTokenForm @@ -14,10 +14,11 @@ class ScheduleForm(forms.ModelForm, NewsletterForm): class Meta: model = models.Schedule - fields = ['monthly', 'amount', 'email'] + fields = ['monthly', 'amount', 'email', 'method'] widgets = { 'amount': forms.HiddenInput, 'monthly': forms.HiddenInput, + 'method': forms.HiddenInput, } def __init__(self, referer=None, **kwargs): @@ -35,6 +36,18 @@ class ScheduleForm(forms.ModelForm, NewsletterForm): ) return value + def clean_method(self): + value = self.cleaned_data['method'] + monthly = self.cleaned_data['monthly'] + for m in payment_methods.methods: + if m.slug == value: + if (monthly and m.is_recurring) or (not monthly and m.is_onetime): + return value + if monthly: + return payment_methods.recurring_payment_method.slug + else: + return payment_methods.single_payment_method.slug + def save(self, *args, **kwargs): NewsletterForm.save(self, *args, **kwargs) self.instance.source = self.referer or '' diff --git a/src/club/migrations/0029_schedule_method.py b/src/club/migrations/0029_schedule_method.py new file mode 100644 index 000000000..2be768bdc --- /dev/null +++ b/src/club/migrations/0029_schedule_method.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.19 on 2021-06-18 10:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0028_directdebit'), + ] + + operations = [ + migrations.AddField( + model_name='schedule', + name='method', + field=models.CharField(choices=[('payu-re', 'payu-re'), ('payu', 'payu'), ('paypal', 'paypal')], default='', max_length=32, verbose_name='method'), + preserve_default=False, + ), + ] diff --git a/src/club/migrations/0030_populate_method.py b/src/club/migrations/0030_populate_method.py new file mode 100644 index 000000000..40b2cdd67 --- /dev/null +++ b/src/club/migrations/0030_populate_method.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.19 on 2021-06-18 10:11 + +from django.db import migrations, models + + +def populate_method(apps, schema_editor): + Schedule = apps.get_model('club', 'Schedule') + Schedule.objects.filter(method='', monthly=False, yearly=False).update(method='payu') + Schedule.objects.filter( + models.Q(monthly=True) | models.Q(yearly=True), + method='' + ).update(method='payu-re') + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0029_schedule_method'), + ] + + operations = [ + migrations.RunPython(populate_method, migrations.RunPython.noop) + ] diff --git a/src/club/migrations/0031_auto_20210622_0945.py b/src/club/migrations/0031_auto_20210622_0945.py new file mode 100644 index 000000000..01e7c6c7b --- /dev/null +++ b/src/club/migrations/0031_auto_20210622_0945.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.19 on 2021-06-22 07:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0030_populate_method'), + ] + + operations = [ + migrations.AlterField( + model_name='schedule', + name='method', + field=models.CharField(choices=[('payu-re', 'PayU recurring'), ('payu', 'PayU'), ('paypal', 'PayPal')], max_length=32, verbose_name='method'), + ), + ] diff --git a/src/club/models.py b/src/club/models.py index 9a74e27b2..8905f08c4 100644 --- a/src/club/models.py +++ b/src/club/models.py @@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _, ungettext, ugettext, ge from catalogue.utils import get_random_hash from messaging.states import Level from reporting.utils import render_to_pdf -from .payment_methods import recurring_payment_method, single_payment_method +from .payment_methods import methods from .payu import models as payu_models from . import utils @@ -49,6 +49,9 @@ class Schedule(models.Model): email = models.EmailField(_('email')) membership = models.ForeignKey('Membership', verbose_name=_('membership'), null=True, blank=True, on_delete=models.SET_NULL) amount = models.DecimalField(_('amount'), max_digits=10, decimal_places=2) + method = models.CharField(_('method'), max_length=32, choices=[ + (m.slug, m.name) for m in methods + ]) monthly = models.BooleanField(_('monthly'), default=True) yearly = models.BooleanField(_('yearly'), default=False) @@ -86,7 +89,7 @@ class Schedule(models.Model): return reverse('club_thanks', args=[self.key]) def get_payment_method(self): - return recurring_payment_method if self.monthly or self.yearly else single_payment_method + return [m for m in methods if m.slug == self.method][0] def is_expired(self): return self.expires_at is not None and self.expires_at <= now() @@ -97,6 +100,21 @@ class Schedule(models.Model): def is_recurring(self): return self.monthly or self.yearly + def set_payed(self): + since = self.expires_at + n = now() + if since is None or since < n: + since = n + new_exp = self.get_next_installment(since) + if self.payed_at is None: + self.payed_at = n + if self.expires_at is None or self.expires_at < new_exp: + self.expires_at = new_exp + self.save() + + if not self.email_sent: + self.send_email() + def get_next_installment(self, date): if self.yearly: return utils.add_year(date) @@ -247,19 +265,8 @@ class PayUOrder(payu_models.Order): def status_updated(self): if self.status == 'COMPLETED': - since = self.schedule.expires_at - n = now() - if since is None or since < n: - since = n - new_exp = self.schedule.get_next_installment(since) - if self.schedule.payed_at is None: - self.schedule.payed_at = n - if self.schedule.expires_at is None or self.schedule.expires_at < new_exp: - self.schedule.expires_at = new_exp - self.schedule.save() - - if not self.schedule.email_sent: - self.schedule.send_email() + self.schedule.set_payed() + @classmethod def send_receipt(cls, email, year): diff --git a/src/club/payment_methods.py b/src/club/payment_methods.py index 1b27bd3e7..9b73c8970 100644 --- a/src/club/payment_methods.py +++ b/src/club/payment_methods.py @@ -3,6 +3,7 @@ # from django.conf import settings from django.urls import reverse +from paypal.rest import agreement_approval_url class PaymentMethod(object): @@ -16,6 +17,7 @@ class PaymentMethod(object): class PayU(PaymentMethod): is_onetime = True slug = 'payu' + name = 'PayU' template_name = 'club/payment/payu.html' def __init__(self, pos_id): @@ -33,7 +35,8 @@ class PayU(PaymentMethod): class PayURe(PaymentMethod): - slug='payu-re' + slug = 'payu-re' + name = 'PayU recurring' template_name = 'club/payment/payu-re.html' is_recurring = True @@ -59,23 +62,35 @@ class PayURe(PaymentMethod): class PayPal(PaymentMethod): - slug='paypal' + slug = 'paypal' + name = 'PayPal' template_name = 'club/payment/paypal.html' is_recurring = True - is_onetime = True + is_onetime = False def initiate(self, request, schedule): - return reverse('club_dummy_payment', args=[schedule.key]) + app = request.GET.get('app') + return agreement_approval_url(schedule.amount, schedule.key, app=app) + +methods = [] pos = getattr(settings, 'CLUB_PAYU_RECURRING_POS', None) if pos: recurring_payment_method = PayURe(pos) + methods.append(recurring_payment_method) else: recurring_payment_method = None pos = getattr(settings, 'CLUB_PAYU_POS', None) if pos: single_payment_method = PayU(pos) + methods.append(single_payment_method) else: single_payment_method = None + + + +methods.append( + PayPal() +) diff --git a/src/club/static/club/club.scss b/src/club/static/club/club.scss index 892faf4ee..6b533e2c3 100644 --- a/src/club/static/club/club.scss +++ b/src/club/static/club/club.scss @@ -125,3 +125,17 @@ flex: 1; } } + + +.methods { + .button { + border: 1px solid black; + border-radius: 10px; + padding: 10px; + margin-right: 3%; + + &.active { + background: #9ACD3240; + } + } +} diff --git a/src/club/static/club/paypal2.png b/src/club/static/club/paypal2.png new file mode 100644 index 0000000000000000000000000000000000000000..33ea162bcb5d9d09d5753a3888fb376d656f016e GIT binary patch literal 17255 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 + {% trans "Safe payments" %} - - - + + PayU + Visa + Mastercard + + + PayPal + diff --git a/src/club/templates/club/payment_form.html b/src/club/templates/club/payment_form.html index 2891b98c9..72d3e2515 100644 --- a/src/club/templates/club/payment_form.html +++ b/src/club/templates/club/payment_form.html @@ -10,12 +10,16 @@ {% for e in form.amount.errors %}
  • {{ e }}
  • {% endfor %} + {% for e in form.method.errors %} +
  • {{ e }}
  • + {% endfor %}

    1. {% trans "Choose your type of support" %}

    {{ form.amount }} {{ form.monthly }} +{{ form.method }}
    {% trans "one-time" %} {% trans "monthly" %} @@ -45,7 +49,9 @@ {% trans "different amount" %} -
    {% include 'club/payment/payu-re.html' %}
    +
    + {% include 'club/payment/payu-re.html' %} +

    3. {% trans "Provide an e-mail address" %}

    diff --git a/src/paypal/admin.py b/src/paypal/admin.py new file mode 100644 index 000000000..c8151fab7 --- /dev/null +++ b/src/paypal/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from . import models + + +admin.site.register(models.BillingAgreement) +admin.site.register(models.BillingPlan) + diff --git a/src/paypal/migrations/0004_auto_20210622_0945.py b/src/paypal/migrations/0004_auto_20210622_0945.py new file mode 100644 index 000000000..cc1b0cd84 --- /dev/null +++ b/src/paypal/migrations/0004_auto_20210622_0945.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.19 on 2021-06-22 07:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0031_auto_20210622_0945'), + ('paypal', '0003_auto_20190729_1450'), + ] + + operations = [ + migrations.RemoveField( + model_name='billingagreement', + name='user', + ), + migrations.AddField( + model_name='billingagreement', + name='schedule', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.PROTECT, to='club.Schedule'), + preserve_default=False, + ), + ] diff --git a/src/paypal/models.py b/src/paypal/models.py index 2ce3635a7..3fc012ba7 100644 --- a/src/paypal/models.py +++ b/src/paypal/models.py @@ -13,7 +13,7 @@ class BillingPlan(models.Model): class BillingAgreement(models.Model): agreement_id = models.CharField(max_length=32) - user = models.ForeignKey(User, models.PROTECT) + schedule = models.ForeignKey('club.Schedule', models.PROTECT) plan = models.ForeignKey(BillingPlan, models.PROTECT) active = models.BooleanField(max_length=32) token = models.CharField(max_length=32) diff --git a/src/paypal/rest.py b/src/paypal/rest.py index 92752b70f..ff8c85115 100644 --- a/src/paypal/rest.py +++ b/src/paypal/rest.py @@ -18,8 +18,8 @@ class PaypalError(Exception): pass -def absolute_url(url_name): - return "http://%s%s" % (Site.objects.get_current().domain, reverse(url_name)) +def absolute_url(url_name, kwargs=None): + return "http://%s%s" % (Site.objects.get_current().domain, reverse(url_name, kwargs=kwargs)) def create_plan(amount): @@ -28,7 +28,7 @@ def create_plan(amount): "description": "Cykliczna darowizna na wsparcie Wolnych Lektur", "merchant_preferences": { "auto_bill_amount": "yes", - "return_url": absolute_url('paypal_return'), + "return_url": absolute_url('paypal_return', {'key': '-'}), "cancel_url": absolute_url('paypal_cancel'), # "initial_fail_amount_action": "continue", "max_fail_attempts": "3", @@ -63,7 +63,7 @@ def get_link(links, rel): return link.href -def create_agreement(amount, app=False): +def create_agreement(amount, key, app=False): try: plan = BillingPlan.objects.get(amount=amount) except BillingPlan.DoesNotExist: @@ -84,8 +84,13 @@ def create_agreement(amount, app=False): }) if app: billing_agreement['override_merchant_preferences'] = { - 'return_url': absolute_url('paypal_app_return'), + 'return_url': absolute_url('paypal_app_return', {'key': key}), } + else: + billing_agreement['override_merchant_preferences'] = { + 'return_url': absolute_url('paypal_return', {'key': key}), + } + response = billing_agreement.create() if response: @@ -94,8 +99,8 @@ def create_agreement(amount, app=False): raise PaypalError(billing_agreement.error) -def agreement_approval_url(amount, app=False): - agreement = create_agreement(amount, app=app) +def agreement_approval_url(amount, key, app=False): + agreement = create_agreement(amount, key, app=app) return get_link(agreement.links, 'approval_url') diff --git a/src/paypal/urls.py b/src/paypal/urls.py index aca891850..7adf644a2 100644 --- a/src/paypal/urls.py +++ b/src/paypal/urls.py @@ -9,7 +9,7 @@ urlpatterns = ( path('form/', RedirectView.as_view(url='/towarzystwo/dolacz/')), path('app-form/', RedirectView.as_view(url='/towarzystwo/dolacz/app/')), - path('return/', views.paypal_return, name='paypal_return'), - path('app-return/', views.paypal_return, kwargs={'app': True}, name='paypal_app_return'), + path('return//', views.paypal_return, name='paypal_return'), + path('app-return//', views.paypal_return, kwargs={'app': True}, name='paypal_app_return'), path('cancel/', views.paypal_cancel, name='paypal_cancel'), ) diff --git a/src/paypal/views.py b/src/paypal/views.py index 01d3a5aab..b1720f9f8 100644 --- a/src/paypal/views.py +++ b/src/paypal/views.py @@ -6,9 +6,10 @@ from decimal import Decimal from django.contrib.auth.decorators import login_required from django.http import Http404 from django.http.response import HttpResponseRedirect, HttpResponseForbidden -from django.shortcuts import render +from django.shortcuts import get_object_or_404, render from api.utils import HttpResponseAppRedirect +from club.models import Schedule from paypal.forms import PaypalSubscriptionForm from paypal.rest import execute_agreement, check_agreement, agreement_approval_url, PaypalError from paypal.models import BillingAgreement, BillingPlan @@ -32,7 +33,9 @@ def paypal_form(request, app=False): @login_required -def paypal_return(request, app=False): +def paypal_return(request, key, app=False): + schedule = get_object_or_404(Schedule, key=key) + token = request.GET.get('token') if not token: raise Http404 @@ -43,7 +46,9 @@ def paypal_return(request, app=False): plan = BillingPlan.objects.get(amount=amount) active = check_agreement(resource.id) or False BillingAgreement.objects.create( - agreement_id=resource.id, user=request.user, plan=plan, active=active, token=token) + agreement_id=resource.id, schedule=schedule, plan=plan, active=active, token=token) + if active: + schedule.set_payed() else: resource = None if app: @@ -52,7 +57,7 @@ def paypal_return(request, app=False): else: return HttpResponseAppRedirect('wolnelekturyapp://paypal_return') else: - return render(request, 'paypal/return.html', {'resource': resource}) + return HttpResponseRedirect(schedule.get_thanks_url()) def paypal_cancel(request): diff --git a/src/wolnelektury/static/js/base.js b/src/wolnelektury/static/js/base.js index 7d311fce3..377a969c0 100644 --- a/src/wolnelektury/static/js/base.js +++ b/src/wolnelektury/static/js/base.js @@ -355,6 +355,13 @@ return false; }); + $("#id_method").val('payu-re'); + $(".methods .button").click(function() { + $("#id_method").val($(this).attr('data-method')); + $(".methods .button").removeClass('active'); + $(this).addClass("active"); + }); + }); })(jQuery); diff --git a/src/wolnelektury/urls.py b/src/wolnelektury/urls.py index 09b4d3d42..492f1ddd5 100644 --- a/src/wolnelektury/urls.py +++ b/src/wolnelektury/urls.py @@ -59,7 +59,7 @@ urlpatterns += [ path('towarzystwo/', include('club.urls')), #path('pomagam/', include('club.urls2')), path('pomagam/', RedirectView.as_view( - url='/towarzystwo/', permanent=False)), + url='/towarzystwo/?pk_campaign=pomagam', permanent=False)), # Admin panel -- 2.20.1