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
Line 
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
23from zope.component import getUtility
24from zope.catalog.interfaces import ICatalog
25from waeup.kofa.interfaces import IUniversity
26from waeup.kofa.payments.interfaces import IPayer
27from waeup.kofa.webservices import PaymentDataWebservice
28from waeup.kofa.browser.layout import KofaPage, UtilityView
29from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
30from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
31from waeup.aaue.interfaces import academic_sessions_vocab
32from kofacustom.nigeria.interswitch.browser import (
33    InterswitchActionButtonStudent,
34    InterswitchRequestWebserviceActionButtonStudent,
35    InterswitchActionButtonApplicant,
36    InterswitchRequestWebserviceActionButtonApplicant)
37from waeup.aaue.interfaces import MessageFactory as _
38from waeup.aaue.students.interfaces import ICustomStudentOnlinePayment
39from waeup.aaue.applicants.interfaces import ICustomApplicantOnlinePayment
40
41class CustomPaymentDataWebservice(PaymentDataWebservice):
42    """A simple webservice to publish payment and payer details on request from
43    accepted IP addresses without authentication.
44
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')
50
51    #ACCEPTED_IP = ('195.219.3.181', '195.219.3.184')
52    ACCEPTED_IP = None
53
54    def update(self, PAYEE_ID=None):
55        if PAYEE_ID == None:
56            self.output = '-1'
57            return
58        real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)
59        # We can forego the logging once eTranzact payments run smoothly
60        # and the accepted IP addresses are used.
61        if real_ip:
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:
65                self.output = '-4'
66                return
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
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'
84            return
85        try:
86            owner = IPayer(results[0])
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)
113        return
114
115
116# Requerying eTranzact payments
117
118TERMINAL_ID = '0330000046'
119QUERY_URL =   'https://www.etranzact.net/Query/queryPayoutletTransaction.jsp'
120
121# Test environment
122#QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
123#TERMINAL_ID = '5009892289'
124
125def query_etranzact(confirmation_number, payment):
126   
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)
132    payment.conf_number = confirmation_number
133    try:
134        f = urllib.urlopen(url=QUERY_URL, data=data)
135        success = f.read()
136        success = success.replace('\r\n','')
137        if 'CUSTOMER_ID' not in success:
138            msg = _('Invalid or unsuccessful callback: ${a}',
139                mapping = {'a': success})
140            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
141            payment.p_state = 'failed'
142            return False, msg, log
143        success = success.replace('%20',' ').split('&')
144        # We expect at least two parameters
145        if len(success) < 2:
146            msg = _('Invalid callback: ${a}', mapping = {'a': success})
147            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
148            payment.p_state = 'failed'
149            return False, msg, log
150        try:
151            success_dict = dict([tuple(i.split('=')) for i in success])
152        except ValueError:
153            msg = _('Invalid callback: ${a}', mapping = {'a': success})
154            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
155            payment.p_state = 'failed'
156            return False, msg, log
157    except IOError:
158        msg = _('eTranzact IOError')
159        log = 'eTranzact IOError'
160        return False, msg, log
161    payment.r_code = u'ET'
162    payment.r_company = u'etranzact'
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:
168        msg = _('Wrong amount')
169        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
170        payment.p_state = 'failed'
171        return False, msg, log
172    customer_id = success_dict.get('CUSTOMER_ID')
173    if payment.p_id != customer_id:
174        msg = _('Wrong payment id')
175        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
176        payment.p_state = 'failed'
177        return False, msg, log
178    log = 'valid callback for payment %s: %s' % (payment.p_id, success)
179    msg = _('Successful callback received')
180    payment.p_state = 'paid'
181    payment.payment_date = datetime.utcnow()
182    return True, msg, log
183
184class EtranzactEnterPinActionButtonApplicant(APABApplicant):
185    grok.context(ICustomApplicantOnlinePayment)
186    grok.require('waeup.payApplicant')
187    grok.order(3)
188    icon = 'actionicon_call.png'
189    text = _('Query eTranzact History')
190    target = 'enterpin'
191
192class EtranzactEnterPinActionButtonStudent(APABStudent):
193    grok.context(ICustomStudentOnlinePayment)
194    grok.require('waeup.payStudent')
195    grok.order(3)
196    icon = 'actionicon_call.png'
197    text = _('Query eTranzact History')
198    target = 'enterpin'
199
200class EtranzactEnterPinPageStudent(KofaPage):
201    """
202    """
203    grok.context(ICustomStudentOnlinePayment)
204    grok.name('enterpin')
205    grok.template('enterpin')
206    grok.require('waeup.payStudent')
207
208    buttonname = _('Submit to eTranzact')
209    label = _('Requery eTranzact History')
210    action = 'query_history'
211
212class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):
213    """
214    """
215    grok.require('waeup.payApplicant')
216    grok.context(ICustomApplicantOnlinePayment)
217
218class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):
219    """ Query history of eTranzact payments
220    """
221    grok.context(ICustomStudentOnlinePayment)
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
229        student = self.context.student
230        success, msg, log = query_etranzact(confirmation_number,self.context)
231        student.writeLogMessage(self, log)
232        if not success:
233            self.flash(msg)
234            return
235        success, msg, log = self.context.doAfterStudentPayment()
236        if log is not None:
237            student.writeLogMessage(self, log)
238        self.flash(msg)
239        return
240
241    def render(self):
242        self.redirect(self.url(self.context, '@@index'))
243        return
244
245class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):
246    """ Query history of eTranzact payments
247    """
248    grok.context(ICustomApplicantOnlinePayment)
249    grok.name('query_history')
250    grok.require('waeup.payApplicant')
251
252    def update(self, confirmation_number=None):
253        ob_class = self.__implemented__.__name__
254        if self.context.p_state == 'paid':
255            self.flash(_('This ticket has already been paid.'))
256            return
257        applicant = self.context.__parent__
258        success, msg, log = query_etranzact(confirmation_number,self.context)
259        applicant.writeLogMessage(self, log)
260        if not success:
261            self.flash(msg)
262            return
263        success, msg, log = self.context.doAfterApplicantPayment()
264        if log is not None:
265            applicant.writeLogMessage(self, log)
266        self.flash(msg)
267        return
268
269    def render(self):
270        self.redirect(self.url(self.context, '@@index'))
271        return
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.