8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-22 11:23:10 +00:00

Rechargement via comnpay du solde.

This commit is contained in:
Hugo LEVY-FALK 2018-01-12 01:07:25 +01:00 committed by root
parent d9ebb266d5
commit f7657a2236
15 changed files with 343 additions and 6 deletions

View file

@ -279,3 +279,11 @@ class NewFactureSoldeForm(NewFactureForm):
raise forms.ValidationError("Le numéro de chèque et\ raise forms.ValidationError("Le numéro de chèque et\
la banque sont obligatoires.") la banque sont obligatoires.")
return cleaned_data return cleaned_data
class RechargeForm(Form):
value = forms.FloatField(
label='Valeur',
min_value=0.01,
validators = []
)

View file

@ -289,7 +289,7 @@ class Vente(models.Model):
if not user_request.has_perm('cotisations.change_vente'): if not user_request.has_perm('cotisations.change_vente'):
return False, u"Vous n'avez pas le droit d'éditer les ventes" return False, u"Vous n'avez pas le droit d'éditer les ventes"
elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]: elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]:
return False, u"Vous ne pouvez pas éditer les factures de cet user protégé" return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
elif not user_request.has_perm('cotisations.change_all_vente') and\ elif not user_request.has_perm('cotisations.change_all_vente') and\
(self.facture.control or not self.facture.valid): (self.facture.control or not self.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une vente\ return False, u"Vous n'avez pas le droit d'éditer une vente\

101
cotisations/payment.py Normal file
View file

@ -0,0 +1,101 @@
"""Payment
Here are defined some views dedicated to online payement.
"""
from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.views.decorators.csrf import csrf_exempt
from django.utils.datastructures import MultiValueDictKeyError
from django.http import HttpResponse, HttpResponseBadRequest
from collections import OrderedDict
from .models import Facture
from .payment_utils.comnpay import Payment as ComnpayPayment
@csrf_exempt
@login_required
def accept_payment(request, factureid):
facture = get_object_or_404(Facture, id=factureid)
messages.success(
request,
"Le paiement de {} € a été accepté.".format(facture.prix())
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
@csrf_exempt
@login_required
def refuse_payment(request):
messages.error(
request,
"Le paiement a été refusé."
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
@csrf_exempt
def ipn(request):
p = ComnpayPayment()
order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', )
try:
data = OrderedDict([(f, request.POST[f]) for f in order])
except MultiValueDictKeyError:
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
if not p.validSec(data, "DEMO"):
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
result = True if (request.POST['result'] == 'OK') else False
idTpe = request.POST['idTpe']
idTransaction = request.POST['idTransaction']
# On vérifie que le paiement nous est destiné
if not idTpe == "DEMO":
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
try:
factureid = int(idTransaction)
except ValueError:
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
facture = get_object_or_404(Facture, id=factureid)
# On vérifie que le paiement est valide
if not result:
# Le paiement a échoué : on effectue les actions nécessaires (On indique qu'elle a échoué)
facture.delete()
# On notifie au serveur ComNPay qu'on a reçu les données pour traitement
return HttpResponse("HTTP/1.1 200 OK")
facture.valid = True
facture.save()
# A nouveau, on notifie au serveur qu'on a bien traité les données
return HttpResponse("HTTP/1.0 200 OK")
def comnpay(facture, host):
p = ComnpayPayment(
"DEMO",
"DEMO",
'https://' + host + reverse('cotisations:accept_payment', kwargs={'factureid':facture.id}),
'https://' + host + reverse('cotisations:refuse_payment'),
'https://' + host + reverse('cotisations:ipn'),
"",
"D"
)
r = {
'action' : 'https://secure.homologation.comnpay.com',
'method' : 'POST',
'content' : p.buildSecretHTML("Rechargement du solde", facture.prix(), idTransaction=str(facture.id)),
'amount' : facture.prix,
}
return r
PAYMENT_SYSTEM = {
'COMNPAY' : comnpay,
'NONE' : None
}

View file

View file

@ -0,0 +1,68 @@
import time
from random import randrange
import base64
import hashlib
from collections import OrderedDict
from itertools import chain
class Payment():
vad_number = ""
secret_key = ""
urlRetourOK = ""
urlRetourNOK = ""
urlIPN = ""
source = ""
typeTr = "D"
def __init__(self, vad_number = "", secret_key = "", urlRetourOK = "", urlRetourNOK = "", urlIPN = "", source="", typeTr="D"):
self.vad_number = vad_number
self.secret_key = secret_key
self.urlRetourOK = urlRetourOK
self.urlRetourNOK = urlRetourNOK
self.urlIPN = urlIPN
self.source = source
self.typeTr = typeTr
def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""):
if idTransaction == "":
self.idTransaction = str(time.time())+self.vad_number+str(randrange(999))
else:
self.idTransaction = idTransaction
array_tpe = OrderedDict(
montant= str(montant),
idTPE= self.vad_number,
idTransaction= self.idTransaction,
devise= "EUR",
lang= 'fr',
nom_produit= produit,
source= self.source,
urlRetourOK= self.urlRetourOK,
urlRetourNOK= self.urlRetourNOK,
typeTr= str(self.typeTr)
)
if self.urlIPN!="":
array_tpe['urlIPN'] = self.urlIPN
array_tpe['key'] = self.secret_key;
strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8'))
del array_tpe["key"]
array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest()
ret = ""
for key in array_tpe:
ret += '<input type="hidden" name="'+key+'" value="'+array_tpe[key]+'"/>'
return ret
def validSec(self, values, secret_key):
if "sec" in values:
sec = values['sec']
del values["sec"]
strWithKey = hashlib.sha512(base64.b64encode(bytes('|'.join(values.values()) +"|"+secret_key, 'utf-8'))).hexdigest()
return strWithKey.upper() == sec.upper()
else:
return False

View file

@ -0,0 +1,37 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load staticfiles%}
{% block title %}Rechargement du solde{% endblock %}
{% block content %}
<h3>Recharger de {{ amount }} €</h3>
<form class="form" method="{{ method }}" action="{{action}}">
{{ content | safe }}
{% bootstrap_button "Payer" button_type="submit" icon="piggy-bank" %}
</form>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load staticfiles%}
{% block title %}Rechargement du solde{% endblock %}
{% block content %}
<h3>Rechargement du solde</h3>
<p>Solde de l'utilisateur : {{ request.user.solde }} €</p>
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form rechargeform %}
{% bootstrap_button "Valider" button_type="submit" icon="piggy-bank" %}
</form>
{% endblock %}

