Projet

Général

Profil

0001-add-Saga-payment-method-46502.patch

Benjamin Dauvergne, 09 septembre 2020 22:49

Télécharger (9,97 ko)

Voir les différences:

Subject: [PATCH] add Saga payment method (#46502)

 eopayment/__init__.py |   5 +-
 eopayment/saga.py     | 246 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 249 insertions(+), 2 deletions(-)
 create mode 100644 eopayment/saga.py
eopayment/__init__.py
31 31
__all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS', 'SYSTEMPAY', 'SPPLUS',
32 32
           'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED', 'PAID',
33 33
           'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'WAITING',
34
           'get_backends', 'PAYFIP_WS']
34
           'get_backends', 'PAYFIP_WS', 'SAGA']
35 35

  
36 36
if six.PY3:
37 37
    __all__.extend(['KEYWARE', 'MOLLIE'])
......
48 48
PAYFIP_WS = 'payfip_ws'
49 49
KEYWARE = 'keyware'
50 50
MOLLIE = 'mollie'
51
SAGA = 'saga'
51 52

  
52 53
logger = logging.getLogger(__name__)
53 54

  
......
58 59
    return module.Payment
59 60

  
60 61
__BACKENDS = [DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN,
61
              TIPI, PAYFIP_WS, KEYWARE, MOLLIE]
62
              TIPI, PAYFIP_WS, KEYWARE, MOLLIE, SAGA]
62 63

  
63 64

  
64 65
def get_backends():
eopayment/saga.py
1
# -*- coding: utf-8 -*-
2
# eopayment - online payment library
3
# Copyright (C) 2011-2020 Entr'ouvert
4
#
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU Affero General Public License as published
7
# by the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a copy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

  
18
from __future__ import unicode_literals, print_function
19

  
20
import functools
21

  
22
from six.moves.urllib.parse import urljoin, parse_qs
23

  
24
import lxml.etree as ET
25
import zeep
26
import zeep.exceptions
27

  
28
from .common import (PaymentException, PaymentCommon, ResponseError, URL, PAID,
29
                     DENIED, CANCELLED, ERROR, PaymentResponse)
30

  
31

  
32
class SagaError(PaymentException):
33
    pass
34

  
35

  
36
class Saga(object):
37
    def __init__(self, wsdl_url, service_url=None, zeep_client_kwargs=None):
38
        self.wsdl_url = wsdl_url
39
        self.client = zeep.Client(wsdl_url, **(zeep_client_kwargs or {}))
40
        # distribued WSDL is wrong :/
41
        if service_url:
42
            self.client.service._binding_options['address'] = service_url
43

  
44
    def soap_call(self, operation, content_tag, **kwargs):
45
        content = getattr(self.client.service, operation)(**kwargs)
46
        if 'ISO-8859-1' in content:
47
            encoded_content = content.encode('latin1')
48
        else:
49
            encoded_content = content.encode('utf-8')
50
        try:
51
            tree = ET.fromstring(encoded_content)
52
        except Exception:
53
            raise SagaError('Invalid SAGA response "%s"' % content[:1024])
54
        if tree.tag == 'erreur':
55
            raise SagaError(tree.text)
56
        if tree.tag != content_tag:
57
            raise SagaError('Invalid SAGA response "%s"' % content[:1024])
58
        return tree
59

  
60
    def transaction(self, num_service, id_tiers, compte, lib_ecriture, montant,
61
                    urlretour_asynchrone, email, urlretour_synchrone):
62
        tree = self.soap_call(
63
            'Transaction',
64
            'url',
65
            num_service=num_service,
66
            id_tiers=id_tiers,
67
            compte=compte,
68
            lib_ecriture=lib_ecriture,
69
            montant=montant,
70
            urlretour_asynchrone=urlretour_asynchrone,
71
            email=email,
72
            urlretour_synchrone=urlretour_synchrone)
73
        # tree == <url>...</url>
74
        return tree.text
75

  
76
    def page_retour(self, operation, idop):
77
        tree = self.soap_call(
78
            operation,
79
            'ok',
80
            idop=idop)
81
        # tree == <ok id_tiers="A1"
82
        # etat="paye" email="albert,dupond@monsite.com" num_service="222222"
83
        # montant="100.00" compte="708"
84
        # lib_ecriture="Paiement pour M. Albert Dupondréservationsejourxxxx" />
85
        return tree.attrib
86

  
87
    def page_retour_synchrone(self, idop):
88
        return self.page_retou('PageRetourSynchrone', idop)
89

  
90
    def page_retour_asynchrone(self, idop):
91
        return self.page_retou('PageRetourAsynchrone', idop)
