Project

General

Profile

Download (11.2 KB) Statistics
| Branch: | Tag: | Revision:

root / extra / modules / payments.py @ b29b079c

1
import random
2
import string
3
import urllib
4
from datetime import datetime as dt
5

    
6
from decimal import Decimal
7

    
8
from quixote import redirect, get_request, get_session
9
from quixote.directory import Directory
10

    
11
if not set:
12
    from sets import Set as set
13

    
14
try:
15
    import eopayment
16
except ImportError:
17
    eopayment = None
18

    
19
from qommon.storage import StorableObject
20
from qommon.form import *
21
from qommon import errors, get_logger
22

    
23
from wcs.formdef import FormDef
24
from wcs.formdata import Evolution
25
from wcs.workflows import WorkflowStatusItem, register_item_class, \
26
        render_list_of_roles
27
from wcs.roles import logged_users_role, get_user_roles
28

    
29
def is_payment_supported():
30
    if not eopayment:
31
        return False
32
    return get_cfg('aq-permissions', {}).get('payments', None) is not None
33

    
34

    
35
class Regie(StorableObject):
36
    _names = 'regies'
37

    
38
    label = None
39
    description = None
40
    service = None
41
    service_options = None
42

    
43
    def get_payment_object(self):
44
        return eopayment.Payment(kind=self.service,
45
                                 options=self.service_options)
46

    
47

    
48
class Invoice(StorableObject):
49
    _names = 'invoices'
50
    _hashed_indexes = ['user_id', 'regie_id']
51

    
52
    user_id = None
53
    regie_id = None
54
    formdef_id = None
55
    formdata_id = None
56
    amount = None
57
    date = None
58
    paid = False
59
    paid_date = None
60

    
61

    
62
class Transaction(StorableObject):
63
    _names = 'transactions'
64
    _hashed_indexes = ['invoice_ids']
65
    _indexes = ['eop_transaction_id']
66

    
67
    invoice_ids = None
68

    
69
    eop_transaction_id = None
70
    start = None
71
    end = None
72
    bank_data = None
73

    
74
    def __init__(self, *args, **kwargs):
75
        self.invoice_ids = list()
76
        StorableObject.__init__(self, *args, **kwargs)
77

    
78
    def get_new_id(cls):
79
        keys = cls.keys()
80
        r = random.SystemRandom()
81
        while True:
82
            id = ''.join([r.choice(string.digits) for x in range(16)])
83
            if not id in keys:
84
                return id
85
    get_new_id = classmethod(get_new_id)
86

    
87
class PaymentWorkflowStatusItem(WorkflowStatusItem):
88
    description = N_('Payment Creation')
89
    key = 'payment'
90
    endpoint = False
91

    
92
    by = None
93
    amount = None
94
    waiting_status = None
95
    regie_id = None
96

    
97
    def render_as_line(self):
98
        if self.by and self.regie_id:
99
            return _('Payable to %(regie)s by %(roles)s') % {
100
                'regie': Regie.get(self.regie_id).label,
101
                'roles': render_list_of_roles(self.by)}
102
        else:
103
            return _('Payable (not completed)')
104

    
105
    def get_parameters(self):
106
        return ('by', 'amount', 'waiting_status', 'regie_id')
107

    
108
    def fill_admin_form(self, form):
109
        if self.by and not type(self.by) is list:
110
            self.by = None
111
        self.add_parameters_widgets(form, self.get_parameters())
112

    
113
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
114
        if 'amount' in parameters:
115
            form.add(StringWidget, '%samount' % prefix, title=_('Amount'), value=self.amount)
116
        if 'by' in parameters:
117
            form.add(WidgetList, '%sby' % prefix, title=_('By'), element_type=SingleSelectWidget,
118
                value=self.by,
119
                add_element_label=_('Add Role'),
120
                element_kwargs={
121
                    'render_br': False,
122
                    'options': [(None, '---'),
123
                                ('_submitter', _('Sender')),
124
                                ('_receiver', _('Receiver')),
125
                                (logged_users_role().id, logged_users_role().name),
126
                                (None, '----')] + get_user_roles()})
