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

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

Conform with the new eTranzact format.

  • Property svn:keywords set to Id
File size: 11.9 KB
RevLine 
[7929]1## $Id: browser.py 10907 2014-01-14 06:54:26Z 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~' +
130            'PaymentType=%s~' +
131            'MatricNumber=%s~' +
132            'Email=%s~' +
133            'PhoneNumber=%s'
134
135            ) % (full_name, faculty,
136            department, level, programme_type, study_type,
137            academic_session, PAYEE_ID, amount, status, payment_type,
138            matric_no, email, phone)
[8698]139        return
140
141
142# Requerying eTranzact payments
143
[8776]144TERMINAL_ID = '0330000046'
145QUERY_URL =   'https://www.etranzact.net/Query/queryPayoutletTransaction.jsp'
[8259]146
[8680]147# Test environment
[8776]148#QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
149#TERMINAL_ID = '5009892289'
[8680]150
[8430]151def query_etranzact(confirmation_number, payment):
152   
[8247]153    postdict = {}
154    postdict['TERMINAL_ID'] = TERMINAL_ID
155    #postdict['RESPONSE_URL'] = 'http://dummy'
156    postdict['CONFIRMATION_NO'] = confirmation_number
[10070]157    data = urllib.urlencode(postdict)
[8682]158    payment.conf_number = confirmation_number
[8247]159    try:
[10069]160        # eTranzact only accepts HTTP 1.1 requests. Therefore
161        # the urllib2 package is required here.
162        f = urllib2.urlopen(url=QUERY_URL, data=data)
[8247]163        success = f.read()
[8432]164        success = success.replace('\r\n','')
[9935]165        if 'CUSTOMER_ID' not in success:
[8430]166            msg = _('Invalid or unsuccessful callback: ${a}',
167                mapping = {'a': success})
168            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]169            payment.p_state = 'failed'
[8430]170            return False, msg, log
[8247]171        success = success.replace('%20',' ').split('&')
172        # We expect at least two parameters
173        if len(success) < 2:
[8430]174            msg = _('Invalid callback: ${a}', mapping = {'a': success})
175            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]176            payment.p_state = 'failed'
[8430]177            return False, msg, log
[8247]178        try:
179            success_dict = dict([tuple(i.split('=')) for i in success])
180        except ValueError:
[8430]181            msg = _('Invalid callback: ${a}', mapping = {'a': success})
182            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
[8247]183            payment.p_state = 'failed'
[8430]184            return False, msg, log
[8247]185    except IOError:
[8430]186        msg = _('eTranzact IOError')
187        log = 'eTranzact IOError'
188        return False, msg, log
[8247]189    payment.r_code = u'ET'
[9327]190    payment.r_company = u'etranzact'
[8247]191    payment.r_desc = u'%s' % success_dict.get('TRANS_DESCR')
192    payment.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))
193    payment.r_card_num = None
194    payment.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')
195    if payment.r_amount_approved != payment.amount_auth:
[8430]196        msg = _('Wrong amount')
197        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
[8247]198        payment.p_state = 'failed'
[8430]199        return False, msg, log
[9935]200    customer_id = success_dict.get('CUSTOMER_ID')
201    if payment.p_id != customer_id:
[8717]202        msg = _('Wrong payment id')
[8430]203        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
[8247]204        payment.p_state = 'failed'
[8430]205        return False, msg, log
206    log = 'valid callback for payment %s: %s' % (payment.p_id, success)
207    msg = _('Successful callback received')
[8247]208    payment.p_state = 'paid'
[8433]209    payment.payment_date = datetime.utcnow()
[8430]210    return True, msg, log
[8247]211
[8430]212class EtranzactEnterPinActionButtonApplicant(APABApplicant):
[8253]213    grok.context(ICustomApplicantOnlinePayment)
[8430]214    grok.require('waeup.payApplicant')
[8259]215    grok.order(3)
[7929]216    icon = 'actionicon_call.png'
217    text = _('Query eTranzact History')
[7976]218    target = 'enterpin'
[7929]219
[8430]220class EtranzactEnterPinActionButtonStudent(APABStudent):
[8253]221    grok.context(ICustomStudentOnlinePayment)
[8430]222    grok.require('waeup.payStudent')
[8259]223    grok.order(3)
[8247]224    icon = 'actionicon_call.png'
225    text = _('Query eTranzact History')
226    target = 'enterpin'
227
228class EtranzactEnterPinPageStudent(KofaPage):
[7976]229    """
230    """
[8253]231    grok.context(ICustomStudentOnlinePayment)
[7976]232    grok.name('enterpin')
233    grok.template('enterpin')
[7929]234    grok.require('waeup.payStudent')
235
[7976]236    buttonname = _('Submit to eTranzact')
237    label = _('Requery eTranzact History')
238    action = 'query_history'
[7929]239
[8247]240class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):
241    """
242    """
243    grok.require('waeup.payApplicant')
[8253]244    grok.context(ICustomApplicantOnlinePayment)
[8247]245
246class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):
[7929]247    """ Query history of eTranzact payments
248    """
[8253]249    grok.context(ICustomStudentOnlinePayment)
[7929]250    grok.name('query_history')
251    grok.require('waeup.payStudent')
252
253    def update(self, confirmation_number=None):
254        if self.context.p_state == 'paid':
255            self.flash(_('This ticket has already been paid.'))
256            return
[8763]257        student = self.context.student
[8430]258        success, msg, log = query_etranzact(confirmation_number,self.context)
[8764]259        student.writeLogMessage(self, log)
[8430]260        if not success:
261            self.flash(msg)
262            return
263        success, msg, log = self.context.doAfterStudentPayment()
264        if log is not None:
[8764]265            student.writeLogMessage(self, log)
[8430]266        self.flash(msg)
[8247]267        return
[7929]268
[8247]269    def render(self):
270        self.redirect(self.url(self.context, '@@index'))
271        return
[7929]272
[8247]273class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):
274    """ Query history of eTranzact payments
275    """
[8253]276    grok.context(ICustomApplicantOnlinePayment)
[8247]277    grok.name('query_history')
278    grok.require('waeup.payApplicant')
279
280    def update(self, confirmation_number=None):
[8430]281        ob_class = self.__implemented__.__name__
[8247]282        if self.context.p_state == 'paid':
283            self.flash(_('This ticket has already been paid.'))
[7929]284            return
[8247]285        applicant = self.context.__parent__
[8430]286        success, msg, log = query_etranzact(confirmation_number,self.context)
[8769]287        applicant.writeLogMessage(self, log)
[8430]288        if not success:
289            self.flash(msg)
290            return
291        success, msg, log = self.context.doAfterApplicantPayment()
292        if log is not None:
[8769]293            applicant.writeLogMessage(self, log)
[8430]294        self.flash(msg)
[7929]295        return
296
297    def render(self):
298        self.redirect(self.url(self.context, '@@index'))
[8259]299        return
[9904]300
301# Disable Interswitch viewlets. This could be avoided by defining the
302# action button viewlets of kofacustom.nigeria.interswitch.browser in the
303# context of INigeriaStudentOnlinePayment or INigeriaApplicantOnlinePayment
304# respectively. But then all interswitch.browser modules have to be extended.
305
306class InterswitchActionButtonStudent(InterswitchActionButtonStudent):
307
308    @property
309    def target_url(self):
310        return ''
311
312class InterswitchRequestWebserviceActionButtonStudent(
313    InterswitchRequestWebserviceActionButtonStudent):
314
315    @property
316    def target_url(self):
317        return ''
318
319class InterswitchActionButtonApplicant(InterswitchActionButtonApplicant):
320
321    @property
322    def target_url(self):
323        return ''
324
325class InterswitchRequestWebserviceActionButtonApplicant(
326    InterswitchRequestWebserviceActionButtonApplicant):
327
328    @property
329    def target_url(self):
330        return ''
Note: See TracBrowser for help on using the repository browser.