92

  
93

  
94
class Payment(PaymentCommon):
95
    description = {
96
        'caption': 'Système de paiement Saga / SAGA',
97
        'parameters': [
98
            {
99
                'name': 'base_url',
100
                'caption': 'URL de base du WSDL',
101
                'help_text': 'Sans la partie /paiement_internet_ws_ministere?wsdl',
102
                'required': True,
103
            },
104
            {
105
                'name': 'num_service',
106
                'caption': 'Numéro du service',
107
                'required': True,
108
            },
109
            {
110
                'name': 'compte',
111
                'caption': 'Compte de recettes',
112
                'required': True,
113
            },
114
            {
115
                'name': 'normal_return_url',
116
                'caption': 'Normal return URL',
117
                'default': '',
118
                'required': True,
119
            },
120
            {
121
                'name': 'automatic_return_url',
122
                'caption': 'Automatic return URL (ignored, must be set in Ogone backoffice)',
123
                'required': False,
124
            },
125
        ]
126
    }
127

  
128
    @property
129
    def saga(self):
130
        return Saga(
131
            wsdl_url=urljoin(self.base_url, 'paiement_internet_ws_ministere?wsdl'))
132

  
133
    def request(self, amount, email, description, **kwargs):
134
        num_service = self.num_service
135
        id_tiers = '-1'
136
        compte = self.compte
137
        lib_ecriture = description
138
        montant = self.clean_amount(amount, max_amount=100000, cents=False)
139
        urlretour_synchrone = self.normal_return_url
140
        # email
141
        urlretour_asynchrone = self.automatic_return_url
142

  
143
        url = self.saga.transaction(
144
            num_service=num_service,
145
            id_tiers=id_tiers,
146
            compte=compte,
147
            lib_ecriture=lib_ecriture,
148
            montant=montant,
149
            urlretour_asynchrone=urlretour_asynchrone,
150
            email=email,
151
            urlretour_synchrone=urlretour_synchrone)
152

  
153
        try:
154
            idop = parse_qs(url.split('?', 1)[-1])['idop'][0]
155
        except Exception:
156
            raise SagaError('Invalid payment URL, no idop: %s' % url)
157

  
158
        return str(idop), URL, url
159

  
160
    def response(self, query_string, **kwargs):
161
        fields = parse_qs(query_string, True)
162
        idop = (fields.get('idop') or [None])[0]
163

  
164
        if not idop:
165
            raise ResponseError('missing idop parameter in query')
166

  
167
        redirect = kwargs.get('redirect', False)
168

  
169
        if redirect:
170
            response = self.saga.page_retour_synchrone(idop=idop)
171
        else:
172
            response = self.saga.page_retour_asynchrone(idop=idop)
173

  
174
        etat = response['etat']
175
        if etat == 'paye':
176
            result = PAID
177
            bank_status = 'paid'
178
        elif etat == 'refus':
179
            result = DENIED
180
            bank_status = 'refused'
181
        elif etat == 'abandon':
182
            result = CANCELLED
183
            bank_status = 'cancelled'
184
        else:
185
            result = ERROR
186
            bank_status = 'unknown result code: etat=%r' % etat
187

  
188
        return PaymentResponse(
189
            result=result,
190
            bank_status=bank_status,
191
            signed=True,
192
            bank_data=response,
193
            order_id=idop,
194
            transaction_id=idop,
195
            test=False)
196

  
197

  
198
if __name__ == '__main__':
199
    import click
200

  
201
    def show_payfip_error(func):
202
        @functools.wraps(func)
203
        def f(*args, **kwargs):
204
            try:
205
                return func(*args, **kwargs)
206
            except SagaError as e:
207
                click.echo(click.style('SAGA ERROR : %s' % e, fg='red'))
208
        return f
209

  
210
    @click.group()
211
    @click.option('--wsdl-url')
212
    @click.option('--service-url', default=None)
213
    @click.pass_context
214
    def main(ctx, wsdl_url, service_url):
215
        import logging
216
        logging.basicConfig(level=logging.INFO)
217
        # hide warning from zeep
218
        logging.getLogger('zeep.wsdl.bindings.soap').level = logging.ERROR
219

  
220
        ctx.obj = Saga(wsdl_url=wsdl_url, service_url=service_url)
221

  
222
    @main.command()
223
    @click.option('--num-service', type=str, required=True)
224
    @click.option('--id-tiers', type=str, required=True)
225
    @click.option('--compte', type=str, required=True)
226
    @click.option('--lib-ecriture', type=str, required=True)
227
    @click.option('--montant', type=str, required=True)
228
    @click.option('--urlretour-asynchrone', type=str, required=True)
229
    @click.option('--email', type=str, required=True)
230
    @click.option('--urlretour-synchrone', type=str, required=True)
231
    @click.pass_obj
232
    @show_payfip_error
233
    def transaction(saga, num_service, id_tiers, compte, lib_ecriture, montant,
234
                    urlretour_asynchrone, email, urlretour_synchrone):
235
        url = saga.transaction(
236
            num_service=num_service,
237
            id_tiers=id_tiers,
238
            compte=compte,
239
            lib_ecriture=lib_ecriture,
240
            montant=montant,
241
            urlretour_asynchrone=urlretour_asynchrone,
242
            email=email,
243
            urlretour_synchrone=urlretour_synchrone)
244
        print('url:', url)
245

  
246
    main()
0
-