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

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

Replace COL1 by CUSTOMER_ID.

  • Property svn:keywords set to Id
File size: 10.8 KB
Line 
1## $Id: browser.py 9935 2013-02-06 15:06: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 'CUSTOMER_ID' 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    customer_id = success_dict.get('CUSTOMER_ID')
173    if payment.p_id != customer_id:
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
272
273# Disable Interswitch viewlets. This could be avoided by defining the
274# action button viewlets of kofacustom.nigeria.interswitch.browser in the
275# context of INigeriaStudentOnlinePayment or INigeriaApplicantOnlinePayment
276# respectively. But then all interswitch.browser modules have to be extended.
277
278class InterswitchActionButtonStudent(InterswitchActionButtonStudent):
279
280    @property
281    def target_url(self):
282        return ''
283
284class InterswitchRequestWebserviceActionButtonStudent(
285    InterswitchRequestWebserviceActionButtonStudent):
286
287    @property
288    def target_url(self):
289        return ''
290
291class InterswitchActionButtonApplicant(InterswitchActionButtonApplicant):
292
293    @property
294    def target_url(self):
295        return ''
296
297class InterswitchRequestWebserviceActionButtonApplicant(
298    InterswitchRequestWebserviceActionButtonApplicant):
299
300    @property
301    def target_url(self):
302        return ''
Note: See TracBrowser for help on using the repository browser.