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]
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
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):
)
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 ''
--- /dev/null
+# 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,
+ ),
+ ]
--- /dev/null
+# 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)
+ ]
--- /dev/null
+# 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'),
+ ),
+ ]
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
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)
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()
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)
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):
#
from django.conf import settings
from django.urls import reverse
+from paypal.rest import agreement_approval_url
class PaymentMethod(object):
class PayU(PaymentMethod):
is_onetime = True
slug = 'payu'
+ name = 'PayU'
template_name = 'club/payment/payu.html'
def __init__(self, pos_id):
class PayURe(PaymentMethod):
- slug='payu-re'
+ slug = 'payu-re'
+ name = 'PayU recurring'
template_name = 'club/payment/payu-re.html'
is_recurring = True
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()
+)
flex: 1;
}
}
+
+
+.methods {
+ .button {
+ border: 1px solid black;
+ border-radius: 10px;
+ padding: 10px;
+ margin-right: 3%;
+
+ &.active {
+ background: #9ACD3240;
+ }
+ }
+}
{% load i18n %}
{% load static %}
-<span style="vertical-align: bottom; padding-right: 5px; font-size: 12px">
+<span style="padding-right: 5px; font-size: 12px">
{% trans "Safe payments" %}
</span>
-<img src="{% static 'club/payu/payu.png' %}">
-<img src="{% static 'club/visa-100.png' %}">
-<img src="{% static 'club/mastercard.png' %}">
+<span class="button active" data-method="payu-re">
+ <img src="{% static 'club/payu/payu.png' %}" alt="PayU">
+ <img src="{% static 'club/visa-100.png' %}" alt="Visa" >
+ <img src="{% static 'club/mastercard.png' %}" alt="Mastercard">
+</span>
+<span class="button" data-method="paypal">
+ <img src="{% static 'club/paypal2.png' %}" alt="PayPal">
+</span>
{% for e in form.amount.errors %}
<li>{{ e }}</li>
{% endfor %}
+ {% for e in form.method.errors %}
+ <li>{{ e }}</li>
+ {% endfor %}
</ul>
<h3>1. {% trans "Choose your type of support" %}</h3>
{{ form.amount }}
{{ form.monthly }}
+{{ form.method }}
<div class="plan-select">
<span class="button plan-toggle" data-plan="plan-single" data-monthly="False">{% trans "one-time" %}</span>
<span class="button plan-toggle active" data-plan="plan-monthly" data-monthly="True">{% trans "monthly" %}</span>
<span class="button">{% trans "different amount" %}</span>
<input type="number" min="{{ club.min_amount }}">
</span>
- <div class="methods">{% include 'club/payment/payu-re.html' %}</div>
+ <div class="methods">
+ {% include 'club/payment/payu-re.html' %}
+ </div>
</div>
<h3>3. {% trans "Provide an e-mail address" %}</h3>
--- /dev/null
+from django.contrib import admin
+from . import models
+
+
+admin.site.register(models.BillingAgreement)
+admin.site.register(models.BillingPlan)
+
--- /dev/null
+# 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,
+ ),
+ ]
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)
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):
"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",
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:
})
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:
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')
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/<key>/', views.paypal_return, name='paypal_return'),
+ path('app-return/<key>/', views.paypal_return, kwargs={'app': True}, name='paypal_app_return'),
path('cancel/', views.paypal_cancel, name='paypal_cancel'),
)
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
@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
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:
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):
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);
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