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

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

Catch AttributeError?: 'module' object has no attribute 'urlencode'

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