source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/studentsbrowser.py @ 16206

Last change on this file since 16206 was 16206, checked in by Henrik Bettermann, 4 years ago

Don't allow resending ticket to Remita if RRR has already been generated.

  • Property svn:keywords set to Id
File size: 12.0 KB
Line 
1## $Id: studentsbrowser.py 16206 2020-08-18 09:17:00Z henrik $
2##
3## Copyright (C) 2017 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19import hashlib
20from datetime import datetime, timedelta
21from zope.component import getUtility
22from zope.security import checkPermission
23from waeup.kofa.interfaces import IKofaUtils
24from waeup.kofa.utils.helpers import to_timezone
25from waeup.kofa.browser.layout import UtilityView, KofaPage
26from waeup.kofa.browser.viewlets import ManageActionButton
27from waeup.kofa.students.interfaces import IStudentsUtils
28from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
29from kofacustom.nigeria.remita.helpers import (
30    get_JSON_POST_response, query_remita, write_payments_log)
31from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
32from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
33from kofacustom.nigeria.interfaces import MessageFactory as _
34
35from kofacustom.nigeria.remita.tests import (
36    MERCHANTID, HOST, HTTPS, API_KEY, SERVICETYPEID, GATEWAY_AMT)
37
38grok.templatedir('browser_templates')
39
40def module_activated(session, payment):
41    if payment.r_company and payment.r_company != 'remita':
42        return False
43    try:
44        return getattr(grok.getSite()['configuration'][str(session)],
45            'remita_enabled', False)
46    except KeyError:
47        return False
48
49# Buttons
50
51class RemitaActionButtonStudent(ManageActionButton):
52    grok.order(1)
53    grok.context(INigeriaOnlinePayment)
54    grok.view(OPDPStudent)
55    grok.require('waeup.payStudent')
56    icon = 'actionicon_pay.png'
57    text = _('Pay via Remita')
58    target = 'goto_remita'
59
60    @property
61    def target_url(self):
62        if not module_activated(
63            self.context.student.current_session, self.context):
64            return ''
65        if self.context.p_state != 'unpaid':
66            return ''
67        return self.view.url(self.view.context, self.target)
68
69class RemitaRequestPaymentStatusActionButtonStudent(ManageActionButton):
70    grok.order(2)
71    grok.context(INigeriaOnlinePayment)
72    grok.view(OPDPStudent)
73    grok.require('waeup.payStudent')
74    icon = 'actionicon_call.png'
75    text = _('Requery Remita Payment Status')
76    target = 'request_payment_status'
77
78    @property
79    def target_url(self):
80        if not module_activated(
81            self.context.student.current_session, self.context):
82            return ''
83        if self.context.p_state in ('paid', 'waived', 'scholarship'):
84            return ''
85        return self.view.url(self.view.context, self.target)
86
87
88class RemitaVerifyPaymentStatusActionButtonStudent(ManageActionButton):
89    grok.order(3)
90    grok.context(INigeriaOnlinePayment)
91    grok.view(OPDPStudent)
92    grok.require('waeup.manageStudent')
93    icon = 'actionicon_call.png'
94    text = _('Verify Remita Payment Status')
95    target = 'verify_payment_status'
96
97    @property
98    def target_url(self):
99        if not module_activated(
100            self.context.student.current_session, self.context):
101            return ''
102        if self.context.p_state != 'paid' \
103            or self.context.r_company != u'remita':
104            return ''
105        return self.view.url(self.view.context, self.target)
106
107# Webservice request views
108
109class RemitaRequestPaymentStatusPageStudent(UtilityView, grok.View):
110    """ Request webservice view for the Remita gateway.
111    """
112    grok.context(INigeriaStudentOnlinePayment)
113    grok.name('request_payment_status')
114    grok.require('waeup.payStudent')
115
116    merchantId = MERCHANTID
117    host = HOST
118    https = HTTPS
119    api_key = API_KEY
120
121    def update(self):
122        if not module_activated(
123            self.context.student.current_session, self.context):
124            self.flash(_('Forbidden'), type='danger')
125            self.redirect(self.url(self.context, '@@index'))
126            return
127        if self.context.p_state in ('paid', 'waived', 'scholarship'):
128            self.flash(_('This ticket has already been paid.'), type='danger')
129            return
130        student = self.context.student
131        RRR = self.context.r_pay_reference
132        if not RRR:
133            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
134            return
135        # Remita sends a POST request which may contain more information
136        # if a payment was not successful.
137        resp = self.request.form
138        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
139            self.flash('Transaction status message from Remita: %s'
140                % resp.get('status'), type='warning')
141        success, msg, log = query_remita(
142            self.context,
143            self.merchantId,
144            self.api_key,
145            RRR,
146            self.host,
147            self.https,
148            False)
149        student.writeLogMessage(self, log)
150        if not success:
151            self.flash(msg, type='danger')
152            return
153        write_payments_log(student.student_id, self.context)
154        flashtype, msg, log = self.context.doAfterStudentPayment()
155        if log is not None:
156            student.writeLogMessage(self, log)
157        self.flash(msg, type=flashtype)
158        return
159
160    def render(self):
161        self.redirect(self.url(self.context, '@@index'))
162        return
163
164class RemitaVerifyPaymentStatusPageStudent(UtilityView, grok.View):
165    """ Request webservice view for the Remita gateway.
166    """
167    grok.context(INigeriaStudentOnlinePayment)
168    grok.name('verify_payment_status')
169    grok.require('waeup.manageStudent')
170
171    merchantId = MERCHANTID
172    host = HOST
173    https = HTTPS
174    api_key = API_KEY
175
176    def update(self):
177        if not module_activated(
178            self.context.student.current_session, self.context):
179            self.flash(_('Forbidden'), type='danger')
180            self.redirect(self.url(self.context, '@@index'))
181            return
182        if self.context.p_state  != 'paid' \
183            or self.context.r_company != u'remita':
184            self.flash(_('This ticket has not been paid.'), type='danger')
185            return
186        student = self.context.student
187        RRR = self.context.r_pay_reference
188        if not RRR:
189            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
190            return
191        # Remita sends a POST request which may contain more information
192        # if a payment was not successful.
193        resp = self.request.form
194        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
195            self.flash('Transaction status message from Remita: %s'
196                % resp.get('status'), type='warning')
197        success, msg, log = query_remita(
198            self.context,
199            self.merchantId,
200            self.api_key,
201            RRR,
202            self.host,
203            self.https,
204            True)
205        student.writeLogMessage(self, log)
206        if not success:
207            self.flash(msg, type='danger')
208            return
209        self.flash(msg)
210        return
211
212    def render(self):
213        self.redirect(self.url(self.context, '@@index'))
214        return
215
216
217# Forwarding pages
218
219class RemitaPageStudent(KofaPage):
220    """ View which sends a POST request to the Remita payment gateway.
221    """
222    grok.context(INigeriaOnlinePayment)
223    grok.name('goto_remita')
224    grok.template('goto_remita')
225    grok.require('waeup.payStudent')
226    label = _('Pay via Remita')
227    submit_button = _('Pay now')
228
229    merchantId = MERCHANTID
230    host = HOST
231    https = HTTPS
232    api_key = API_KEY
233    serviceTypeId = SERVICETYPEID
234    gateway_amt = GATEWAY_AMT
235
236    #orderId = '3456346346'
237    init_url = '/remita/ecomm/split/init.reg'
238    amount='1000'
239    lineitems = (
240                  {"lineItemsId":"itemid1","beneficiaryName":"Klaus Mueller",
241                  "beneficiaryAccount":"6020067886","bankCode":"011",
242                  "beneficiaryAmount":"500","deductFeeFrom":"1"},
243                  {"lineItemsId":"itemid2","beneficiaryName":"Werner Rumm",
244                  "beneficiaryAccount":"0360883515","bankCode":"050",
245                  "beneficiaryAmount":"500","deductFeeFrom":"0"}
246                )
247
248    @property
249    def action(self):
250        if self.https:
251            return 'https://' + self.host + '/remita/ecomm/finalize.reg'
252        return 'http://' + self.host + '/remita/ecomm/finalize.reg'
253
254    def init_update(self):
255        if self.context.r_pay_reference or self.context.p_state == 'paid':
256            return _("Payment ticket can't be re-sent to Remita.")
257        if self.context.r_company and self.context.r_company != 'remita':
258            return _("Payment ticket has been used for another payment gateway.")
259        now = datetime.utcnow()
260        if self.context.creation_date.tzinfo is not None:
261            # That's bad. Please store timezone-naive datetimes only!
262            now = self.context.creation_date.tzinfo.localize(now)
263        time_delta = now - self.context.creation_date
264        if time_delta.days > 7:
265            return _("This payment ticket is too old. Please create a new ticket.")
266        certificate = getattr(self.context.student['studycourse'],'certificate',None)
267        if certificate is None:
268            return _("Study course data are incomplete.")
269        kofa_utils = getUtility(IKofaUtils)
270        student_utils = getUtility(IStudentsUtils)
271        if student_utils.samePaymentMade(self.context.student, self.context.p_category,
272            self.context.p_item, self.context.p_session):
273            return _("This type of payment has already been made.")
274        self.responseurl = self.url(self.context, 'request_payment_status')
275        resp = get_JSON_POST_response(
276            merchantId=self.merchantId,
277            serviceTypeId=self.serviceTypeId,
278            api_key=self.api_key,
279            orderId=self.orderId,
280            amount=self.amount,
281            responseurl=self.responseurl,
282            host=self.host,
283            url=self.init_url,
284            https=self.https,
285            fullname=self.context.student.display_fullname,
286            email=self.context.student.email,
287            lineitems=self.lineitems)
288        if resp.get('error'):
289            return resp.get('error')
290        if resp.get('statuscode') not in ('021', '025', '055'):
291            return 'RRR generation message from Remita: ' + resp.get('status')
292        self.rrr = self.context.r_pay_reference = resp['RRR'].rstrip()
293        hashargs =      self.merchantId + self.rrr + self.api_key
294        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
295        self.customer = self.context.student
296        self.customer.writeLogMessage(self,
297            'RRR retrieved: %s, ServiceTypeId: %s'
298            % (self.rrr, self.serviceTypeId))
299        return
300
301    def update(self):
302        if not module_activated(
303            self.context.student.current_session, self.context):
304            self.flash(_('Forbidden'), type='danger')
305            self.redirect(self.url(self.context, '@@index'))
306            return
307        self.orderId = self.context.p_id
308        error = self.init_update()
309        if error:
310            self.flash(error, type='danger')
311            self.redirect(self.url(self.context, '@@index'))
312            return
313        # Already now it becomes a Remita payment. We set the net amount
314        # and add the gateway amount.
315        if not self.context.r_company:
316            self.context.net_amt = self.context.amount_auth
317            self.context.amount_auth += self.gateway_amt
318            self.context.gateway_amt = self.gateway_amt
319            self.context.r_company = u'remita'
320        self.amount_auth = int(100 * self.context.amount_auth)
321        return
Note: See TracBrowser for help on using the repository browser.