combo/combo/apps/lingo/views.py

241 lines
8.8 KiB
Python

# lingo - basket and payment system
# Copyright (C) 2015 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import json
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseForbidden
from django.template.response import TemplateResponse
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View, ListView, TemplateView
import eopayment
try:
from mellon.models import UserSAMLIdentifier
except ImportError:
UserSAMLIdentifier = None
from .models import Regie, BasketItem, Transaction
class RegiesApiView(ListView):
model = Regie
def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='application/json')
data = {'data': [x.as_api_dict() for x in self.get_queryset()]}
json_str = json.dumps(data)
if 'jsonpCallback' in request.GET:
json_str = '%s(%s);' % (request.GET['jsonpCallback'], json_str)
response.write(json_str)
return response
class AddBasketItemApiView(View):
http_method_names = ['post', 'options']
@csrf_exempt
def dispatch(self, *args, **kwargs):
return super(AddBasketItemApiView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
# XXX: check request signature
request_body = json.loads(self.request.body)
item = BasketItem()
item.amount = sum([Decimal(x) for x in request.GET.getlist('amount')])
try:
if request.GET.get('NameId'):
if UserSAMLIdentifier is None:
raise Exception('missing mellon?')
try:
user = UserSAMLIdentifier.objects.get(name_id=request.GET.get('NameId')).user
except UserSAMLIdentifier.DoesNotExist:
raise Exception('unknown name id')
elif request.GET.get('email'):
user = User.objects.get(email=request.GET.get('email'))
else:
raise Exception('no user specified')
except User.DoesNotExist:
raise Exception('unknown user')
item.user = user
if request.GET.get('regie_id'):
item.regie = Regie.objects.get(id=request.GET.get('regie_id'))
else:
# if there's no regie specified, use the first one we get from the
# database...
item.regie = Regie.objects.all()[0]
item.subject = request_body.get('display_name')
item.source_url = request_body.get('url')
item.save()
response = HttpResponse(content_type='application/json')
response.write(json.dumps({'result': 'success'}))
return response
class PayView(View):
def post(self, request, *args, **kwargs):
regie_id = request.POST.get('regie')
if regie_id:
regie = Regie.objects.get(pk=regie_id)
if regie.is_remote():
items = []
remote_items_data = []
# get all items data from regie webservice
for item in request.POST.getlist('item'):
remote_items_data.append(regie.get_item(request, item))
remote_items = ','.join([x.id for x in remote_items_data])
else:
items = BasketItem.objects.filter(id__in=request.POST.getlist('item'), regie=regie)
remote_items = ''
else:
items = BasketItem.objects.filter(id__in=request.POST.getlist('item'))
# XXX: check all items are going to the same regie
regie = items[0].regie
remote_items = ''
transaction = Transaction()
if request.user.is_authenticated():
transaction.user = request.user
else:
transaction.user = None
transaction.save()
transaction.regie = regie
transaction.items = items
transaction.remote_items = remote_items
transaction.status = 0
if remote_items:
total_amount = sum([x.amount for x in remote_items_data])
else:
total_amount = sum([x.amount for x in items])
transaction.amount = total_amount
transaction.save()
if total_amount < regie.payment_min_amount:
return HttpResponseForbidden()
payment = eopayment.Payment(regie.service, regie.service_options)
return_url = request.build_absolute_uri(
reverse('lingo-callback', kwargs={'regie_pk': regie.id}))
(order_id, kind, data) = payment.request(total_amount,
email=request.user.email,
next_url=return_url)
transaction.order_id = order_id
transaction.save()
# XXX: mark basket items as being processed (?)
if kind == eopayment.URL:
return HttpResponseRedirect(data)
elif kind == eopayment.FORM:
return TemplateResponse(request, 'lingo/payment_form.html', {'form': data})
raise NotImplementedError()
class CallbackView(View):
def get(self, request, *args, **kwargs):
regie = Regie.objects.get(id=kwargs.get('regie_pk'))
payment = eopayment.Payment(regie.service, regie.service_options)
payment_response = payment.response(request.environ['QUERY_STRING'])
if not payment_response.result == eopayment.CANCELLED:
# cancellation are not signed...
assert payment_response.signed is True
transaction = Transaction.objects.get(order_id=payment_response.order_id)
transaction.status = payment_response.result
transaction.bank_data = payment_response.bank_data
transaction.end_date = timezone.now()
transaction.save()
# check if transaction belongs to right regie
assert transaction.regie == regie
if payment_response.result != eopayment.PAID:
return HttpResponse()
for item in transaction.items.all():
item.payment_date = transaction.end_date
item.save()
try:
item.notify()
except:
# ignore errors, it will be retried later on if it fails
pass
if transaction.remote_items:
for item in transaction.remote_items.split(','):
regie.pay_item(request, item)
return HttpResponse()
class ReturnView(View):
def get(self, request, *args, **kwargs):
regie = Regie.objects.get(id=kwargs.get('regie_pk'))
payment = eopayment.Payment(regie.service, regie.service_options)
try:
payment_response = payment.response(request.environ['QUERY_STRING'])
except:
# if eopayment can't get response from query string redirect to
# homepage
return HttpResponseRedirect('/')
transaction = Transaction.objects.get(order_id=payment_response.order_id)
if transaction.items:
# redirect to first transaction local item view
return HttpResponseRedirect(reverse('view-item',
kwargs={'regie_id': regie.pk,
'item_id': transaction.items[0].id}))
if transaction.remote_items:
# redirect to first transaction remote item view
item = transaction.remote_items.split(',')[0]
return HttpResponseRedirect(reverse('view-item',
kwargs={'regie_id': regie.pk,
'item_id': item.id}))
class ItemDownloadView(View):
http_method_names = [u'get']
def get(self, request, *args, **kwargs):
regie = Regie.objects.get(pk=kwargs['regie_id'])
data = regie.download_item(request, kwargs['item_id'])
r = HttpResponse(data, content_type='application/pdf')
r['Content-Disposition'] = 'attachment; filename="%(item_id)s.pdf"' % kwargs
return r
class ItemView(TemplateView):
http_method_names = [u'get']
template_name = 'lingo/combo/item.html'
def get_context_data(self, **kwargs):
regie = Regie.objects.get(pk=kwargs['regie_id'])
item = regie.get_item(self.request, kwargs['item_id'])
return {'item': item, 'regie': regie}