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

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

Add semester.

  • Property svn:keywords set to Id
File size: 11.9 KB
RevLine 
[7929]1## $Id: browser.py 10932 2014-01-16 06:34:01Z 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
[10032]42class CustomPaymentDataWebservice(PaymentDataWebservice):
43    """A simple webservice to publish payment and payer details on request from
44    accepted IP addresses without authentication.
[8746]45
[10032]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')
[8769]51
[10032]52    #ACCEPTED_IP = ('195.219.3.181', '195.219.3.184')
53    ACCEPTED_IP = None
[8698]54
55    def update(self, PAYEE_ID=None):
[9508]56        if PAYEE_ID == None:
57            self.output = '-1'
58            return
[8746]59        real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)
[8769]60        # We can forego the logging once eTranzact payments run smoothly
61        # and the accepted IP addresses are used.
62        if real_ip:
[10032]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:
[8746]66                self.output = '-4'
67                return
[8754]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
[8704]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'
[8746]85            return
86        try:
[10032]87            owner = IPayer(results[0])
[8746]88            full_name = owner.display_fullname
89            matric_no = owner.id
90            faculty = owner.faculty
91            department = owner.department
[10907]92            study_type = owner.current_mode
93            email = owner.email
94            phone = owner.phone
95            level = owner.current_level
[8746]96        except (TypeError, AttributeError):
97            self.output = '-3'
98            return
99        amount = results[0].amount_auth
100        payment_type = results[0].category
101        programme_type = results[0].p_item
[10907]102
[8746]103        academic_session = academic_sessions_vocab.getTerm(
104            results[0].p_session).title
105        status = results[0].p_state
106        self.output = (
[10907]107            # Version 1
108            #'FULL_NAME=%s&' +
109            #'FACULTY=%s&' +
110            #'DEPARTMENT=%s&' +
111            #'RETURN_TYPE=%s&' +
112            #'PROGRAMME_TYPE=%s&' +
113            #'PAYMENT_TYPE=%s&' +
114            #'ACADEMIC_SESSION=%s&' +
115            #'MATRIC_NO=%s&' +
116            #'FEE_AMOUNT=%s&' +
117            #'TRANSACTION_STATUS=%s'
118
119            # Version 2
120            'PayeeName=%s~' +
121            'Faculty=%s~' +
122            'Department=%s~' +
123            'Level=%s~' +
124            'ProgrammeType=%s~' +
125            'StudyType=%s~' +
126            'Session=%s~' +
127            'PayeeID=%s~' +
128            'Amount=%s~' +
129            'FeeStatus=%s~' +
[10932]130            'Semester=N/A~' +
[10907]131            'PaymentType=%s~' +
132            'MatricNumber=%s~' +
133            'Email=%s~' +
134            'PhoneNumber=%s'
135
136            ) % (full_name, faculty,
137            department, level, programme_type, study_type,
138            academic_session, PAYEE_ID, amount, status, payment_type,
139            matric_no, email, phone)
[8698]140        return
141
142
143# Requerying eTranzact payments
144
[8776]145TERMINAL_ID = '0330000046'
146QUERY_URL =   'https://www.etranzact.net/Query/queryPayoutletTransaction.jsp'
[8259]147
[8680]148# Test environment
[8776]149#QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
150#TERMINAL_ID = '5009892289'
[8680]151
[8430]152def query_etranzact(confirmation_number, payment):
153   
[8247]154    postdict = {}
155    postdict['TERMINAL_ID'] = TERMINAL_ID
156    #postdict['RESPONSE_URL'] = 'http://dummy'
157    postdict['CONFIRMATION_NO'] = confirmation_number
[10070]158    data = urllib.urlencode(postdict)
[8682]159    payment.conf_number = confirmation_number
[8247]160    try:
[10069]161        # eTranzact only accepts HTTP 1.1 requests. Therefore
162        # the urllib2 package is required here.
163        f = urllib2.urlopen(url=QUERY_URL, data=data)
[8247]164        success = f.read()
[8432]165        success = success.replace('\r\n','')
[9935]166        if 'CUSTOMER_ID' not in success:
[8430]167            msg = _('Invalid or unsuccessful callback: ${a}',
168                mapping = {'a': success})
169            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]170            payment.p_state = 'failed'
[8430]171            return False, msg, log
[8247]172        success = success.replace('%20',' ').split('&')
173        # We expect at least two parameters
174        if len(success) < 2:
[8430]175            msg = _('Invalid callback: ${a}', mapping = {'a': success})
176            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]177            payment.p_state = 'failed'
[8430]178            return False, msg, log
[8247]179        try:
180            success_dict = dict([tuple(i.split('=')) for i in success])
181        except ValueError:
[8430]182            msg = _('Invalid callback: ${a}', mapping = {'a': success})
183            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]184            payment.p_state = 'failed'
[8430]185            return False, msg, log
[8247]186    except IOError:
[8430]187        msg = _('eTranzact IOError')
188        log = 'eTranzact IOError'
189        return False, msg, log
[8247]190    payment.r_code = u'ET'
[9327]191    payment.r_company = u'etranzact'
[8247]192    payment.r_desc = u'%s' % success_dict.get('TRANS_DESCR')
193    payment.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))
194    payment.r_card_num = None
195    payment.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')
196    if payment.r_amount_approved != payment.amount_auth:
[8430]197        msg = _('Wrong amount')
198        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
[8247]199        payment.p_state = 'failed'
[8430]200        return False, msg, log
[9935]201    customer_id = success_dict.get('CUSTOMER_ID')
202    if payment.p_id != customer_id:
[8717]203        msg = _('Wrong payment id')
[8430]204        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
[8247]205        payment.p_state = 'failed'
[8430]206        return False, msg, log
207    log = 'valid callback for payment %s: %s' % (payment.p_id, success)
208    msg = _('Successful callback received')
[8247]209    payment.p_state = 'paid'
[8433]210    payment.payment_date = datetime.utcnow()
[8430]211    return True, msg, log
[8247]212
[8430]213class EtranzactEnterPinActionButtonApplicant(APABApplicant):
[8253]214    grok.context(ICustomApplicantOnlinePayment)
[8430]215    grok.require('waeup.payApplicant')
[8259]216    grok.order(3)
[7929]217    icon = 'actionicon_call.png'
218    text = _('Query eTranzact History')
[7976]219    target = 'enterpin'
[7929]220
[8430]221class EtranzactEnterPinActionButtonStudent(APABStudent):
[8253]222    grok.context(ICustomStudentOnlinePayment)
[8430]223    grok.require('waeup.payStudent')
[8259]224    grok.order(3)
[8247]225    icon = 'actionicon_call.png'
226    text = _('Query eTranzact History')
227    target = 'enterpin'
228
229class EtranzactEnterPinPageStudent(KofaPage):
[7976]230    """
231    """
[8253]232    grok.context(ICustomStudentOnlinePayment)
[7976]233    grok.name('enterpin')
234    grok.template('enterpin')
[7929]235    grok.require('waeup.payStudent')
236
[7976]237    buttonname = _('Submit to eTranzact')
238    label = _('Requery eTranzact History')
239    action = 'query_history'
[7929]240
[8247]241class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):
242    """
243    """
244    grok.require('waeup.payApplicant')
[8253]245    grok.context(ICustomApplicantOnlinePayment)
[8247]246
247class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):
[7929]248    """ Query history of eTranzact payments
249    """
[8253]250    grok.context(ICustomStudentOnlinePayment)
[7929]251    grok.name('query_history')
252    grok.require('waeup.payStudent')
253
254    def update(self, confirmation_number=None):
255        if self.context.p_state == 'paid':
256            self.flash(_('This ticket has already been paid.'))
257            return
[8763]258        student = self.context.student
[8430]259        success, msg, log = query_etranzact(confirmation_number,self.context)
[8764]260        student.writeLogMessage(self, log)
[8430]261        if not success:
262            self.flash(msg)
263            return
264        success, msg, log = self.context.doAfterStudentPayment()
265        if log is not None:
[8764]266            student.writeLogMessage(self, log)
[8430]267        self.flash(msg)
[8247]268        return
[7929]269
[8247]270    def render(self):
271        self.redirect(self.url(self.context, '@@index'))
272        return
[7929]273
[8247]274class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):
275    """ Query history of eTranzact payments
276    """
[8253]277    grok.context(ICustomApplicantOnlinePayment)
[8247]278    grok.name('query_history')
279    grok.require('waeup.payApplicant')
280
281    def update(self, confirmation_number=None):
[8430]282        ob_class = self.__implemented__.__name__
[8247]283        if self.context.p_state == 'paid':
284            self.flash(_('This ticket has already been paid.'))
[7929]285            return
[8247]286        applicant = self.context.__parent__
[8430]287        success, msg, log = query_etranzact(confirmation_number,self.context)
[8769]288        applicant.writeLogMessage(self, log)
[8430]289        if not success:
290            self.flash(msg)
291            return
292        success, msg, log = self.context.doAfterApplicantPayment()
293        if log is not None:
[8769]294            applicant.writeLogMessage(self, log)
[8430]295        self.flash(msg)
[7929]296        return
297
298    def render(self):
299        self.redirect(self.url(self.context, '@@index'))
[8259]300        return
[9904]301
302# Disable Interswitch viewlets. This could be avoided by defining the
303# action button viewlets of kofacustom.nigeria.interswitch.browser in the
304# context of INigeriaStudentOnlinePayment or INigeriaApplicantOnlinePayment
305# respectively. But then all interswitch.browser modules have to be extended.
306
307class InterswitchActionButtonStudent(InterswitchActionButtonStudent):
308
309    @property
310    def target_url(self):
311        return ''
312
313class InterswitchRequestWebserviceActionButtonStudent(
314    InterswitchRequestWebserviceActionButtonStudent):
315
316    @property
317    def target_url(self):
318        return ''
319
320class InterswitchActionButtonApplicant(InterswitchActionButtonApplicant):
321
322    @property
323    def target_url(self):
324        return ''
325
326class InterswitchRequestWebserviceActionButtonApplicant(
327    InterswitchRequestWebserviceActionButtonApplicant):
328
329    @property
330    def target_url(self):
331        return ''
Note: See TracBrowser for help on using the repository browser.