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

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

It's COL1 not COL6 where etranzact forwards the p_id.

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