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, 12 months ago

Redirect in the update method.

  • Property svn:keywords set to Id
File size: 17.2 KB
RevLine 
[9781]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
[12973]19from datetime import datetime, timedelta
[11630]20from zope.component import getUtility
[13578]21from zope.security import checkPermission
[11630]22from waeup.kofa.interfaces import IKofaUtils
23from waeup.kofa.utils.helpers import to_timezone
[16484]24from waeup.kofa.browser.layout import UtilityView, KofaPage, KofaFormPage, action
[9781]25from waeup.kofa.browser.viewlets import ManageActionButton
[11642]26from waeup.kofa.students.interfaces import IStudentsUtils
[9781]27from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
28from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
29from kofacustom.nigeria.interswitch.helpers import (
[16484]30    query_interswitch, write_payments_log, fetch_booking_details)
[9781]31from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
32from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
33from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
[16484]34from kofacustom.nigeria.interswitch.tests import PAYDIRECT_URL, PAYDIRECT_HOST, MERCHANT_ID
[9781]35from kofacustom.nigeria.interfaces import MessageFactory as _
36
[15755]37GATEWAY_AMT = 300.0
38
[13581]39# Buttons
40
[15770]41def module_activated(session, payment):
[17248]42    if payment.p_currency != 'NGN':
43        return False
[15770]44    if payment.r_company and payment.r_company != 'interswitch':
45        return False
[14755]46    try:
47        return getattr(grok.getSite()['configuration'][str(session)],
48            'interswitch_enabled', True)
49    except KeyError:
[16246]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
[14755]56
[9781]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'
[16484]63    text = _('Pay via Interswitch CollegePAY')
[9781]64    target = 'goto_interswitch'
65
66    @property
67    def target_url(self):
[15770]68        if not module_activated(
69            self.context.student.current_session, self.context):
[14755]70            return ''
[9781]71        if self.context.p_state != 'unpaid':
72            return ''
[16880]73        if self.context.amount_auth == 0:
74            return ''
[9781]75        return self.view.url(self.view.context, self.target)
76
77class InterswitchActionButtonApplicant(InterswitchActionButtonStudent):
78    grok.view(OPDPApplicant)
79    grok.require('waeup.payApplicant')
80
[14755]81    @property
82    def target_url(self):
[15770]83        if not module_activated(
84            self.context.__parent__.__parent__.year, self.context):
[14755]85            return ''
86        if self.context.p_state != 'unpaid':
87            return ''
[16880]88        if self.context.amount_auth == 0:
89            return ''
[14755]90        return self.view.url(self.view.context, self.target)
91
[9781]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'
[16484]98    text = _('Requery CollegePAY History')
[9781]99    target = 'request_webservice'
100
101    @property
102    def target_url(self):
[15770]103        if not module_activated(
104            self.context.student.current_session, self.context):
[14755]105            return ''
[16881]106        if self.context.amount_auth == 0:
107            return ''
[15842]108        if self.context.p_state in ('paid', 'waived', 'scholarship'):
[9781]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
[14755]117    @property
118    def target_url(self):
[15770]119        if not module_activated(
120            self.context.__parent__.__parent__.year, self.context):
[14755]121            return ''
[16881]122        if self.context.amount_auth == 0:
123            return ''
[15842]124        if self.context.p_state in ('paid', 'waived', 'scholarship'):
[14755]125            return ''
126        return self.view.url(self.view.context, self.target)
127
[13581]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):
[15770]139        if not module_activated(
140            self.context.student.current_session, self.context):
[14755]141            return ''
[13585]142        if self.context.p_state != 'paid' \
143            or self.context.r_company != u'interswitch':
[13581]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
[14755]152    @property
153    def target_url(self):
[15770]154        if not module_activated(
155            self.context.__parent__.__parent__.year, self.context):
[14755]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)
[13581]161
162# Webservice request views
163
[9781]164class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
165    """ Request webservice view for the CollegePAY gateway
166    """
167    grok.context(INigeriaStudentOnlinePayment)
168    grok.name('request_webservice')
169
[17604]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
[9781]180    product_id = None
181    gateway_host = None
182    gateway_url = None
[13044]183    https = True
[13387]184    mac = None
[9781]185
186    def update(self):
[15770]187        if not module_activated(
188            self.context.student.current_session, self.context):
[14755]189            return
[15842]190        if self.context.p_state in ('paid', 'waived', 'scholarship'):
[13579]191            self.flash(_('This ticket has already been paid.'), type='danger')
[9781]192            return
193        student = self.context.student
194        success, msg, log = query_interswitch(
[11915]195            self.context, self.product_id,
196            self.gateway_host, self.gateway_url,
[13584]197            self.https, self.mac, False)
[9781]198        student.writeLogMessage(self, log)
199        if not success:
[11579]200            self.flash(msg, type='danger')
[9781]201            return
202        write_payments_log(student.student_id, self.context)
[11581]203        flashtype, msg, log = self.context.doAfterStudentPayment()
[9781]204        if log is not None:
205            student.writeLogMessage(self, log)
[11581]206        self.flash(msg, type=flashtype)
[17605]207        self.redirect(self.url(self.context, '@@index'))
[9781]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
[13044]224    https = True
[13387]225    mac = None
[9781]226
227    def update(self):
[15770]228        if not module_activated(
229            self.context.__parent__.__parent__.year, self.context):
[14755]230            return
[13580]231        if self.context.p_state == 'paid':
[11642]232            self.flash(_('This ticket has already been paid.'), type='danger')
[9781]233            return
234        applicant = self.context.__parent__
235        success, msg, log = query_interswitch(
[11915]236            self.context, self.product_id,
237            self.gateway_host, self.gateway_url,
[13584]238            self.https, self.mac, False)
[9781]239        applicant.writeLogMessage(self, log)
240        if not success:
[11579]241            self.flash(msg, type='danger')
[9781]242            return
243        write_payments_log(applicant.applicant_id, self.context)
[11581]244        flashtype, msg, log = self.context.doAfterApplicantPayment()
[9781]245        if log is not None:
246            applicant.writeLogMessage(self, log)
[11581]247        self.flash(msg, type=flashtype)
[9781]248        return
249
250    def render(self):
[15529]251        self.redirect(self.url(self.context.__parent__, 'edit'))
[9781]252        return
[11630]253
[13581]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):
[15770]268        if not module_activated(
269            self.context.student.current_session, self.context):
[14755]270            return
[13585]271        if self.context.p_state  != 'paid' \
272            or self.context.r_company != u'interswitch':
[13581]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,
[13584]279            self.https, self.mac, True)
[13581]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):
[15770]305        if not module_activated(
306            self.context.__parent__.__parent__.year, self.context):
[14755]307            return
[13585]308        if self.context.p_state != 'paid' \
309            or self.context.r_company != u'interswitch':
[13581]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,
[13584]316            self.https, self.mac, True)
[13581]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
[11630]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')
[16484]338    label = _('Submit data to CollegePAY')
[11630]339    submit_button = _('Submit')
340
[11639]341    action = None
342    site_name = None
343    currency = None
344    pay_item_id = None
345    product_id = None
346    xml_data = None
[12477]347    hashvalue = None
[15755]348    gateway_amt = GATEWAY_AMT
[11639]349
[11642]350    def init_update(self):
[11630]351        if self.context.p_state == 'paid':
[11642]352            return _("Payment ticket can't be re-sent to CollegePAY.")
[13478]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
[13015]358        if time_delta.days > 7:
[12973]359            return _("This payment ticket is too old. Please create a new ticket.")
[15755]360        if self.context.r_company and self.context.r_company != 'interswitch':
361            return _("Payment ticket has been used for another payment gateway.")
[11642]362        student = self.context.student
[11630]363        certificate = getattr(student['studycourse'],'certificate',None)
364        if certificate is None:
[11642]365            return _("Study course data are incomplete.")
[11639]366        kofa_utils = getUtility(IKofaUtils)
[11642]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.")
[11630]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
[12509]378        self.category = self.context.category
[11639]379        tz = kofa_utils.tzinfo
[11630]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')
[11642]383        self.student = student
384        self.xmldict = xmldict
385        return
[11630]386
[11642]387    def update(self):
[15770]388        if not module_activated(
389            self.context.student.current_session, self.context):
[15974]390            self.flash(_('Forbidden'), type='danger')
391            self.redirect(self.url(self.context, '@@index'))
[14755]392            return
[11642]393        error = self.init_update()
394        if error:
395            self.flash(error, type='danger')
396            self.redirect(self.url(self.context, '@@index'))
[15755]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)
[11642]405        return
406
[11630]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')
[16484]415    label = _('Submit data to CollegePAY')
[11630]416    submit_button = _('Submit')
417
[11639]418    action = None
419    site_name = None
420    currency = None
421    pay_item_id = None
422    product_id = None
423    xml_data = None
[15755]424    hashvalue = None
425    gateway_amt = GATEWAY_AMT
[11639]426
[11642]427    def init_update(self):
[11630]428        if self.context.p_state != 'unpaid':
[11642]429            return _("Payment ticket can't be re-sent to CollegePAY.")
[11630]430        if self.context.__parent__.__parent__.expired \
431            and self.context.__parent__.__parent__.strict_deadline:
[16247]432            return _("Payment ticket can't be sent to CollegePAY. "
[11642]433                     "Application period has expired.")
[15755]434        if self.context.r_company and self.context.r_company != 'interswitch':
435            return _("Payment ticket has been used for another payment gateway.")
[12973]436        tz = getUtility(IKofaUtils).tzinfo
437        time_delta = datetime.utcnow() - self.context.creation_date
[13015]438        if time_delta.days > 7:
[12973]439            return _("This payment ticket is too old. Please create a new ticket.")
[11630]440        self.applicant = self.context.__parent__
[12509]441        self.category = self.context.category
[11630]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')
[11642]446        return
447
448    def update(self):
[15770]449        if not module_activated(
450            self.context.__parent__.__parent__.year, self.context):
[15974]451            self.flash(_('Forbidden'), type='danger')
452            self.redirect(self.url(self.context, '@@index'))
[14755]453            return
[11642]454        error = self.init_update()
455        if error:
456            self.flash(error, type='danger')
457            self.redirect(self.url(self.context, '@@index'))
[15755]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)
[13478]466        return
[16484]467
Note: See TracBrowser for help on using the repository browser.