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')
|
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=''):
|
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=''):
|
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)
|