diff --git a/combo_plugin_nanterre/__init__.py b/combo_plugin_nanterre/__init__.py new file mode 100644 index 0000000..23e556a --- /dev/null +++ b/combo_plugin_nanterre/__init__.py @@ -0,0 +1,33 @@ +# combo-plugin-nanterre - Combo Nanterre plugin +# Copyright (C) 2017 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 . + +import django.apps +from django.utils.translation import ugettext_lazy as _ + + +class Plugin(object): + def get_apps(self): + return [__name__] + +class AppConfig(django.apps.AppConfig): + name = __name__ + verbose_name = _('Nanterre extension') + + def get_after_urls(self): + from . import urls + return urls.urlpatterns + +default_app_config = __name__ + '.AppConfig' diff --git a/combo_plugin_nanterre/urls.py b/combo_plugin_nanterre/urls.py new file mode 100644 index 0000000..d1597de --- /dev/null +++ b/combo_plugin_nanterre/urls.py @@ -0,0 +1,28 @@ +# combo-plugin-nanterre - Combo Nanterre plugin +# Copyright (C) 2017 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 django.conf.urls import url + +from .views import saga_transaction, saga_retour_asynchrone, saga_retour_synchrone + +urlpatterns = [ + url('^_plugin/nanterre/saga-transaction/*$', saga_transaction, + name='nanterre-saga-transaction'), + url('^_plugin/nanterre/saga-retour-asynchrone/*$', saga_retour_asynchrone, + name='nanterre-saga-retour-asynchrone'), + url('^_plugin/nanterre/saga-retour-synchrone/*$', saga_retour_synchrone, + name='nanterre-saga-retour-synchrone'), +] diff --git a/combo_plugin_nanterre/views.py b/combo_plugin_nanterre/views.py new file mode 100644 index 0000000..a79a1c5 --- /dev/null +++ b/combo_plugin_nanterre/views.py @@ -0,0 +1,163 @@ +# combo-plugin-nanterre - Combo Nanterre plugin +# -*- coding: utf-8 -*- +# Copyright (C) 2017 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 . + +import json +import logging + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.template import RequestContext +from django.views.decorators.csrf import csrf_exempt + +from combo.utils import requests, get_templated_url + + +ERROR_MESSAGE = ("Le système de paiement n'est pas disponible, " + "veuillez essayer ultérieurement.") +MESSAGE_BY_STATE = { + 'paye': (messages.SUCCESS, "Le paiement a bien été effectué."), + 'abandon': (messages.WARNING, "Le paiement a été annulé."), + 'refus': (messages.WARNING, "Le paiement a été refusé."), +} + + +def rsu_post(request, endpoint, payload): + timeout = getattr(settings, 'NANTERRE_PAYMENT_TIMEOUT', 10) + url = '[zoo_url]rsu/' + endpoint + context = RequestContext(request, {'request': request}) + url = get_templated_url(url, context=context) + return requests.post(url, json=payload, timeout=timeout).json() + + +@csrf_exempt +@login_required +def saga_transaction(request): + logger = logging.getLogger('combo_plugin_nanterre.saga_transaction') + num_factures = request.POST.getlist('num_factures') + email = request.POST.get('email') or request.user.email + error_url = request.POST.get('error_url') or '/' + + urlretour_asynchrone = request.build_absolute_uri( + reverse('nanterre-saga-retour-asynchrone')).rstrip('/') + urlretour_synchrone = request.build_absolute_uri( + reverse('nanterre-saga-retour-synchrone')).rstrip('/') + + payload = { + 'num_factures': num_factures, + 'email': email, + 'urlretour_asynchrone': urlretour_asynchrone, + 'urlretour_synchrone': urlretour_synchrone, + } + try: + saga = rsu_post(request, 'saga/[user_nameid]/transaction/', payload) + except: + logger.error('[rsu/saga] failed to create transaction ' + 'for num_factures=%s', num_factures) + messages.error(request, ERROR_MESSAGE) + return HttpResponseRedirect(error_url) + if not isinstance(saga, dict) or not saga.get('data', {}).get('url'): + logger.error('[rsu/saga] failed to create transaction ' + 'for num_factures=%s, received bad response=%r', + num_factures, saga) + messages.error(request, ERROR_MESSAGE) + return HttpResponseRedirect(error_url) + if saga.get('errors'): + logger.warning('[rsu/saga] failed to create transaction ' + 'for num_factures=%s, errors=%r', + num_factures, saga['errors']) + for error in saga['errors']: + messages.error(request, error) + return HttpResponseRedirect(error_url) + if saga.get('err') != 0: + logger.warning('[rsu/saga] failed to create transaction ' + 'for num_factures=%s, unknown error, code=%r', + num_factures, saga['err']) + messages.error(request, ERROR_MESSAGE) + return HttpResponseRedirect(error_url) + + # finally, response seems good! redirect to payment system URL + logger.info('[rsu/saga] new transaction created ' + 'for num_factures=%s, redirect to %s', + num_factures, saga['data']['url']) + return HttpResponseRedirect(saga['data']['url']) + + +@csrf_exempt +@login_required +def saga_retour_synchrone(request): + logger = logging.getLogger('combo_plugin_nanterre.saga_retour_synchrone') + next_url = getattr(settings, 'NANTERRE_PAYMENT_RESULT_PAGE', '/') + idop = request.GET.get('idop') + payload = {'idop': idop} + try: + saga = rsu_post(request, 'saga/retour-synchrone/', payload) + except: + logger.error('[rsu/saga] retour-synchrone: cannot post idop=%s', idop) + messages.error(request, ERROR_MESSAGE) + return HttpResponseRedirect(next_url) + + # add a result message and redirect + if (isinstance(saga, dict) and saga.get('err') == 0 + and saga.get('data', {}).get('etat')): + etat = saga['data']['etat'] + if etat in MESSAGE_BY_STATE: + logger.info('[rsu/saga] retour-synchrone: idop=%s etat=%s', + idop, etat) + messages.add_message(request, *MESSAGE_BY_STATE[etat]) + else: + logger.error('[rsu/saga] retour-synchrone: idop=%s ' + 'receive unknown etat=%s', idop, etat) + messages.error(request, ERROR_MESSAGE) + else: + logger.error('[rsu/saga] retour-synchrone: idop=%s ' + 'receive bad response=%r', idop, saga) + messages.error(request, ERROR_MESSAGE) + return HttpResponseRedirect(next_url) + + +@csrf_exempt +def saga_retour_asynchrone(request): + logger = logging.getLogger('combo_plugin_nanterre.saga_retour_asynchrone') + idop = request.GET.get('idop') + payload = {'idop': idop} + err = 0 + try: + saga = rsu_post(request, 'saga/retour-asynchrone/', payload) + except: + err = 1 + logger.error('[rsu/saga] retour-asynchrone: cannot post idop=%s', idop) + else: + if (isinstance(saga, dict) and saga.get('err') == 0 + and saga.get('data', {}).get('etat')): + etat = saga['data']['etat'] + if etat in MESSAGE_BY_STATE: + logger.info('[rsu/saga] retour-asynchrone: idop=%s etat=%s', + idop, etat) + else: + err = 1 + logger.error('[rsu/saga] retour-asynchrone: idop=%s ' + 'receive unknown etat=%s', idop, etat) + else: + err = 1 + logger.error('[rsu/saga] retour-asynchrone: idop=%s ' + 'receive bad response=%r', idop, saga) + response = HttpResponse(content_type='application/json') + response.write(json.dumps({'err': err})) + return response