From: Radek Czajka Date: Tue, 22 Jun 2021 08:30:51 +0000 (+0200) Subject: paypal X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/74f7584b18b4386433b4c02336f5adafcae530c5 paypal --- 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 000000000..33ea162bc Binary files /dev/null and b/src/club/static/club/paypal2.png differ diff --git a/src/club/templates/club/payment/payu-re.html b/src/club/templates/club/payment/payu-re.html index b71e87a27..c3e3ea5ef 100644 --- a/src/club/templates/club/payment/payu-re.html +++ b/src/club/templates/club/payment/payu-re.html @@ -1,8 +1,13 @@ {% load i18n %} {% load static %} - + {% 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