source: main/waeup.aaue/branches/henrik-diazo-themed/src/waeup/aaue/etranzact/browser.py @ 11348

Last change on this file since 11348 was 11100, checked in by Henrik Bettermann, 11 years ago

Diazofy first custom package.

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