8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-27 07:02:26 +00:00

Documentation des paiements personnalisés

This commit is contained in:
Hugo LEVY-FALK 2018-07-08 00:01:06 +02:00
parent cee7d5be7f
commit 8e12fefa2c
16 changed files with 479 additions and 27 deletions

View file

@ -1,3 +1,132 @@
# -*- 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
PAYMENT_METHODS = [

View file

@ -1,3 +1,23 @@
# -*- 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.
"""

View file

@ -1,3 +1,23 @@
# -*- 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.shortcuts import redirect
from django.urls import reverse
@ -16,6 +36,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
"""
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
editable=False
)
@ -38,6 +59,9 @@ class BalancePayment(PaymentMethodMixin, models.Model):
)
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
total_price = invoice.prix_total()
if float(user.solde) - float(total_price) < self.minimum_balance:
@ -58,6 +82,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
)
def valid_form(self, form):
"""Checks that there is not already a balance payment method."""
p = Paiement.objects.filter(is_balance=True)
if len(p) > 0:
form.add_error(
@ -66,4 +91,5 @@ class BalancePayment(PaymentMethodMixin, models.Model):
)
def alter_payment(self, payment):
"""Register the payment as a balance payment."""
self.payment.is_balance = True

View file

@ -1,3 +1,23 @@
# -*- 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.
"""

View file

@ -1,5 +1,24 @@
# -*- 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.utils.translation import ugettext_lazy as _l
from re2o.mixins import FormRevMixin
from cotisations.models import Facture as Invoice

View file

@ -1,3 +1,23 @@
# -*- 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.shortcuts import redirect
from django.urls import reverse
@ -12,10 +32,15 @@ class ChequePayment(PaymentMethodMixin, models.Model):
"""
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
editable=False
)
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.save()
return redirect(reverse(

View file

@ -1,3 +1,23 @@
# -*- 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 . import views

View file

@ -1,3 +1,23 @@
# -*- 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
Here are defined some views dedicated to cheque payement.
@ -17,6 +37,7 @@ from .forms import InvoiceForm
@login_required
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)
payment_method = getattr(invoice.paiement, 'payment_method', None)
if invoice.valid or not isinstance(payment_method, ChequePayment):

View file

@ -1,3 +1,23 @@
# -*- 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.
"""

View file

@ -79,14 +79,22 @@ class AESEncryptedField(models.CharField):
def to_python(self, value):
if value is None:
return None
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
try:
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):
if value is None:
return value
return decrypt(settings.AES_KEY,
try:
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 get_prep_value(self, value):
if value is None:

View file

@ -1,3 +1,23 @@
# -*- 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.shortcuts import render
from django.urls import reverse
@ -17,6 +37,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
"""
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
editable=False
)

View file

@ -1,6 +1,26 @@
# -*- 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
Here are defined some views dedicated to online payement.
Here are the views needed by comnpay
"""
from collections import OrderedDict
@ -23,7 +43,8 @@ from .models import ComnpayPayment
@login_required
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)
if invoice.valid:
@ -55,7 +76,8 @@ def accept_payment(request, factureid):
@login_required
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(
request,
@ -72,7 +94,7 @@ def ipn(request):
"""
The view called by Comnpay server to validate the transaction.
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()
order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', )

View file

@ -1,3 +1,23 @@
# -*- 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.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l
@ -6,6 +26,19 @@ from . import PAYMENT_METHODS
from cotisations.utils import find_payment_method
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))
if payment_method is not None:
return forms.modelform_factory(type(payment_method), fields='__all__')(
@ -14,16 +47,14 @@ def payment_method_factory(payment, *args, creation=True, **kwargs):
**kwargs
)
elif creation:
return PaymentMethodForm(payment_method, *args, **kwargs)
return PaymentMethodForm(*args, **kwargs)
else:
return forms.Form()
class PaymentMethodForm(forms.Form):
"""A special form which allows you to add a payment method to a `Payment`
objects if it hasn't one yet, or to edit the existing payment method.
To do so it replaces itself with a `modelform_factory`.
object.
"""
payment_method = forms.ChoiceField(
@ -35,23 +66,24 @@ class PaymentMethodForm(forms.Form):
required=False
)
def __init__(self, payment_method, *args, **kwargs):
def __init__(self, *args, **kwargs):
super(PaymentMethodForm, self).__init__(*args, **kwargs)
if payment_method is None:
prefix = kwargs.get('prefix', None)
self.fields['payment_method'].choices = [(i,p.NAME) for (i,p) in enumerate(PAYMENT_METHODS)]
self.fields['payment_method'].choices.insert(0, ('', _l('no')))
self.fields['payment_method'].widget.attrs = {
'id': 'paymentMethodSelect'
}
self.templates = [
forms.modelform_factory(p.PaymentMethod, fields='__all__')(prefix=prefix)
for p in PAYMENT_METHODS
]
else:
self.fields = {}
prefix = kwargs.get('prefix', None)
self.fields['payment_method'].choices = [(i,p.NAME) for (i,p) in enumerate(PAYMENT_METHODS)]
self.fields['payment_method'].choices.insert(0, ('', _l('no')))
self.fields['payment_method'].widget.attrs = {
'id': 'paymentMethodSelect'
}
self.templates = [
forms.modelform_factory(p.PaymentMethod, fields='__all__')(prefix=prefix)
for p in PAYMENT_METHODS
]
def clean(self):
"""A classic `clean` method, except that it replaces
`self.payment_method` by the payment method object if one has been
found. Tries to call `payment_method.valid_form` if it exists.
"""
super(PaymentMethodForm, self).clean()
choice = self.cleaned_data['payment_method']
if choice=='':
@ -67,6 +99,10 @@ class PaymentMethodForm(forms.Form):
def save(self, payment, *args, **kwargs):
"""Saves the payment method.
Tries to call `payment_method.alter_payment` if it exists.
"""
commit = kwargs.pop('commit', True)
self.payment_method.payment = payment
if hasattr(self.payment_method, 'alter_payment'):

View file

@ -1,5 +1,27 @@
# -*- 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.
class PaymentMethodMixin:
"""The base class for payment models. They should inherit from this."""
"""A simple mixin to avoid redefining end_payment if you don't need to"""
def end_payment(self, invoice, request):
"""Redefine this method in order to get a different ending to the

View file

@ -1,3 +1,23 @@
# -*- 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 include, url
from . import comnpay, cheque

View file

@ -1,4 +1,27 @@
# -*- 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.
def find_payment_method(payment):
"""Finds the payment method associated to the payment if it exists."""
from cotisations.payment_methods import PAYMENT_METHODS
for method in PAYMENT_METHODS:
try: