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

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

Class names have changed.

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