Commit 8e12fefa authored by Hugo LEVY-FALK's avatar Hugo LEVY-FALK

Documentation des paiements personnalisés

parent cee7d5be
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
"""
# Custom Payment methods
When creating an invoice with a classic payment method, the creation view calls
the `end_payment` method of the `Payment` object of the invoice. This method
checks for a payment method associated to the `Payment` and if nothing is
found, adds a message for payment confirmation and redirects the user towards
their profil page. This is fine for most of the payment method, but you might
want to define custom payment methods. As an example for negociating with an
other server for online payment or updating some fields in your models.
# Defining a custom payment method
To define a custom payment method, you can add a Python module to
`cotisations/payment_methods/`. This module should be organized like
a Django application.
As an example, if you want to add the payment method `foo`.
## Basic
The first thing to do is to create a `foo` Python module with a `models.py`.
```
payment_methods
├── foo
│ ├── __init__.py
│ └── models.py
├── forms.py
├── __init__.py
├── mixins.py
└── urls.py
```
Then, in `models.py` you could add a model like this :
```python
from django.db import models
from cotisations.models import Paiement
from cotisations.payment_methods.mixins import PaymentMethodMixin
# The `PaymentMethodMixin` defines the default `end_payment`
class FooPayment(PaymentMethodMixin, models.Model):
# This field is required, it is used by `Paiement` in order to
# determine if a payment method is associated to it.
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
editable=False
)
```
And in `__init__.py` :
```python
from . import models
NAME = "FOO" # Name displayed when you crate a payment type
PaymentMethod = models.FooPayment # You must define this alias
```
Then you just have to register your payment method in
`payment_methods/__init__.py` in the `PAYMENT_METHODS` list :
```
from . import ... # Some existing imports
from . import foo
PAYMENT_METHODS = [
# Some already registered payment methods...
foo
]
```
And... that's it, you can use your new payment method after running
`makemigrations` and `migrate`.
But this payment method is not really usefull, since it does noting !
## A payment method which does something
You have to redefine the `end_payment` method. Here is its prototype :
```python
def end_payment(self, invoice, request):
pass
```
With `invoice` the invoice being created and `request` the request which
created it. This method has to return an HttpResponse-like object.
## Additional views
You can add specific urls for your payment method like in any django app. To
register these urls, modify `payment_methods/urls.py`.
## Alter the `Paiement` object after creation
You can do that by adding a `alter_payment(self, payment)`
method to your model.
## Validate the creation field
You may want to perform some additionals verifications on the form
creating the payment. You can do that by adding a `valid_form(self, form)`
method to your model, where `form` is an instance of
`cotisations.payment_methods.forms.PaymentMethodForm`.
"""
from . import comnpay, cheque, balance, urls from . import comnpay, cheque, balance, urls
PAYMENT_METHODS = [ PAYMENT_METHODS = [
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
""" """
This module contains a method to pay online using user balance. This module contains a method to pay online using user balance.
""" """
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django.db import models from django.db import models
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
...@@ -16,6 +36,7 @@ class BalancePayment(PaymentMethodMixin, models.Model): ...@@ -16,6 +36,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
""" """
payment = models.OneToOneField( payment = models.OneToOneField(
Paiement, Paiement,
on_delete=models.CASCADE,
related_name='payment_method', related_name='payment_method',
editable=False editable=False
) )
...@@ -38,6 +59,9 @@ class BalancePayment(PaymentMethodMixin, models.Model): ...@@ -38,6 +59,9 @@ class BalancePayment(PaymentMethodMixin, models.Model):
) )
def end_payment(self, invoice, request): def end_payment(self, invoice, request):
"""Changes the user's balance to pay the invoice. If it is not
possible, shows an error and invalidates the invoice.
"""
user = invoice.user user = invoice.user
total_price = invoice.prix_total() total_price = invoice.prix_total()
if float(user.solde) - float(total_price) < self.minimum_balance: if float(user.solde) - float(total_price) < self.minimum_balance:
...@@ -58,6 +82,7 @@ class BalancePayment(PaymentMethodMixin, models.Model): ...@@ -58,6 +82,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
) )
def valid_form(self, form): def valid_form(self, form):
"""Checks that there is not already a balance payment method."""
p = Paiement.objects.filter(is_balance=True) p = Paiement.objects.filter(is_balance=True)
if len(p) > 0: if len(p) > 0:
form.add_error( form.add_error(
...@@ -66,4 +91,5 @@ class BalancePayment(PaymentMethodMixin, models.Model): ...@@ -66,4 +91,5 @@ class BalancePayment(PaymentMethodMixin, models.Model):
) )
def alter_payment(self, payment): def alter_payment(self, payment):
"""Register the payment as a balance payment."""
self.payment.is_balance = True self.payment.is_balance = True
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
""" """
This module contains a method to pay online using cheque. This module contains a method to pay online using cheque.
""" """
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _l
from re2o.mixins import FormRevMixin from re2o.mixins import FormRevMixin
from cotisations.models import Facture as Invoice from cotisations.models import Facture as Invoice
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django.db import models from django.db import models
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
...@@ -12,10 +32,15 @@ class ChequePayment(PaymentMethodMixin, models.Model): ...@@ -12,10 +32,15 @@ class ChequePayment(PaymentMethodMixin, models.Model):
""" """
payment = models.OneToOneField( payment = models.OneToOneField(
Paiement, Paiement,
on_delete=models.CASCADE,
related_name='payment_method', related_name='payment_method',
editable=False editable=False
) )
def end_payment(self, invoice, request): def end_payment(self, invoice, request):
"""Invalidates the invoice then redirect the user towards a view asking
for informations to add to the invoice before validating it.
"""
invoice.valid = False invoice.valid = False
invoice.save() invoice.save()
return redirect(reverse( return redirect(reverse(
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django.conf.urls import url from django.conf.urls import url
from . import views from . import views
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
"""Payment """Payment
Here are defined some views dedicated to cheque payement. Here are defined some views dedicated to cheque payement.
...@@ -17,6 +37,7 @@ from .forms import InvoiceForm ...@@ -17,6 +37,7 @@ from .forms import InvoiceForm
@login_required @login_required
def cheque(request, invoice_pk): def cheque(request, invoice_pk):
"""This view validate an invoice with the data from a cheque."""
invoice = get_object_or_404(Invoice, pk=invoice_pk) invoice = get_object_or_404(Invoice, pk=invoice_pk)
payment_method = getattr(invoice.paiement, 'payment_method', None) payment_method = getattr(invoice.paiement, 'payment_method', None)
if invoice.valid or not isinstance(payment_method, ChequePayment): if invoice.valid or not isinstance(payment_method, ChequePayment):
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
""" """
This module contains a method to pay online using comnpay. This module contains a method to pay online using comnpay.
""" """
......
...@@ -79,14 +79,22 @@ class AESEncryptedField(models.CharField): ...@@ -79,14 +79,22 @@ class AESEncryptedField(models.CharField):
def to_python(self, value): def to_python(self, value):
if value is None: if value is None:
return None return None
return decrypt(settings.AES_KEY, try:
binascii.a2b_base64(value)).decode('utf-8') return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
except Exception as e:
v = decrypt(settings.AES_KEY, binascii.a2b_base64(value))
raise ValueError(v)
def from_db_value(self, value, *args, **kwargs): def from_db_value(self, value, *args, **kwargs):
if value is None: if value is None:
return value return value
return decrypt(settings.AES_KEY, try:
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8') binascii.a2b_base64(value)).decode('utf-8')
except Exception as e:
v = decrypt(settings.AES_KEY, binascii.a2b_base64(value))
raise ValueError(v)
def get_prep_value(self, value): def get_prep_value(self, value):
if value is None: if value is None:
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django.db import models from django.db import models
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
...@@ -17,6 +37,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model): ...@@ -17,6 +37,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
""" """
payment = models.OneToOneField( payment = models.OneToOneField(
Paiement, Paiement,
on_delete=models.CASCADE,
related_name='payment_method', related_name='payment_method',
editable=False editable=False
) )
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
"""Payment """Payment
Here are defined some views dedicated to online payement. Here are the views needed by comnpay
""" """
from collections import OrderedDict from collections import OrderedDict
...@@ -23,7 +43,8 @@ from .models import ComnpayPayment ...@@ -23,7 +43,8 @@ from .models import ComnpayPayment
@login_required @login_required
def accept_payment(request, factureid): def accept_payment(request, factureid):
""" """
The view called when an online payment has been accepted. The view where the user is redirected when a comnpay payment has been
accepted.
""" """
invoice = get_object_or_404(Facture, id=factureid) invoice = get_object_or_404(Facture, id=factureid)
if invoice.valid: if invoice.valid:
...@@ -55,7 +76,8 @@ def accept_payment(request, factureid): ...@@ -55,7 +76,8 @@ def accept_payment(request, factureid):
@login_required @login_required
def refuse_payment(request): def refuse_payment(request):
""" """
The view called when an online payment has been refused. The view where the user is redirected when a comnpay payment has been
refused.
""" """
messages.error( messages.error(
request, request,
...@@ -72,7 +94,7 @@ def ipn(request): ...@@ -72,7 +94,7 @@ def ipn(request):
""" """
The view called by Comnpay server to validate the transaction. The view called by Comnpay server to validate the transaction.
Verify that we can firmly save the user's action and notify Verify that we can firmly save the user's action and notify
Comnpay with 400 response if not or with a 200 response if yes Comnpay with 400 response if not or with a 200 response if yes.
""" """
p = Transaction() p = Transaction()
order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', ) order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', )
......
# -*- mode: python; coding: utf-8 -*-
# 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 © 2018 Hugo Levy-Falk
#
# 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.
from django import forms from django import forms
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l from django.utils.translation import ugettext_lazy as _l
...@@ -6,6 +26,19 @@ from . import PAYMENT_METHODS ...@@ -6,6 +26,19 @@ from . import PAYMENT_METHODS
from cotisations.utils import find_payment_method from cotisations.utils import find_payment_method
def payment_method_factory(payment, *args, creation=True, **kwargs): def payment_method_factory(payment, *args, creation=True, **kwargs):
"""This function finds the right payment method form for a given payment.
If the payment has a payment method, returns a ModelForm of it. Else if
it is the creation of the payment, a `PaymentMethodForm`.
Else an empty form.
:param payment: The payment
:param *args: arguments passed to the form
:param creation: Should be True if you are creating the payment
:param **kwargs: passed to the form
:returns: A form
"""
payment_method = kwargs.pop('instance', find_payment_method(payment)) payment_method = kwargs.pop('instance', find_payment_method(payment))
if payment_method is not None: if payment_method is not None:
return forms.modelform_factory(type(payment_method), fields='__all__')( return forms.modelform_factory(type(payment_method), fields='__all__')(
...@@ -14,16 +47,14 @@ def payment_method_factory(payment, *args, creation=True, **kwargs): ...@@ -14,16 +47,14 @@ def payment_method_factory(payment, *args, creation=True, **kwargs):
**kwargs **kwargs
) )
elif creation: elif creation:
return PaymentMethodForm(payment_method, *args, **kwargs) return PaymentMethodForm(*args, **kwargs)
else: else:
return forms.Form() return forms.Form()