raven
-mailchimp3
\ No newline at end of file
+mailchimp3
+
+requests
+
+paypalrestsdk
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+
+class PaypalSubscriptionForm(forms.Form):
+ amount = forms.IntegerField(min_value=10, max_value=30000, initial=20, label=_('amount'))
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='BillingAgreement',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('agreement_id', models.CharField(max_length=32)),
+ ('active', models.BooleanField(max_length=32)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='BillingPlan',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('plan_id', models.CharField(max_length=32)),
+ ('amount', models.IntegerField(unique=True, db_index=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='billingagreement',
+ name='plan',
+ field=models.ForeignKey(to='paypal.BillingPlan'),
+ ),
+ migrations.AddField(
+ model_name='billingagreement',
+ name='user',
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('paypal', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='billingagreement',
+ name='token',
+ field=models.CharField(default='', max_length=32),
+ preserve_default=False,
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+# from django.contrib.auth import get_user_model
+from django.contrib.auth.models import User
+from django.db import models
+
+
+class BillingPlan(models.Model):
+ plan_id = models.CharField(max_length=32)
+ amount = models.IntegerField(db_index=True, unique=True)
+
+
+class BillingAgreement(models.Model):
+ agreement_id = models.CharField(max_length=32)
+ user = models.ForeignKey(User)
+ plan = models.ForeignKey(BillingPlan)
+ active = models.BooleanField(max_length=32)
+ token = models.CharField(max_length=32)
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+# UNUSED
+import requests
+import urlparse
+from django.conf import settings
+
+DESC = 'Wolne Lektury subscription'
+
+
+def paypal_request(data):
+ request_data = {
+ 'USER': settings.PAYPAL['user'],
+ 'PWD': settings.PAYPAL['password'],
+ 'SIGNATURE': settings.PAYPAL['signature'],
+ 'SUBJECT': settings.PAYPAL['email'],
+ 'VERSION': 93,
+ }
+ request_data.update(data)
+
+ response = requests.post(settings.PAYPAL['api-url'], data=request_data)
+ return dict(urlparse.parse_qsl(response.text))
+
+
+def set_express_checkout(amount):
+ response = paypal_request({
+ 'METHOD': 'SetExpressCheckout',
+ 'PAYMENTREQUEST_0_PAYMENTACTION': 'SALE',
+ 'PAYMENTREQUEST_0_AMT': amount,
+ 'PAYMENTREQUEST_0_CURRENCYCODE': 'PLN',
+ 'L_BILLINGTYPE0': 'RecurringPayments',
+ 'L_BILLINGAGREEMENTDESCRIPTION0': DESC,
+ 'RETURNURL': settings.PAYPAL['return-url'],
+ 'CANCELURL': settings.PAYPAL['cancel-url'],
+ })
+ return response.get('TOKEN')
+
+
+def create_profile(token, amount):
+ response = paypal_request({
+ 'METHOD': 'CreateRecurringPaymentsProfile',
+ 'TOKEN': token,
+ 'PROFILESTARTDATE': '2011-03-11T00:00:00Z',
+ 'DESC': DESC,
+ 'MAXFAILEDPAYMENTS': 3,
+ 'AUTOBILLAMT': 'AddToNextBilling',
+ 'BILLINGPERIOD': 'Month', # or 30 Days?
+ 'BILLINGFREQUENCY': 1,
+ 'AMT': amount,
+ 'CURRENCYCODE': 'PLN',
+ 'L_PAYMENTREQUEST_0_ITEMCATEGORY0': 'Digital',
+ 'L_PAYMENTREQUEST_0_NAME0': 'Subskrypcja Wolnych Lektur',
+ 'L_PAYMENTREQUEST_0_AMT0': amount,
+ 'L_PAYMENTREQUEST_0_QTY0': 1,
+ })
+ return response.get('PROFILEID')
+
+
+# min amount: 10, max amount: 30000
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from datetime import timedelta
+
+import paypalrestsdk
+import pytz
+from django.contrib.sites.models import Site
+from django.core.urlresolvers import reverse
+from django.utils import timezone
+from paypalrestsdk import BillingPlan, BillingAgreement, ResourceNotFound
+from django.conf import settings
+from .models import BillingPlan as BillingPlanModel
+
+paypalrestsdk.configure(settings.PAYPAL_CONFIG)
+
+
+class PaypalError(Exception):
+ pass
+
+
+def absolute_url(url_name):
+ return "http://%s%s" % (Site.objects.get_current().domain, reverse(url_name))
+
+
+def create_plan(amount):
+ billing_plan = BillingPlan({
+ "name": "Cykliczna darowizna na Wolne Lektury: %s zł" % amount,
+ "description": "Cykliczna darowizna na wsparcie Wolnych Lektur",
+ "merchant_preferences": {
+ "auto_bill_amount": "yes",
+ "return_url": absolute_url('paypal_return'),
+ "cancel_url": absolute_url('paypal_cancel'),
+ # "initial_fail_amount_action": "continue",
+ "max_fail_attempts": "3",
+ },
+ "payment_definitions": [
+ {
+ "amount": {
+ "currency": "PLN",
+ "value": str(amount),
+ },
+ "cycles": "0",
+ "frequency": "MONTH",
+ "frequency_interval": "1",
+ "name": "Cykliczna darowizna",
+ "type": "REGULAR",
+ }
+ ],
+ "type": "INFINITE",
+ })
+
+ if not billing_plan.create():
+ raise PaypalError(billing_plan.error)
+ if not billing_plan.activate():
+ raise PaypalError(billing_plan.error)
+ plan, created = BillingPlanModel.objects.get_or_create(amount=amount, defaults={'plan_id': billing_plan.id})
+ return plan.plan_id
+
+
+def get_link(links, rel):
+ for link in links:
+ if link.rel == rel:
+ return link.href
+
+
+def create_agreement(amount):
+ try:
+ plan = BillingPlanModel.objects.get(amount=amount)
+ except BillingPlanModel.DoesNotExist:
+ plan_id = create_plan(amount)
+ else:
+ plan_id = plan.plan_id
+ start = (timezone.now() + timedelta(0, 3600*24)).astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
+ billing_agreement = BillingAgreement({
+ "name": "Subskrypcja klubu WL",
+ "description": "Cykliczne wspieranie Wolnych Lektur kwotą %s złotych" % amount,
+ "start_date": start,
+ "plan": {
+ "id": plan_id,
+ },
+ "payer": {
+ "payment_method": "paypal"
+ },
+ })
+
+ response = billing_agreement.create()
+ if response:
+ return billing_agreement
+ else:
+ raise PaypalError(billing_agreement.error)
+
+
+def agreement_approval_url(amount):
+ agreement = create_agreement(amount)
+ return get_link(agreement.links, 'approval_url')
+
+
+def get_agreement(agreement_id):
+ try:
+ return BillingAgreement.find(agreement_id)
+ except ResourceNotFound:
+ return None
+
+
+def check_agreement(agreement_id):
+ a = get_agreement(agreement_id)
+ if a:
+ return a.state == 'Active'
+
+
+def execute_agreement(token):
+ return BillingAgreement.execute(token)
--- /dev/null
+{% extends "base/base.html" %}
+{% load i18n %}
+
+{% block body %}
+ <p>{% trans "Zrezygnowano z płatności :(" %}</p>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+<h1>{% trans "PayPal Error" %}: {{ error.message }}</h1>
+{% for detail in error.details %}
+ <p>{{ detail.field }}: {{ detail.issue }}</p>
+{% endfor %}
+<p><a href="{{ error.information_link }}">{% trans "Learn more" %}</a></p>
\ No newline at end of file
--- /dev/null
+{% extends "base/base.html" %}
+
+{% block body %}
+ {% include "paypal/error.html" %}
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends "base/base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Subscription" %}{% endblock %}
+
+{% block body %}
+ <form method="post">
+ {% csrf_token %}
+ {{ form.as_p }}
+ {# paypal submit button #}
+ <button type="submit">{% trans "Subscribe with PayPal" %}</button>
+ </form>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends "base/base.html" %}
+{% load i18n %}
+
+{% block body %}
+ {% if resource.error %}
+ {% include "paypal/error.html" with error=resource.error %}
+ {% else %}
+ <p>{% trans "Płatność potwierdzona i zlecona do wykonania." %}</p>
+ {% endif %}
+{% endblock %}
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf.urls import url
+from . import views
+
+urlpatterns = (
+ url(r'^form/$', views.paypal_form, name='paypal_form'),
+ url(r'^return/$', views.paypal_return, name='paypal_return'),
+ url(r'^cancel/$', views.paypal_cancel, name='paypal_cancel'),
+)
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from decimal import Decimal
+
+from django.contrib.auth.decorators import login_required
+from django.http import Http404
+from django.http.response import HttpResponseRedirect
+from django.shortcuts import render
+
+from paypal.forms import PaypalSubscriptionForm
+from paypal.rest import execute_agreement, check_agreement, agreement_approval_url, PaypalError
+from paypal.models import BillingAgreement as BillingAgreementModel, BillingPlan
+
+
+@login_required
+def paypal_form(request):
+ if request.POST:
+ form = PaypalSubscriptionForm(data=request.POST)
+ if form.is_valid():
+ amount = form.cleaned_data['amount']
+ try:
+ approval_url = agreement_approval_url(amount)
+ except PaypalError as e:
+ return render(request, 'paypal/error_page.html', {'error': e.message})
+ return HttpResponseRedirect(approval_url)
+ else:
+ form = PaypalSubscriptionForm()
+ return render(request, 'paypal/form.html', {'form': form})
+
+
+@login_required
+def paypal_return(request):
+ token = request.GET.get('token')
+ if not token:
+ raise Http404
+ if not BillingAgreementModel.objects.filter(token=token):
+ resource = execute_agreement(token)
+ if resource.id:
+ amount = int(Decimal(resource.plan.payment_definitions[0].amount['value']))
+ plan = BillingPlan.objects.get(amount=amount)
+ active = check_agreement(resource.id)
+ BillingAgreementModel.objects.create(
+ agreement_id=resource.id, user=request.user, plan=plan, active=active, token=token)
+ return render(request, 'paypal/return.html', {'resource': resource})
+
+
+def paypal_cancel(request):
+ return render(request, 'paypal/cancel.html', {})
'newsletter',
'contact',
'isbn',
+ 'paypal',
]
GETPAID_BACKENDS = (
PIWIK_URL = ''
PIWIK_SITE_ID = 0
PIWIK_TOKEN = ''
+
+PAYPAL_CONFIG = {
+ 'mode': 'sandbox', # sandbox or live
+ 'client_id': '',
+ 'client_secret': '',
+}
url(r'^newsletter/', include('newsletter.urls')),
url(r'^formularz/', include('contact.urls')),
url(r'^isbn/', include('isbn.urls')),
+ url(r'^paypal/', include('paypal.urls')),
# Admin panel
url(r'^admin/catalogue/book/import$', catalogue.views.import_book, name='import_book'),