combo/combo/apps/lingo/views.py

254 lines
9.2 KiB
Python
Raw Normal View History

# 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, ROUND_HALF_UP
import json
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
2015-03-05 17:02:52 +01:00
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
2015-09-04 10:41:57 +02:00
from django.views.generic import View, ListView, TemplateView
2015-03-05 17:02:52 +01:00
import eopayment
try:
from mellon.models import UserSAMLIdentifier
except ImportError:
UserSAMLIdentifier = None
2015-03-05 17:02:52 +01:00
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 get_amount(self, amount):
if isinstance(amount, list):
d = sum([Decimal(a) for a in amount])
else:
d = Decimal(amount)
return d.quantize(Decimal('0.01'), ROUND_HALF_UP)
def post(self, request, *args, **kwargs):
# XXX: check request signature
request_body = json.loads(self.request.body)
extra = request_body.get('extra', {})
item = BasketItem(amount=0)
if request_body.get('amount'):
item.amount += self.get_amount(request_body['amount'])
if extra.get('amount'):
item.amount += self.get_amount(extra['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
2015-03-05 17:02:52 +01:00
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 = ''
2015-03-05 17:02:52 +01:00
transaction = Transaction()
if request.user.is_authenticated():
transaction.user = request.user
else:
transaction.user = None
2015-03-05 17:02:52 +01:00
transaction.save()
transaction.regie = regie
2015-03-05 17:02:52 +01:00
transaction.items = items
transaction.remote_items = remote_items
transaction.status = 0
2015-03-05 17:02:52 +01:00
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()
2015-03-05 17:02:52 +01:00
if total_amount < regie.payment_min_amount:
return HttpResponseForbidden()
2015-03-05 17:02:52 +01:00
payment = eopayment.Payment(regie.service, regie.service_options)
return_url = request.build_absolute_uri(
reverse('lingo-callback', kwargs={'regie_pk': regie.id}))
2015-03-05 17:02:52 +01:00
(order_id, kind, data) = payment.request(total_amount,
email=request.user.email,
next_url=return_url)
2015-03-05 17:02:52 +01:00
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})
2015-03-05 17:02:52 +01:00
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}))
2015-09-03 16:03:44 +02:00
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
2015-09-04 10:41:57 +02:00
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}