This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
facturier/facturier/views.py

280 lines
11 KiB
Python

from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.views.generic import DetailView, ListView
from django.http import HttpResponse, HttpResponseForbidden
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.core.exceptions import ObjectDoesNotExist
from . import app_settings
import datetime
import eopayment
from .models import Regie, TransactionEvent
from cmsplugin_blurp.renderers.data_source import Data
import logging
logger = logging.getLogger(__name__)
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
class InvoiceView(DetailView):
model = Regie
http_method_names = [u'get']
def get_template_names(self):
return ('facturier/%s.html' % self.object.slug,
'facturier/%s.html' % self.object.service)
def get_object_and_invoice(self, **kwargs):
self.object = self.get_object()
self.invoice = self.object.get_invoice(kwargs['id'],
kwargs['hash'], self.request)
def get_context_data(self):
context = super(InvoiceView, self).get_context_data()
context['invoice'] = self.invoice
return context
def get(self, request, *args, **kwargs):
self.get_object_and_invoice(**kwargs)
context = self.get_context_data()
return self.render_to_response(context)
class InvoiceDownloadView(LoginRequiredMixin, InvoiceView):
def get(self, request, *args, **kwargs):
self.get_object_and_invoice(**kwargs)
# if invoice can't be downloaded, stop here
if not self.invoice.get('download_url'):
return HttpResponseForbidden()
context = self.get_context_data()
filename = app_settings.download_filename.format(**context)
mimetype = app_settings.download_filename_mimetype
return HttpResponse(open(filename, 'rb'), mimetype=mimetype)
class TransactionView(DetailView):
model = Regie
template_name = 'facturier/transaction.html'
http_method_names = [u'get', u'post']
def create_event(self, transaction_id, invoice_id, status,
response=False, message=None, details={}):
address=self.request.META.get('HTTP_X_REAL_IP') or \
self.request.META.get('HTTP_X_FORWARDED_FOR') or \
self.request.META.get('REMOTE_ADDR')
details['message'] = message
TransactionEvent.objects.create(invoice_id=invoice_id,
regie=self.object,
transaction_id=transaction_id,
address=address,
response=response,
status=status,
details=details)
def create_transaction(self, options, invoice, context, message=None):
p = eopayment.Payment(kind=self.object.service,
options=options)
transaction_id, kind, redirect_url = p.request(**invoice)
assert kind == eopayment.URL
self.create_event(transaction_id, context['invoice_id'], 'CREATED',
message=message)
return transaction_id, redirect_url
def handle_transaction(self, invoice, context):
"""
checks if a transaction exists, otherwise creates a new transaction.
"""
service_options = dict([(option.name, option.value) \
for option in self.object.serviceoption_set.all()])
try:
event = TransactionEvent.objects.filter(regie=self.object,
invoice_id=context['invoice_id']).latest()
except ObjectDoesNotExist:
# this is a new invoice : create a new transaction
transaction_id, redirect_url = self.create_transaction(service_options,
invoice, context, message='new')
context.update({'redirect_url': redirect_url})
else:
if event.status == 'PAID':
# a online payment took place = the user can't pay again
context['paid'] = True
context['paid_date'] = event.date
elif event.status == 'CREATED' and 'force' in self.request.GET:
# the user want to force a new transaction : cancel the current
# one and create a new one
self.create_event(event.transaction_id, context['invoice_id'],
status='CANCELED',
message='forced by user')
transaction_id, redirect_url = self.create_transaction(service_options,
invoice, context,
message='forced by user, replace %s' % event.transaction_id)
context.update({'redirect_url': redirect_url})
elif event.status == 'CREATED':
# there is a current transaction: propose to "force" a new transaction
context['force_new_transaction_allowed'] = True
context['last_transaction_date'] = event.date
else:
# the last transaction is closed (cancel, error, test, etc.):
# create a new one
transaction_id, redirect_url = self.create_transaction(service_options,
invoice, context,
message='recreated after %s of %s' % (event.status, event.transaction_id))
context.update({'redirect_url': redirect_url})
return context
def invoice_rest_request(self, data_source_url, context):
data = Data(data_source_url, RequestContext(self.request, context),
limit=None, refresh=None)
return data.update_content().get('data').get('invoice')
def get_context_data(self, **kwargs):
context = super(TransactionView, self).get_context_data(**kwargs)
invoice_id = self.kwargs['id']
invoice_hash = self.kwargs['hash']
slug = self.object.slug # (self.object is a "Regie")
# create an invoice :
# 1. create a base from request and regie options
invoice = {}
if hasattr(self.request.user, 'email'):
invoice['email'] = self.request.user.email
request_options = dict([(option.name, option.value) \
for option in self.object.requestoption_set.all()])
invoice.update(request_options)
# get invoice from the regie (webservice)
context.update({'invoice_id': invoice_id,
'invoice_hash': invoice_hash,
'slug': slug,
'paid': False,})
invoice_data = self.invoice_rest_request(self.object.get_url, context)
invoice.update(invoice_data)
# invoice can be overriden by request.GET parameters
if self.request.GET:
for k, v in self.request.GET.iteritems():
if k in invoice:
invoice[k] = v
# invoice is paid : stop now
if invoice.get('paid'):
context['paid'] = True
return context
next_url = reverse('transaction', args=(slug, invoice_id, invoice_hash))
if request_options.get('next_url_base'):
invoice['next_url'] = str('%s%s' % (request_options['next_url_base'] ,next_url))
else:
invoice['next_url'] = self.request.build_absolute_uri(next_url)
context.update({'details': invoice})
self.handle_transaction(invoice, context)
logger.debug('payment request: context %s' % context)
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
if context.get('redirect_url'):
return redirect(context['redirect_url'], permanent=False)
return self.render_to_response(context)
# don't use CSRF protection (for post)
@csrf_exempt
def dispatch(self, *args, **kwargs):
return super(TransactionView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
logger.debug('payment response: path %s' % request.path)
query_string = str(request.META['QUERY_STRING'])
if not query_string:
if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded':
query_string = str(request.body)
logger.debug('payment response: query_string %s', query_string)
self.object = self.get_object()
service_options = dict([(option.name, option.value) \
for option in self.object.serviceoption_set.all()])
invoice_id = self.kwargs['id']
invoice_hash = self.kwargs['hash']
logger.debug('payment response: invoice %s', invoice_id)
try:
event = TransactionEvent.objects.filter(regie=self.object,
invoice_id=invoice_id).latest()
logger.debug('payment response: found transaction %s', event.transaction_id)
except ObjectDoesNotExist:
logger.warn('payment response: no transaction for invoice %s', invoice_id)
return HttpResponse(status=404) # HTTP Not Found
if event.status != 'CREATED':
logger.warn('payment response: transaction status != CREATED')
return HttpResponse(status=410) # HTTP Gone
backend = eopayment.Payment(kind=self.object.service, options=service_options)
response = backend.response(query_string)
logger.debug('payment response: response %s', response)
if response.result == eopayment.common.PAID:
status = 'PAID'
else:
status = 'ERROR'
if response.test:
status = 'TEST_%s' % status
context = {
'invoice_id': invoice_id,
'invoice_hash': invoice_hash,
'status': status
}
logger.debug('payment response: close transaction %s' % event.transaction_id)
self.create_event(event.transaction_id, invoice_id, status,
response=True, message='response', details=response.__dict__)
update_url = self.object.get_invoice_update_url(context)
logger.debug('payment response: update invoice PAID %s -- context %s' %
(update_url, context))
self.invoice_rest_request(update_url, context)
# ok !
logger.debug('payment response: OK invoice %s', invoice_id)
return HttpResponse(status=200)
class TransactionResponseListView(ListView):
content_type = 'text/csv'
def get_regie(self):
self.regie = Regie.objects.filter(slug=self.kwargs['regie']).get()
def get_template_names(self):
return ('facturier/response-%s.csv' % self.regie.slug,
'facturier/response-%s.csv' % self.regie.service)
def get_queryset(self):
days = int(self.kwargs.get('days', 5))
day_start = datetime.datetime.now() - datetime.timedelta(days)
return TransactionEvent.objects.filter(regie=self.regie,
response=True,
date__gte=day_start)
def get(self, request, *args, **kwargs):
if request.GET.get('key') != app_settings.responses_csv_key:
return HttpResponseForbidden('forbidden')
self.get_regie()
return super(TransactionResponseListView, self).\
get(request, *args, **kwargs)