source: main/waeup.aaue/trunk/src/waeup/aaue/etranzact/browser.py @ 9508

Last change on this file since 9508 was 9508, checked in by Henrik Bettermann, 12 years ago

Add more security.

  • Property svn:keywords set to Id
File size: 9.9 KB
Line 
1## $Id: browser.py 9508 2012-11-02 10:50:58Z 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##
18from datetime import datetime
19import httplib
20import urllib
21from xml.dom.minidom import parseString
22import grok
23from zope.component import getUtility
24from zope.catalog.interfaces import ICatalog
25from waeup.kofa.interfaces import IUniversity
26from waeup.kofa.payments.interfaces import IPaymentWebservice
27from waeup.kofa.browser.layout import KofaPage, UtilityView
28from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
29from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
30from waeup.aaue.interfaces import academic_sessions_vocab
31from waeup.aaue.interfaces import MessageFactory as _
32from waeup.aaue.students.interfaces import ICustomStudentOnlinePayment
33from waeup.aaue.applicants.interfaces import ICustomApplicantOnlinePayment
34
35#ACCEPTED_IP = ('195.219.3.181', '195.219.3.184')
36ACCEPTED_IP = None
37
38
39# Kofa's webservice
40
41class KofaFeeRequest(grok.View):
42    grok.context(IUniversity)
43    grok.name('feerequest')
44    grok.require('waeup.Public')
45
46    def update(self, PAYEE_ID=None):
47        if PAYEE_ID == None:
48            self.output = '-1'
49            return
50        real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)
51        # We can forego the logging once eTranzact payments run smoothly
52        # and the accepted IP addresses are used.
53        if real_ip:
54            self.context.logger.info('KofaFeeRequest called: %s' % real_ip)
55        if real_ip  and ACCEPTED_IP:
56            if real_ip not in  ACCEPTED_IP:
57                self.output = '-4'
58                return
59
60        # It seems eTranzact sends a POST request with an empty body but the URL
61        # contains a query string. So it's actually a GET request pretended
62        # to be a POST request. Although this does not comply with the
63        # RFC 2616 HTTP guidelines we may try to fetch the id from the QUERY_STRING
64        # value of the request.
65        #if PAYEE_ID is None:
66        #    try:
67        #        PAYEE_ID = self.request['QUERY_STRING'].split('=')[1]
68        #    except:
69        #        self.output = '-2'
70        #        return
71
72        cat = getUtility(ICatalog, name='payments_catalog')
73        results = list(cat.searchResults(p_id=(PAYEE_ID, PAYEE_ID)))
74        if len(results) != 1:
75            self.output = '-1'
76            return
77        try:
78            owner = IPaymentWebservice(results[0])
79            full_name = owner.display_fullname
80            matric_no = owner.id
81            faculty = owner.faculty
82            department = owner.department
83        except (TypeError, AttributeError):
84            self.output = '-3'
85            return
86        amount = results[0].amount_auth
87        payment_type = results[0].category
88        programme_type = results[0].p_item
89        academic_session = academic_sessions_vocab.getTerm(
90            results[0].p_session).title
91        status = results[0].p_state
92        self.output = (
93            'FULL_NAME=%s&' +
94            'FACULTY=%s&' +
95            'DEPARTMENT=%s&' +
96            'RETURN_TYPE=%s&' +
97            'PROGRAMME_TYPE=%s&' +
98            'PAYMENT_TYPE=%s&' +
99            'ACADEMIC_SESSION=%s&' +
100            'MATRIC_NO=%s&' +
101            'FEE_AMOUNT=%s&' +
102            'TRANSACTION_STATUS=%s') % (full_name, faculty,
103            department, PAYEE_ID, programme_type, payment_type,
104            academic_session, matric_no, amount, status)
105        return
106
107    def render(self):
108        return self.output
109
110
111# Requerying eTranzact payments
112
113TERMINAL_ID = '0330000046'
114QUERY_URL =   'https://www.etranzact.net/Query/queryPayoutletTransaction.jsp'
115
116# Test environment
117#QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
118#TERMINAL_ID = '5009892289'
119
120def query_etranzact(confirmation_number, payment):
121   
122    postdict = {}
123    postdict['TERMINAL_ID'] = TERMINAL_ID
124    #postdict['RESPONSE_URL'] = 'http://dummy'
125    postdict['CONFIRMATION_NO'] = confirmation_number
126    data = urllib.urlencode(postdict)
127    payment.conf_number = confirmation_number
128    try:
129        f = urllib.urlopen(url=QUERY_URL, data=data)
130        success = f.read()
131        success = success.replace('\r\n','')
132        if 'COL6' not in success:
133            msg = _('Invalid or unsuccessful callback: ${a}',
134                mapping = {'a': success})
135            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
136            payment.p_state = 'failed'
137            return False, msg, log
138        success = success.replace('%20',' ').split('&')
139        # We expect at least two parameters
140        if len(success) < 2:
141            msg = _('Invalid callback: ${a}', mapping = {'a': success})
142            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
143            payment.p_state = 'failed'
144            return False, msg, log
145        try:
146            success_dict = dict([tuple(i.split('=')) for i in success])
147        except ValueError:
148            msg = _('Invalid callback: ${a}', mapping = {'a': success})
149            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
150            payment.p_state = 'failed'
151            return False, msg, log
152    except IOError:
153        msg = _('eTranzact IOError')
154        log = 'eTranzact IOError'
155        return False, msg, log
156    payment.r_code = u'ET'
157    payment.r_company = u'etranzact'
158    payment.r_desc = u'%s' % success_dict.get('TRANS_DESCR')
159    payment.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))
160    payment.r_card_num = None
161    payment.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')
162    if payment.r_amount_approved != payment.amount_auth:
163        msg = _('Wrong amount')
164        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
165        payment.p_state = 'failed'
166        return False, msg, log
167    #tcode = payment.p_id
168    #tcode = tcode[len(tcode)-8:len(tcode)]
169    col1 = success_dict.get('COL1')
170    #col1 = col1[len(col1)-8:len(col1)]
171    #if tcode != col1:
172    if payment.p_id != col1:
173        #msg = _('Wrong transaction code')
174        msg = _('Wrong payment id')
175        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
176        payment.p_state = 'failed'
177        return False, msg, log
178    log = 'valid callback for payment %s: %s' % (payment.p_id, success)
179    msg = _('Successful callback received')
180    payment.p_state = 'paid'
181    payment.payment_date = datetime.utcnow()
182    return True, msg, log
183
184class EtranzactEnterPinActionButtonApplicant(APABApplicant):
185    grok.context(ICustomApplicantOnlinePayment)
186    grok.require('waeup.payApplicant')
187    grok.order(3)
188    icon = 'actionicon_call.png'
189    text = _('Query eTranzact History')
190    target = 'enterpin'
191
192class EtranzactEnterPinActionButtonStudent(APABStudent):
193    grok.context(ICustomStudentOnlinePayment)
194    grok.require('waeup.payStudent')
195    grok.order(3)
196    icon = 'actionicon_call.png'
197    text = _('Query eTranzact History')
198    target = 'enterpin'
199
200class EtranzactEnterPinPageStudent(KofaPage):
201    """
202    """
203    grok.context(ICustomStudentOnlinePayment)
204    grok.name('enterpin')
205    grok.template('enterpin')
206    grok.require('waeup.payStudent')
207
208    buttonname = _('Submit to eTranzact')
209    label = _('Requery eTranzact History')
210    action = 'query_history'
211
212class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):
213    """
214    """
215    grok.require('waeup.payApplicant')
216    grok.context(ICustomApplicantOnlinePayment)
217
218class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):
219    """ Query history of eTranzact payments
220    """
221    grok.context(ICustomStudentOnlinePayment)
222    grok.name('query_history')
223    grok.require('waeup.payStudent')
224
225    def update(self, confirmation_number=None):
226        if self.context.p_state == 'paid':
227            self.flash(_('This ticket has already been paid.'))
228            return
229        student = self.context.student
230        success, msg, log = query_etranzact(confirmation_number,self.context)
231        student.writeLogMessage(self, log)
232        if not success:
233            self.flash(msg)
234            return
235        success, msg, log = self.context.doAfterStudentPayment()
236        if log is not None:
237            student.writeLogMessage(self, log)
238        self.flash(msg)
239        return
240
241    def render(self):
242        self.redirect(self.url(self.context, '@@index'))
243        return
244
245class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):
246    """ Query history of eTranzact payments
247    """
248    grok.context(ICustomApplicantOnlinePayment)
249    grok.name('query_history')
250    grok.require('waeup.payApplicant')
251
252    def update(self, confirmation_number=None):
253        ob_class = self.__implemented__.__name__
254        if self.context.p_state == 'paid':
255            self.flash(_('This ticket has already been paid.'))
256            return
257        applicant = self.context.__parent__
258        success, msg, log = query_etranzact(confirmation_number,self.context)
259        applicant.writeLogMessage(self, log)
260        if not success:
261            self.flash(msg)
262            return
263        success, msg, log = self.context.doAfterApplicantPayment()
264        if log is not None:
265            applicant.writeLogMessage(self, log)
266        self.flash(msg)
267        return
268
269    def render(self):
270        self.redirect(self.url(self.context, '@@index'))
271        return
Note: See TracBrowser for help on using the repository browser.