View file

@ -115,5 +115,21 @@ urlpatterns = [
views.new_facture_solde, views.new_facture_solde,
name='new_facture_solde' name='new_facture_solde'
), ),
url(r'^recharge/$',
views.recharge,
name='recharge'
),
url(r'^payment/accept/(?P<factureid>[0-9]+)$',
payment.accept_payment,
name='accept_payment'
),
url(r'^payment/refuse/$',
payment.refuse_payment,
name='refuse_payment'
),
url(r'^payment/ipn/$',
payment.ipn,
name='ipn'
),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
] ]

View file

@ -37,6 +37,8 @@ from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.forms import modelformset_factory, formset_factory from django.forms import modelformset_factory, formset_factory
from django.utils import timezone from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables
from reversion import revisions as reversion from reversion import revisions as reversion
from reversion.models import Version from reversion.models import Version
# Import des models, forms et fonctions re2o # Import des models, forms et fonctions re2o
@ -72,6 +74,7 @@ from .forms import (
NewFactureSoldeForm, NewFactureSoldeForm,
RechargeForm RechargeForm
) )
from . import payment
from .tex import render_invoice from .tex import render_invoice
@ -682,3 +685,23 @@ def new_facture_solde(request, userid):
}, 'cotisations/new_facture_solde.html', request) }, 'cotisations/new_facture_solde.html', request)
@login_required
def recharge(request):
f = RechargeForm(request.POST or None)
if f.is_valid():
facture = Facture(user=request.user)
paiement, _created = Paiement.objects.get_or_create(moyen='Rechargement en ligne')
facture.paiement = paiement
facture.valid = False
facture.save()
v = Vente.objects.create(
facture=facture,
name='solde',
prix=f.cleaned_data['value'],
number=1,
)
v.save()
options, _created = AssoOption.objects.get_or_create()
content = payment.PAYMENT_SYSTEM[options.payment](facture, request.get_host())
return render(request, 'cotisations/payment.html', content)
return form({'rechargeform':f}, 'cotisations/recharge.html', request)

View file

@ -2153,3 +2153,4 @@ def srv_post_save(sender, **kwargs):
def text_post_delete(sender, **kwargs): def text_post_delete(sender, **kwargs):
"""Regeneration dns après modification d'un SRV""" """Regeneration dns après modification d'un SRV"""
regen('dns') regen('dns')

View file

@ -48,7 +48,7 @@ class EditOptionalUserForm(ModelForm):
téléphone' téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\ self.fields['user_solde'].label = 'Activation du solde pour\
les utilisateurs' les utilisateurs'
self.fields['max_recharge'].label = 'Rechargement max' self.fields['max_solde'].label = 'Solde maximum'
class EditOptionalMachineForm(ModelForm): class EditOptionalMachineForm(ModelForm):

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-11 10:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0028_auto_20180111_1129'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='payment',
field=models.CharField(choices=[('NONE', 'NONE'), ('COMNPAY', 'COMNPAY')], default='NONE', max_length=255),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-11 22:46
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0029_auto_20180111_1134'),
]
operations = [
migrations.RemoveField(
model_name='optionaluser',
name='max_recharge',
),
migrations.AddField(
model_name='optionaluser',
name='max_solde',
field=models.DecimalField(decimal_places=2, default=50, max_digits=5),
),
]

View file

@ -41,10 +41,10 @@ class OptionalUser(models.Model):
decimal_places=2, decimal_places=2,
default=0 default=0
) )
max_recharge = models.DecimalField( max_solde = models.DecimalField(
max_digits=5, max_digits=5,
decimal_places=2, decimal_places=2,
default=100 default=50
) )
gpg_fingerprint = models.BooleanField(default=True) gpg_fingerprint = models.BooleanField(default=True)
all_can_create = models.BooleanField( all_can_create = models.BooleanField(

View file

@ -55,8 +55,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>Creations d'users par tous</th> <th>Creations d'users par tous</th>
<td>{{ useroptions.all_can_create }}</td> <td>{{ useroptions.all_can_create }}</td>
{% if useroptions.user_solde %} {% if useroptions.user_solde %}
<th>Rechargement max</th> <th>Solde maximum</th>
<td>{{ useroptions.max_recharge }}</td> <td>{{ useroptions.max_solde }}</td>
{% endif %} {% endif %}
</tr> </tr>
</table> </table>