source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/browser.py @ 17605

Last change on this file since 17605 was 17605, checked in by Henrik Bettermann, 13 months ago

Redirect in the update method.

  • Property svn:keywords set to Id
File size: 17.2 KB
Line 
1## $Id: browser.py 17605 2023-10-08 07:25:59Z henrik $
2##
3## Copyright (C) 2012 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
19from datetime import datetime, timedelta
20from zope.component import getUtility
21from zope.security import checkPermission
22from waeup.kofa.interfaces import IKofaUtils
23from waeup.kofa.utils.helpers import to_timezone
24from waeup.kofa.browser.layout import UtilityView, KofaPage, KofaFormPage, action
25from waeup.kofa.browser.viewlets import ManageActionButton
26from waeup.kofa.students.interfaces import IStudentsUtils
27from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
28from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
29from kofacustom.nigeria.interswitch.helpers import (
30    query_interswitch, write_payments_log, fetch_booking_details)
31from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
32from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
33from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
34from kofacustom.nigeria.interswitch.tests import PAYDIRECT_URL, PAYDIRECT_HOST, MERCHANT_ID
35from kofacustom.nigeria.interfaces import MessageFactory as _
36
37GATEWAY_AMT = 300.0
38
39# Buttons
40
41def module_activated(session, payment):
42    if payment.p_currency != 'NGN':
43        return False
44    if payment.r_company and payment.r_company != 'interswitch':
45        return False
46    try:
47        return getattr(grok.getSite()['configuration'][str(session)],
48            'interswitch_enabled', True)
49    except KeyError:
50        session = datetime.now().year
51        try:
52            return getattr(grok.getSite()['configuration'][str(session)],
53                'interswitch_enabled', True)
54        except KeyError:
55            return False
56
57class InterswitchActionButtonStudent(ManageActionButton):
58    grok.order(1)
59    grok.context(INigeriaOnlinePayment)
60    grok.view(OPDPStudent)
61    grok.require('waeup.payStudent')
62    icon = 'actionicon_pay.png'
63    text = _('Pay via Interswitch CollegePAY')
64    target = 'goto_interswitch'
65
66    @property
67    def target_url(self):
68        if not module_activated(
69            self.context.student.current_session, self.context):
70            return ''
71        if self.context.p_state != 'unpaid':
72            return ''
73        if self.context.amount_auth == 0:
74            return ''
75        return self.view.url(self.view.context, self.target)
76
77class InterswitchActionButtonApplicant(InterswitchActionButtonStudent):
78    grok.view(OPDPApplicant)
79    grok.require('waeup.payApplicant')
80
81    @property
82    def target_url(self):
83        if not module_activated(
84            self.context.__parent__.__parent__.year, self.context):
85            return ''
86        if self.context.p_state != 'unpaid':
87            return ''
88        if self.context.amount_auth == 0:
89            return ''
90        return self.view.url(self.view.context, self.target)
91
92class InterswitchRequestWebserviceActionButtonStudent(ManageActionButton):
93    grok.order(2)
94    grok.context(INigeriaOnlinePayment)
95    grok.view(OPDPStudent)
96    grok.require('waeup.payStudent')
97    icon = 'actionicon_call.png'
98    text = _('Requery CollegePAY History')
99    target = 'request_webservice'
100
101    @property
102    def target_url(self):
103        if not module_activated(
104            self.context.student.current_session, self.context):
105            return ''
106        if self.context.amount_auth == 0:
107            return ''
108        if self.context.p_state in ('paid', 'waived', 'scholarship'):
109            return ''
110        return self.view.url(self.view.context, self.target)
111
112class InterswitchRequestWebserviceActionButtonApplicant(
113    InterswitchRequestWebserviceActionButtonStudent):
114    grok.view(OPDPApplicant)
115    grok.require('waeup.payApplicant')
116
117    @property
118    def target_url(self):
119        if not module_activated(
120            self.context.__parent__.__parent__.year, self.context):
121            return ''
122        if self.context.amount_auth == 0:
123            return ''
124        if self.context.p_state in ('paid', 'waived', 'scholarship'):
125            return ''
126        return self.view.url(self.view.context, self.target)
127
128class InterswitchVerifyWebserviceActionButtonStudent(ManageActionButton):
129    grok.order(3)
130    grok.context(INigeriaOnlinePayment)
131    grok.view(OPDPStudent)
132    grok.require('waeup.manageStudent')
133    icon = 'actionicon_call.png'
134    text = _('Verify Payment')
135    target = 'verify_payment'
136
137    @property
138    def target_url(self):
139        if not module_activated(
140            self.context.student.current_session, self.context):
141            return ''
142        if self.context.p_state != 'paid' \
143            or self.context.r_company != u'interswitch':
144            return ''
145        return self.view.url(self.view.context, self.target)
146
147class InterswitchVerifyWebserviceActionButtonApplicant(
148    InterswitchVerifyWebserviceActionButtonStudent):
149    grok.view(OPDPApplicant)
150    grok.require('waeup.manageApplication')
151
152    @property
153    def target_url(self):
154        if not module_activated(
155            self.context.__parent__.__parent__.year, self.context):
156            return ''
157        if self.context.p_state != 'paid' \
158            or self.context.r_company != u'interswitch':
159            return ''
160        return self.view.url(self.view.context, self.target)
161
162# Webservice request views
163
164class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
165    """ Request webservice view for the CollegePAY gateway
166    """
167    grok.context(INigeriaStudentOnlinePayment)
168    grok.name('request_webservice')
169
170    #grok.require('waeup.payStudent')
171   
172    # Function temporarily opened on 08/10/23 because Interswitch is sending
173    # POST requests instead of GET requests. Usually all browsers protect
174    # against cross-site attacks by removing cookies included in requests
175    # originating from other websites. In other words, the cookies with the
176    # session id, which is used for authentication is beeing removed when
177    # receiving a POST request from the Interswitch platform.
178    grok.require('waeup.Public')
179
180    product_id = None
181    gateway_host = None
182    gateway_url = None
183    https = True
184    mac = None
185
186    def update(self):
187        if not module_activated(
188            self.context.student.current_session, self.context):
189            return
190        if self.context.p_state in ('paid', 'waived', 'scholarship'):
191            self.flash(_('This ticket has already been paid.'), type='danger')
192            return
193        student = self.context.student
194        success, msg, log = query_interswitch(
195            self.context, self.product_id,
196            self.gateway_host, self.gateway_url,
197            self.https, self.mac, False)
198        student.writeLogMessage(self, log)
199        if not success:
200            self.flash(msg, type='danger')
201            return
202        write_payments_log(student.student_id, self.context)
203        flashtype, msg, log = self.context.doAfterStudentPayment()
204        if log is not None:
205            student.writeLogMessage(self, log)
206        self.flash(msg, type=flashtype)
207        self.redirect(self.url(self.context, '@@index'))
208        return
209
210    def render(self):
211        self.redirect(self.url(self.context, '@@index'))
212        return
213
214class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
215    """ Request webservice view for the CollegePAY gateway
216    """
217    grok.context(INigeriaApplicantOnlinePayment)
218    grok.name('request_webservice')
219    grok.require('waeup.payApplicant')
220
221    product_id = None
222    gateway_host = None
223    gateway_url = None
224    https = True
225    mac = None
226
227    def update(self):
228        if not module_activated(
229            self.context.__parent__.__parent__.year, self.context):
230            return
231        if self.context.p_state == 'paid':
232            self.flash(_('This ticket has already been paid.'), type='danger')
233            return
234        applicant = self.context.__parent__
235        success, msg, log = query_interswitch(
236            self.context, self.product_id,
237            self.gateway_host, self.gateway_url,
238            self.https, self.mac, False)
239        applicant.writeLogMessage(self, log)
240        if not success:
241            self.flash(msg, type='danger')
242            return
243        write_payments_log(applicant.applicant_id, self.context)
244        flashtype, msg, log = self.context.doAfterApplicantPayment()
245        if log is not None:
246            applicant.writeLogMessage(self, log)
247        self.flash(msg, type=flashtype)
248        return
249
250    def render(self):
251        self.redirect(self.url(self.context.__parent__, 'edit'))
252        return
253
254class InterswitchPaymentVerifyWebservicePageStudent(UtilityView, grok.View):
255    """ Verify payment view for the CollegePAY gateway
256    """
257    grok.context(INigeriaStudentOnlinePayment)
258    grok.name('verify_payment')
259    grok.require('waeup.manageStudent')
260
261    product_id = None
262    gateway_host = None
263    gateway_url = None
264    https = True
265    mac = None
266
267    def update(self):
268        if not module_activated(
269            self.context.student.current_session, self.context):
270            return
271        if self.context.p_state  != 'paid' \
272            or self.context.r_company != u'interswitch':
273            self.flash(_('This ticket has not been paid.'), type='danger')
274            return
275        student = self.context.student
276        success, msg, log = query_interswitch(
277            self.context, self.product_id,
278            self.gateway_host, self.gateway_url,
279            self.https, self.mac, True)
280        student.writeLogMessage(self, log)
281        if not success:
282            self.flash(msg, type='danger')
283            return
284        self.flash(msg)
285        return
286
287    def render(self):
288        self.redirect(self.url(self.context, '@@index'))
289        return
290
291class InterswitchPaymentVerifyWebservicePageApplicant(UtilityView, grok.View):
292    """ Verify payment view for the CollegePAY gateway
293    """
294    grok.context(INigeriaApplicantOnlinePayment)
295    grok.name('verify_payment')
296    grok.require('waeup.manageApplication')
297
298    product_id = None
299    gateway_host = None
300    gateway_url = None
301    https = True
302    mac = None
303
304    def update(self):
305        if not module_activated(
306            self.context.__parent__.__parent__.year, self.context):
307            return
308        if self.context.p_state != 'paid' \
309            or self.context.r_company != u'interswitch':
310            self.flash(_('This ticket has not been paid.'), type='danger')
311            return
312        applicant = self.context.__parent__
313        success, msg, log = query_interswitch(
314            self.context, self.product_id,
315            self.gateway_host, self.gateway_url,
316            self.https, self.mac, True)
317        applicant.writeLogMessage(self, log)
318        if not success:
319            self.flash(msg, type='danger')
320            return
321        self.flash(msg)
322        return
323
324    def render(self):
325        self.redirect(self.url(self.context, '@@index'))
326        return
327
328# Forwarding pages
329
330class InterswitchPageStudent(KofaPage):
331    """ View which sends a POST request to the Interswitch
332    CollegePAY payment gateway.
333    """
334    grok.context(INigeriaOnlinePayment)
335    grok.name('goto_interswitch')
336    grok.template('student_goto_interswitch')
337    grok.require('waeup.payStudent')
338    label = _('Submit data to CollegePAY')
339    submit_button = _('Submit')
340
341    action = None
342    site_name = None
343    currency = None
344    pay_item_id = None
345    product_id = None
346    xml_data = None
347    hashvalue = None
348    gateway_amt = GATEWAY_AMT
349
350    def init_update(self):
351        if self.context.p_state == 'paid':
352            return _("Payment ticket can't be re-sent to CollegePAY.")
353        now = datetime.utcnow()
354        if self.context.creation_date.tzinfo is not None:
355            # That's bad. Please store timezone-naive datetimes only!
356            now = self.context.creation_date.tzinfo.localize(now)
357        time_delta = now - self.context.creation_date
358        if time_delta.days > 7:
359            return _("This payment ticket is too old. Please create a new ticket.")
360        if self.context.r_company and self.context.r_company != 'interswitch':
361            return _("Payment ticket has been used for another payment gateway.")
362        student = self.context.student
363        certificate = getattr(student['studycourse'],'certificate',None)
364        if certificate is None:
365            return _("Study course data are incomplete.")
366        kofa_utils = getUtility(IKofaUtils)
367        student_utils = getUtility(IStudentsUtils)
368        if student_utils.samePaymentMade(student, self.context.p_category,
369            self.context.p_item, self.context.p_session):
370            return _("This type of payment has already been made.")
371        xmldict = {}
372        if certificate is not None:
373            xmldict['department'] = certificate.__parent__.__parent__.code
374            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
375        else:
376            xmldict['department'] = None
377            xmldict['faculty'] = None
378        self.category = self.context.category
379        tz = kofa_utils.tzinfo
380        self.local_date_time = to_timezone(
381            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
382        self.site_redirect_url = self.url(self.context, 'request_webservice')
383        self.student = student
384        self.xmldict = xmldict
385        return
386
387    def update(self):
388        if not module_activated(
389            self.context.student.current_session, self.context):
390            self.flash(_('Forbidden'), type='danger')
391            self.redirect(self.url(self.context, '@@index'))
392            return
393        error = self.init_update()
394        if error:
395            self.flash(error, type='danger')
396            self.redirect(self.url(self.context, '@@index'))
397        # Already now it becomes an Interswitch payment. We set the net amount
398        # and add the gateway amount.
399        if not self.context.r_company:
400            self.context.net_amt = self.context.amount_auth
401            self.context.amount_auth += self.gateway_amt
402            self.context.gateway_amt = self.gateway_amt
403            self.context.r_company = u'interswitch'
404        self.amount_auth = int(100 * self.context.amount_auth)
405        return
406
407class InterswitchPageApplicant(KofaPage):
408    """ View which sends a POST request to the Interswitch
409    CollegePAY payment gateway.
410    """
411    grok.context(INigeriaApplicantOnlinePayment)
412    grok.require('waeup.payApplicant')
413    grok.template('applicant_goto_interswitch')
414    grok.name('goto_interswitch')
415    label = _('Submit data to CollegePAY')
416    submit_button = _('Submit')
417
418    action = None
419    site_name = None
420    currency = None
421    pay_item_id = None
422    product_id = None
423    xml_data = None
424    hashvalue = None
425    gateway_amt = GATEWAY_AMT
426
427    def init_update(self):
428        if self.context.p_state != 'unpaid':
429            return _("Payment ticket can't be re-sent to CollegePAY.")
430        if self.context.__parent__.__parent__.expired \
431            and self.context.__parent__.__parent__.strict_deadline:
432            return _("Payment ticket can't be sent to CollegePAY. "
433                     "Application period has expired.")
434        if self.context.r_company and self.context.r_company != 'interswitch':
435            return _("Payment ticket has been used for another payment gateway.")
436        tz = getUtility(IKofaUtils).tzinfo
437        time_delta = datetime.utcnow() - self.context.creation_date
438        if time_delta.days > 7:
439            return _("This payment ticket is too old. Please create a new ticket.")
440        self.applicant = self.context.__parent__
441        self.category = self.context.category
442        tz = getUtility(IKofaUtils).tzinfo
443        self.local_date_time = to_timezone(
444            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
445        self.site_redirect_url = self.url(self.context, 'request_webservice')
446        return
447
448    def update(self):
449        if not module_activated(
450            self.context.__parent__.__parent__.year, self.context):
451            self.flash(_('Forbidden'), type='danger')
452            self.redirect(self.url(self.context, '@@index'))
453            return
454        error = self.init_update()
455        if error:
456            self.flash(error, type='danger')
457            self.redirect(self.url(self.context, '@@index'))
458        # Already now it becomes an Interswitch payment. We set the net amount
459        # and add the gateway amount.
460        if not self.context.r_company:
461            self.context.net_amt = self.context.amount_auth
462            self.context.amount_auth += self.gateway_amt
463            self.context.gateway_amt = self.gateway_amt
464            self.context.r_company = u'interswitch'
465        self.amount_auth = int(100 * self.context.amount_auth)
466        return
467
Note: See TracBrowser for help on using the repository browser.