127
        if 'waiting_status' in parameters:
128
            form.add(SingleSelectWidget, '%swaiting_status' % prefix,
129
                title=_('Status while waiting validation'), value = self.waiting_status,
130
                options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
131
        if 'regie_id' in parameters:
132
            form.add(SingleSelectWidget, '%sregie_id' % prefix,
133
                title=_('Regie'), value=self.regie_id,
134
                options = [(None, '---')] + [(x.id, x.label) for x in Regie.select()])
135

    
136
    def calculate_amount(self, formdata):
137
        if not self.amount:
138
            return 0
139
        if not self.amount.startswith('='):
140
            return self.amount
141
        vars = formdata.get_as_dict()
142
        amount = eval(self.amount[1:], vars)
143
        # XXX: catch and report the error somehow
144
        return amount
145

    
146
    def perform(self, formdata):
147
        invoice = Invoice()
148
        invoice.user_id = get_request().user.id
149
        invoice.regie_id = self.regie_id
150
        invoice.formdef_id = formdata.formdef.id
151
        invoice.formdata_id = formdata.id
152
        invoice.amount = self.calculate_amount(formdata)
153
        invoice.date = dt.now()
154
        invoice.store()
155

    
156
        wf_status = [x for x in self.parent.parent.possible_status \
157
                     if x.id == self.waiting_status][0]
158
        formdata.status = 'wf-%s' % wf_status.id
159
        formdata.store()
160

    
161
        return get_publisher().get_root_url() + 'myspace/invoices/'
162

    
163
register_item_class(PaymentWorkflowStatusItem)
164

    
165
def request_payment(invoice_ids, url, add_regie=True):
166
    invoices = [ Invoice.get(invoice_id) for invoice_id in invoice_ids ]
167
    invoices = filter(lambda x: not x.paid, invoices)
168
    regie_ids = set([invoice.regie_id for invoice in invoices])
169
    # Do not apply if more than one regie is used or no invoice is not paid
170
    if len(invoices) == 0 or len(regie_ids) != 1:
171
        return redirect(get_publisher().get_root_url() + 'myspace/invoices/')
172
    if add_regie:
173
        url = '%s%s' % (url, list(regie_ids)[0])
174

    
175
    transaction = Transaction()
176
    transaction.store()
177
    transaction.invoice_ids = invoice_ids
178
    transaction.start = dt.now()
179

    
180
    amount = Decimal(0)
181
    for invoice in invoices:
182
        amount += Decimal(invoice.amount)
183

    
184
    regie = Regie.get(invoice.regie_id)
185
    payment = regie.get_payment_object()
186
    (eop_transaction_id, kind, data) = payment.request(amount, next_url=url)
187
    transaction.eop_transaction_id = eop_transaction_id
188
    transaction.store()
189

    
190
    if kind == eopayment.URL:
191
        return redirect(data)
192
    elif kind == eopayment.FORM:
193
        raise NotImplementedError()
194
    else:
195
        raise NotImplementedError()
196

    
197
class PaymentValidationDirectory(Directory):
198
    _q_exports = ['redirect']
199

    
200
    def __init__(self, formdata, wfstatusitem, wfstatus):
201
        self.formdata = formdata
202
        self.wfstatus = wfstatus
203
        self.wfstatusitem = wfstatusitem
204

    
205
    def redirect(self):
206
        invoice = Invoice.get(get_request().form.get('invoice'))
207
        req = get_request()
208
        # FIXME: with
209
        #   url = self.formdata.get_url() + 'payment/'
210
        # it does not work inside iframe- sites FormData.get_url() use
211
        # get_server() with clean=True, and I don't if it's necessary or not
212
        url = self.formdata.get_url()
213
        return request_payment([invoice.id], url, add_regie=False)
214

    
215
    def _q_lookup(self, component):
216
        PublicPaymentDirectory._q_lookup(self, component)
217
        return redirect('..')
218

    
219
class PaymentValidationWorkflowStatusItem(WorkflowStatusItem):
220
    description = N_('Payment Validation')
221
    key = 'payment-validation'
222
    endpoint = False
223

    
224
    directory_name = 'payment'
225
    directory_class = PaymentValidationDirectory
226

    
227
    next_status = None
228

    
229
    def render_as_line(self):
230
        return _('Wait for payment validation')
231

    
232
    def get_parameters(self):
233
        return ('next_status',)
234

    
235
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
236
        if 'next_status' in parameters:
237
            form.add(SingleSelectWidget, '%snext_status' % prefix,
238
                title=_('Status once validated'), value = self.next_status,
239
                options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
240

    
241
    def validated(self, formdata, invoice_id):
242
        invoice = Invoice.get(invoice_id)
243
        if invoice.formdata_id != formdata.id:
244
            raise 'XXX'
245
        if invoice.formdef_id != formdata.formdef.id:
246
            raise 'XXX'
247
        invoice.paid = True
248
        invoice.paid_date = dt.now()
249
        invoice.store()
250

    
251
        evo = Evolution()
252
        evo.time = time.localtime()
253
        wf_status = [x for x in self.parent.parent.possible_status \
254
                     if x.id == self.next_status][0]
255
        evo.status = 'wf-%s' % wf_status.id
256
        formdata.status = evo.status
257
        if not formdata.evolution:
258
            formdata.evolution = []
259
        formdata.evolution.append(evo)
260
        formdata.store()
261

    
262
register_item_class(PaymentValidationWorkflowStatusItem)
263

    
264

    
265
class PublicPaymentRegieBackDirectory(Directory):
266
    def _q_lookup(self, component):
267
        logger = get_logger()
268
        try:
269
            regie = Regie.get(component)
270
        except KeyError:
271
            raise errors.TraversalError()
272
        query_string = get_request().get_query()
273
        payment = regie.get_payment_object()
274
        (result, eop_transaction_id, bank_data, return_content) = payment.response(query_string)
275
        if not return_content:
276
            if result:
277
                # TODO: Here return success message
278
                get_session().message = ('error', _('Payment succeeded'))
279
            else:
280
                # TODO: here return failure message
281
                get_session().message = ('info', _('Payment failed'))
282
            req = get_request()
283
            url = '%s://%s%s/myspace/invoices/' % (
284
                    req.get_scheme(), req.get_server(clean=False),
285
                    urllib.quote(req.environ.get('SCRIPT_NAME')),)
286
            return redirect(url)
287
        else:
288
            get_response().set_content_type('text/plain')
289
            if result is False:
290
                logger.error('transaction %s finished with failure, bank_data:%s' % (
291
                                        eop_transaction_id, bank_data))
292
                return return_content
293
            logger.info('transaction %s successful, bank_data:%s' % (
294
                                    eop_transaction_id, bank_data))
295
            transaction = Transaction.get_on_index(eop_transaction_id,
296
                    'eop_transaction_id')
297
            transaction.end = dt.now()
298
            transaction.bank_data = bank_data
299
            transaction.store()
300

    
301
            for invoice_id in transaction.invoice_ids:
302
                invoice = Invoice.get(invoice_id)
303
                formdef = FormDef.get(invoice.formdef_id)
304
                formdata = formdef.data_class().get(invoice.formdata_id)
305
                wf_status = formdata.get_workflow_status()
306
                for item in wf_status.items:
307
                    if isinstance(item, PaymentValidationWorkflowStatusItem):
308
                        item.validated(formdata, invoice_id)
309
            return return_content
310

    
311
class PublicPaymentDirectory(Directory):
312
    _q_exports = ['', 'init', 'back']
313

    
314
    back = PublicPaymentRegieBackDirectory()
315

    
316
    def init(self):
317
        invoice_ids = get_request().form.get('invoice')
318
        if type(invoice_ids) is not list:
319
            invoice_ids = [invoice_ids]
320

    
321
        req = get_request()
322
        url = '%s://%s%s%spayment/back/' % (
323
                req.get_scheme(), req.get_server(clean=False),
324
                urllib.quote(req.environ.get('SCRIPT_NAME')),
325
                get_publisher().get_root_url())
326

    
327
        return request_payment(invoice_ids, url)
(20-20/26)