# 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 . 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}