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

Last change on this file since 17604 was 17604, checked in by Henrik Bettermann, 12 months ago

Open function to allow receiving POST requests.

  • Property svn:keywords set to Id
File size: 17.1 KB
Line 
1## $Id: browser.py 17604 2023-10-08 07:20:49Z 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        return
208
209    def render(self):
210        self.redirect(self.url(self.context, '@@index'))
211        return
212
213class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
214    """ Request webservice view for the CollegePAY gateway
215    """
216    grok.context(INigeriaApplicantOnlinePayment)
217    grok.name('request_webservice')
218    grok.require('waeup.payApplicant')
219
220    product_id = None
221    gateway_host = None
222    gateway_url = None
223    https = True
224    mac = None
225
226    def update(self):
227        if not module_activated(
228            self.context.__parent__.__parent__.year, self.context):
229            return
230        if self.context.p_state == 'paid':
231            self.flash(_('This ticket has already been paid.'), type='danger')
232            return
233        applicant = self.context.__parent__
234        success, msg, log = query_interswitch(
235            self.context, self.product_id,
236            self.gateway_host, self.gateway_url,
237            self.https, self.mac, False)
238        applicant.writeLogMessage(self, log)
239        if not success:
240            self.flash(msg, type='danger')
241            return
242        write_payments_log(applicant.applicant_id, self.context)
243        flashtype, msg, log = self.context.doAfterApplicantPayment()
244        if log is not None:
245            applicant.writeLogMessage(self, log)
246        self.flash(msg, type=flashtype)
247        return
248
249    def render(self):
250        self.redirect(self.url(self.context.__parent__, 'edit'))
251        return
252
253class InterswitchPaymentVerifyWebservicePageStudent(UtilityView, grok.View):
254    """ Verify payment view for the CollegePAY gateway
255    """
256    grok.context(INigeriaStudentOnlinePayment)
257    grok.name('verify_payment')
258    grok.require('waeup.manageStudent')
259
260    product_id = None
261    gateway_host = None
262    gateway_url = None
263    https = True
264    mac = None
265
266    def update(self):
267        if not module_activated(
268            self.context.student.current_session, self.context):
269            return
270        if self.context.p_state  != 'paid' \
271            or self.context.r_company != u'interswitch':
272            self.flash(_('This ticket has not been paid.'), type='danger')
273            return
274        student = self.context.student
275        success, msg, log = query_interswitch(
276            self.context, self.product_id,
277            self.gateway_host, self.gateway_url,
278            self.https, self.mac, True)
279        student.writeLogMessage(self, log)
280        if not success:
281            self.flash(msg, type='danger')
282            return
283        self.flash(msg)
284        return
285
286    def render(self):
287        self.redirect(self.url(self.context, '@@index'))
288        return
289
290class InterswitchPaymentVerifyWebservicePageApplicant(UtilityView, grok.View):
291    """ Verify payment view for the CollegePAY gateway
292    """
293    grok.context(INigeriaApplicantOnlinePayment)
294    grok.name('verify_payment')
295    grok.require('waeup.manageApplication')
296
297    product_id = None
298    gateway_host = None
299    gateway_url = None
300    https = True
301    mac = None
302
303    def update(self):
304        if not module_activated(
305            self.context.__parent__.__parent__.year, self.context):
306            return
307        if self.context.p_state != 'paid' \
308            or self.context.r_company != u'interswitch':
309            self.flash(_('This ticket has not been paid.'), type='danger')
310            return
311        applicant = self.context.__parent__
312        success, msg, log = query_interswitch(
313            self.context, self.product_id,
314            self.gateway_host, self.gateway_url,
315            self.https, self.mac, True)
316        applicant.writeLogMessage(self, log)
317        if not success:
318            self.flash(msg, type='danger')
319            return
320        self.flash(msg)
321        return
322
323    def render(self):
324        self.redirect(self.url(self.context, '@@index'))
325        return
326
327# Forwarding pages
328
329class InterswitchPageStudent(KofaPage):
330    """ View which sends a POST request to the Interswitch
331    CollegePAY payment gateway.
332    """
333    grok.context(INigeriaOnlinePayment)
334    grok.name('goto_interswitch')
335    grok.template('student_goto_interswitch')
336    grok.require('waeup.payStudent')
337    label = _('Submit data to CollegePAY')
338    submit_button = _('Submit')
339
340    action = None
341    site_name = None
342    currency = None
343    pay_item_id = None
344    product_id = None
345    xml_data = None
346    hashvalue = None
347    gateway_amt = GATEWAY_AMT
348
349    def init_update(self):
350        if self.context.p_state == 'paid':
351            return _("Payment ticket can't be re-sent to CollegePAY.")
352        now = datetime.utcnow()
353        if self.context.creation_date.tzinfo is not None:
354            # That's bad. Please store timezone-naive datetimes only!
355            now = self.context.creation_date.tzinfo.localize(now)
356        time_delta = now - self.context.creation_date
357        if time_delta.days > 7:
358            return _("This payment ticket is too old. Please create a new ticket.")
359        if self.context.r_company and self.context.r_company != 'interswitch':
360            return _("Payment ticket has been used for another payment gateway.")
361        student = self.context.student
362        certificate = getattr(student['studycourse'],'certificate',None)
363        if certificate is None:
364            return _("Study course data are incomplete.")
365        kofa_utils = getUtility(IKofaUtils)
366        student_utils = getUtility(IStudentsUtils)
367        if student_utils.samePaymentMade(student, self.context.p_category,
368            self.context.p_item, self.context.p_session):
369            return _("This type of payment has already been made.")
370        xmldict = {}
371        if certificate is not None:
372            xmldict['department'] = certificate.__parent__.__parent__.code
373            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
374        else:
375            xmldict['department'] = None
376            xmldict['faculty'] = None
377        self.category = self.context.category
378        tz = kofa_utils.tzinfo
379        self.local_date_time = to_timezone(
380            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
381        self.site_redirect_url = self.url(self.context, 'request_webservice')
382        self.student = student
383        self.xmldict = xmldict
384        return
385
386    def update(self):
387        if not module_activated(
388            self.context.student.current_session, self.context):
389            self.flash(_('Forbidden'), type='danger')
390            self.redirect(self.url(self.context, '@@index'))
391            return
392        error = self.init_update()
393        if error:
394            self.flash(error, type='danger')
395            self.redirect(self.url(self.context, '@@index'))
396        # Already now it becomes an Interswitch payment. We set the net amount
397        # and add the gateway amount.
398        if not self.context.r_company:
399            self.context.net_amt = self.context.amount_auth
400            self.context.amount_auth += self.gateway_amt
401            self.context.gateway_amt = self.gateway_amt
402            self.context.r_company = u'interswitch'
403        self.amount_auth = int(100 * self.context.amount_auth)
404        return
405
406class InterswitchPageApplicant(KofaPage):
407    """ View which sends a POST request to the Interswitch
408    CollegePAY payment gateway.
409    """
410    grok.context(INigeriaApplicantOnlinePayment)
411    grok.require('waeup.payApplicant')
412    grok.template('applicant_goto_interswitch')
413    grok.name('goto_interswitch')
414    label = _('Submit data to CollegePAY')
415    submit_button = _('Submit')
416
417    action = None
418    site_name = None
419    currency = None
420    pay_item_id = None
421    product_id = None
422    xml_data = None
423    hashvalue = None
424    gateway_amt = GATEWAY_AMT
425
426    def init_update(self):
427        if self.context.p_state != 'unpaid':
428            return _("Payment ticket can't be re-sent to CollegePAY.")
429        if self.context.__parent__.__parent__.expired \
430            and self.context.__parent__.__parent__.strict_deadline:
431            return _("Payment ticket can't be sent to CollegePAY. "
432                     "Application period has expired.")
433        if self.context.r_company and self.context.r_company != 'interswitch':
434            return _("Payment ticket has been used for another payment gateway.")
435        tz = getUtility(IKofaUtils).tzinfo
436        time_delta = datetime.utcnow() - self.context.creation_date
437        if time_delta.days > 7:
438            return _("This payment ticket is too old. Please create a new ticket.")
439        self.applicant = self.context.__parent__
440        self.category = self.context.category
441        tz = getUtility(IKofaUtils).tzinfo
442        self.local_date_time = to_timezone(
443            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
444        self.site_redirect_url = self.url(self.context, 'request_webservice')
445        return
446
447    def update(self):
448        if not module_activated(
449            self.context.__parent__.__parent__.year, self.context):
450            self.flash(_('Forbidden'), type='danger')
451            self.redirect(self.url(self.context, '@@index'))
452            return
453        error = self.init_update()
454        if error:
455            self.flash(error, type='danger')
456            self.redirect(self.url(self.context, '@@index'))
457        # Already now it becomes an Interswitch payment. We set the net amount
458        # and add the gateway amount.
459        if not self.context.r_company:
460            self.context.net_amt = self.context.amount_auth
461            self.context.amount_auth += self.gateway_amt
462            self.context.gateway_amt = self.gateway_amt
463            self.context.r_company = u'interswitch'
464        self.amount_auth = int(100 * self.context.amount_auth)
465        return
466
Note: See TracBrowser for help on using the repository browser.