source: main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser.py @ 9147

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

Fix if statement. PG students are in level 999. We can use the is_postgrad property.

  • Property svn:keywords set to Id
File size: 19.3 KB
Line 
1## $Id: browser.py 9081 2012-08-03 21:21:32Z 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 waeup.kofa.browser.layout import KofaPage, UtilityView
25from waeup.kofa.accesscodes import create_accesscode
26from waeup.kofa.interfaces import RETURNING, IKofaUtils
27from waeup.kofa.utils.helpers import to_timezone
28from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
29from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
30from waeup.kofa.payments.interfaces import payment_categories
31from waeup.uniben.students.interfaces import ICustomStudentOnlinePayment
32from waeup.uniben.applicants.interfaces import ICustomApplicantOnlinePayment
33from waeup.uniben.interfaces import MessageFactory as _
34
35PRODUCT_ID = '57'
36SITE_NAME = 'uniben-kofa.waeup.org'
37PROVIDER_ACCT = '1010764827'
38PROVIDER_BANK_ID = '117'
39PROVIDER_ITEM_NAME = 'BT Education'
40INSTITUTION_NAME = 'Uniben'
41CURRENCY = '566'
42#QUERY_URL = 'https://webpay.interswitchng.com/paydirect/services/TransactionQueryURL.aspx'
43#QUERY_URL = 'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
44POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
45#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
46
47HOST = 'webpay.interswitchng.com'
48#HOST = 'testwebpay.interswitchng.com'
49URL = '/paydirect/services/TransactionQueryWs.asmx'
50#URL = '/test_paydirect/services/TransactionQueryWs.asmx'
51httplib.HTTPConnection.debuglevel = 0
52
53
54def SOAP_post(soap_action,xml):
55    """Handles making the SOAP request.
56
57    Further reading:
58    http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
59    """
60    h = httplib.HTTPConnection(HOST)
61    headers={
62        'Host':HOST,
63        'Content-Type':'text/xml; charset=utf-8',
64        'Content-Length':len(xml),
65        'SOAPAction':'"%s"' % soap_action,
66    }
67    h.request('POST', URL, body=xml,headers=headers)
68    r = h.getresponse()
69    d = r.read()
70    if r.status!=200:
71        raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
72    return d
73
74def get_SOAP_response(product_id, transref):
75    xml="""\
76<?xml version="1.0" encoding="utf-8"?>
77<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
78  <soap:Body>
79    <getTransactionData xmlns="http://tempuri.org/">
80      <product_id>%s</product_id>
81      <trans_ref>%s</trans_ref>
82    </getTransactionData>
83  </soap:Body>
84</soap:Envelope>""" % (product_id, transref)
85    result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
86    doc=parseString(result_xml)
87    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
88    return response
89
90def query_interswitch(payment):
91    sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
92    wlist = sr.split(':')
93    if len(wlist) != 7:
94        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
95        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
96        return False, msg, log
97    payment.r_code = wlist[0]
98    payment.r_desc = wlist[1]
99    payment.r_amount_approved = float(wlist[2]) / 100
100    payment.r_card_num = wlist[3]
101    payment.r_pay_reference = wlist[5]
102    payment.r_company = u'interswitch'
103    if payment.r_code != '00':
104        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
105        log = 'unsuccessful callback for payment %s: %s' % (payment.p_id, sr)
106        payment.p_state = 'failed'
107        return False, msg, log
108    if payment.r_amount_approved != payment.amount_auth:
109        msg = _('Callback amount does not match.')
110        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
111        payment.p_state = 'failed'
112        return False, msg, log
113    if wlist[4] != payment.p_id:
114        msg = _('Callback transaction id does not match.')
115        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
116        payment.p_state = 'failed'
117        return False, msg, log
118    payment.p_state = 'paid'
119    payment.payment_date = datetime.utcnow()
120    msg = _('Successful callback received')
121    log = 'valid callback for payment %s: %s' % (payment.p_id, sr)
122    return True, msg, log
123
124class InterswitchActionButtonStudent(APABStudent):
125    grok.order(1)
126    grok.context(ICustomStudentOnlinePayment)
127    grok.require('waeup.payStudent')
128    icon = 'actionicon_pay.png'
129    text = _('CollegePAY')
130    target = 'goto_interswitch'
131
132    @property
133    def target_url(self):
134        if self.context.p_state != 'unpaid':
135            return ''
136        return self.view.url(self.view.context, self.target)
137
138class InterswitchActionButtonApplicant(APABApplicant):
139    grok.order(1)
140    grok.context(ICustomApplicantOnlinePayment)
141    grok.require('waeup.payApplicant')
142    icon = 'actionicon_pay.png'
143    text = _('CollegePAY')
144    target = 'goto_interswitch'
145
146    @property
147    def target_url(self):
148        if self.context.p_state != 'unpaid':
149            return ''
150        return self.view.url(self.view.context, self.target)
151
152# Deprecated
153#class InterswitchRequestCallbackActionButtonStudent(RCABStudent):
154#    grok.order(3)
155#    grok.context(ICustomStudentOnlinePayment)
156#    icon = 'actionicon_call.png'
157#    text = _('Request CollegePAY callback')
158
159#    def target_url(self):
160#        if self.context.p_state == 'paid':
161#            return ''
162#        site_redirect_url = self.view.url(self.view.context, 'isw_callback')
163#        args = {
164#            'transRef':self.context.p_id,
165#            'prodID':PRODUCT_ID,
166#            'redirectURL':site_redirect_url}
167#        return QUERY_URL + '?%s' % urllib.urlencode(args)
168
169# Alternative preferred solution
170class InterswitchRequestWebserviceActionButtonStudent(APABStudent):
171    grok.order(2)
172    grok.context(ICustomStudentOnlinePayment)
173    grok.require('waeup.payStudent')
174    icon = 'actionicon_call.png'
175    text = _('Requery CollegePAY')
176    target = 'request_webservice'
177
178class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
179    grok.order(2)
180    grok.context(ICustomApplicantOnlinePayment)
181    grok.require('waeup.payApplicant')
182    icon = 'actionicon_call.png'
183    text = _('Requery CollegePAY')
184    target = 'request_webservice'
185
186
187class InterswitchPageStudent(KofaPage):
188    """ View which sends a POST request to the Interswitch
189    CollegePAY payment gateway.
190    """
191    grok.context(ICustomStudentOnlinePayment)
192    grok.name('goto_interswitch')
193    grok.template('student_goto_interswitch')
194    grok.require('waeup.payStudent')
195    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
196    submit_button = _('Submit')
197    action = POST_ACTION
198    site_name = SITE_NAME
199    currency = CURRENCY
200    pay_item_id = '5700'
201    product_id = PRODUCT_ID
202
203    def update(self):
204        #if self.context.p_state != 'unpaid':
205        if self.context.p_state == 'paid':
206            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
207            self.redirect(self.url(self.context, '@@index'))
208            return
209
210        student = self.student = self.context.student
211        certificate = getattr(student['studycourse'],'certificate',None)
212        self.amount_auth = 100 * self.context.amount_auth
213        xmldict = {}
214        if certificate is not None:
215            xmldict['department'] = certificate.__parent__.__parent__.code
216            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
217        else:
218            xmldict['department'] = None
219            xmldict['faculty'] = None
220        self.category = payment_categories.getTermByToken(
221            self.context.p_category).title
222        tz = getUtility(IKofaUtils).tzinfo
223        self.local_date_time = to_timezone(
224            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
225        self.site_redirect_url = self.url(self.context, 'request_webservice')
226        # Provider data
227        xmldict['detail_ref'] = self.context.p_id
228        xmldict['provider_acct'] = PROVIDER_ACCT
229        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
230        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
231        if student.current_mode.endswith('_ft') \
232            and student.state == RETURNING:
233            provider_amt = 600
234        else:
235            provider_amt = 1500
236        xmldict['provider_amt'] = 100 * provider_amt
237        # Institution data
238        studycourse = student['studycourse']
239        xmldict['institution_acct'] = ''
240        xmldict['institution_bank_id'] = ''
241        if student.current_mode.endswith('_ft'):
242            #post-grad full-time students of all faculties
243            if student.is_postgrad:
244                xmldict['institution_acct'] = '1012842833'
245                xmldict['institution_bank_id'] = '117'
246            #all other part-time students depending on faculty
247            elif student.faccode in ('SSC','LAW','MED'):
248                xmldict['institution_acct'] = '0005986938'
249                xmldict['institution_bank_id'] = '31'
250            elif student.faccode in ('ENG','PSC','PHA'):
251                xmldict['institution_acct'] = '0014413973'
252                xmldict['institution_bank_id'] = '129'
253            elif student.faccode in ('LSC','DEN','AGR'):
254                xmldict['institution_acct'] = '1012801319'
255                xmldict['institution_bank_id'] = '117'
256            elif student.faccode in ('ART','EDU','MGS','BMS'):
257                xmldict['institution_acct'] = '6220027556'
258                xmldict['institution_bank_id'] = '51'
259        elif student.current_mode.endswith('_pt'):
260            #post-grad part-time students of all faculties
261            if student.is_postgrad:
262                xmldict['institution_acct'] = '0023708207'
263                xmldict['institution_bank_id'] = '72'
264            #all other part-time students depending on faculty
265            elif student.faccode in ('ENG','LAW','MGS'):
266                xmldict['institution_acct'] = '2019006824'
267                xmldict['institution_bank_id'] = '8'
268            elif student.faccode in ('IPA','PHA','SSC','AGR','EDU'):
269                xmldict['institution_acct'] = '0122012109'
270                xmldict['institution_bank_id'] = '16'
271        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
272        xmldict['institution_item_name'] = self.context.p_category
273        xmldict['institution_name'] = INSTITUTION_NAME
274        # Interswitch amount is not part of the xml data
275        xmltext = """<payment_item_detail>
276<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
277<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
278<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
279</item_details>
280</payment_item_detail>""" % xmldict
281        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
282        return
283
284class InterswitchPageApplicant(KofaPage):
285    """ View which sends a POST request to the Interswitch
286    CollegePAY payment gateway.
287    """
288    grok.context(ICustomApplicantOnlinePayment)
289    grok.require('waeup.payApplicant')
290    grok.template('applicant_goto_interswitch')
291    grok.name('goto_interswitch')
292    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
293    submit_button = _('Submit')
294    action = POST_ACTION
295    site_name = SITE_NAME
296    currency = CURRENCY
297    pay_item_id = '5703'
298    product_id = PRODUCT_ID
299
300    def update(self):
301        if self.context.p_state != 'unpaid':
302            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
303            self.redirect(self.url(self.context, '@@index'))
304            return
305        if self.context.__parent__.__parent__.expired \
306            and self.context.__parent__.__parent__.strict_deadline:
307            self.flash(_("Payment ticket can't be send to CollegePAY. "
308                         "Application period has expired."))
309            self.redirect(self.url(self.context, '@@index'))
310            return
311        self.applicant = self.context.__parent__
312        self.amount_auth = 100 * self.context.amount_auth
313        xmldict = {}
314        self.category = payment_categories.getTermByToken(
315            self.context.p_category).title
316        tz = getUtility(IKofaUtils).tzinfo
317        self.local_date_time = to_timezone(
318            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
319        self.site_redirect_url = self.url(self.context, 'request_webservice')
320        if self.applicant.applicant_id.startswith('pg'):
321            provider_amt = 400
322            xmldict['institution_acct'] = '0031716030'
323            xmldict['institution_bank_id'] = '10'
324        else:
325            provider_amt = 250
326            xmldict['institution_acct'] = '6220032503'
327            xmldict['institution_bank_id'] = '51'
328        xmldict['detail_ref'] = self.context.p_id
329        xmldict['provider_amt'] = 100 * provider_amt
330        xmldict['provider_acct'] = PROVIDER_ACCT
331        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
332        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
333        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
334        xmldict['institution_item_name'] = self.context.p_category
335        xmldict['institution_name'] = INSTITUTION_NAME
336        # Interswitch amount is not part of the xml data
337        xmltext = """<payment_item_detail>
338<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
339<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
340<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
341</item_details>
342</payment_item_detail>""" % xmldict
343        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
344        return
345
346# Deprecated
347#class InterswitchPaymentCallbackPageStudent(UtilityView, grok.View):
348#    """ Callback view for the CollegePAY gateway
349#    """
350#    grok.context(ICustomStudentOnlinePayment)
351#    grok.name('isw_callback')
352#    grok.require('waeup.payStudent')
353
354    # This view is not yet working for offline querying transactions
355    # since the query string differs from the query string sent after
356    # posting transactions. This Interswitch bug must be removed first.
357    # Alternatively, we could use the webservice only and replace
358    # the RequestCallbackActionButton by a RequestWebserviceActionButton
359
360#    def update(self):
361#        if self.context.p_state == 'paid':
362#            self.flash(_('This ticket has already been paid.'))
363#            return
364#        student = self.context.student
365#        query = self.request.form
366#        write_log_message(self,'callback received: %s' % query)
367#        self.context.r_card_num = query.get('cardNum', None)
368#        self.context.r_code = query.get('resp', None)
369#        self.context.r_pay_reference  = query.get('payRef', None)
370#        self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
371#        self.context.r_desc = query.get('desc', None)
372#        if self.context.r_code != '00':
373#            self.flash(_('Unsuccessful callback: ${a}',
374#                mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
375#            write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
376#            self.context.p_state = 'failed'
377#            return
378#        if self.context.r_amount_approved != payment.amount_auth:
379#            self.flash(_('Wrong amount'))
380#            write_log_message(
381#                self,'successful but wrong amount: %s' % self.context.p_id)
382#            self.context.p_state = 'failed'
383#            return
384#        try:
385#            validation_list = get_SOAP_response(
386#                PRODUCT_ID, self.context.p_id).split(':')
387            # Validation does not make sense yet since the query string
388            # formats are conflicting. We are only printing the validation
389            # string, nothing else.
390#            print 'WARNING: Webservice validation is not yet implemented'
391#            print 'validation list: %s' % validation_list
392#        except:
393#            print 'Connection to webservice failed.'
394        # Add webservice validation here
395#        write_log_message(self,'valid callback: %s' % self.context.p_id)
396#        self.context.p_state = 'paid'
397#        self.context.payment_date = datetime.utcnow()
398#        actions_after_student_payment(student, self.context, self)
399#        return
400
401#    def render(self):
402#        self.redirect(self.url(self.context, '@@index'))
403#        return
404
405# Alternative solution, replaces InterswitchPaymentCallbackPage
406class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
407    """ Request webservice view for the CollegePAY gateway
408    """
409    grok.context(ICustomStudentOnlinePayment)
410    grok.name('request_webservice')
411    grok.require('waeup.payStudent')
412
413    def update(self):
414        ob_class = self.__implemented__.__name__
415        if self.context.p_state == 'paid':
416            self.flash(_('This ticket has already been paid.'))
417            return
418        student = self.context.student
419        success, msg, log = query_interswitch(self.context)
420        student.writeLogMessage(self, log)
421        if not success:
422            self.flash(msg)
423            return
424        success, msg, log = self.context.doAfterStudentPayment()
425        if log is not None:
426            student.writeLogMessage(self, log)
427        self.flash(msg)
428        return
429
430    def render(self):
431        self.redirect(self.url(self.context, '@@index'))
432        return
433
434class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
435    """ Request webservice view for the CollegePAY gateway
436    """
437    grok.context(ICustomApplicantOnlinePayment)
438    grok.name('request_webservice')
439    grok.require('waeup.payApplicant')
440
441    def update(self):
442        if self.context.p_state == 'paid':
443            self.flash(_('This ticket has already been paid.'))
444            return
445        applicant = self.context.__parent__
446        success, msg, log = query_interswitch(self.context)
447        applicant.writeLogMessage(self, log)
448        if not success:
449            self.flash(msg)
450            return
451        success, msg, log = self.context.doAfterApplicantPayment()
452        if log is not None:
453            applicant.writeLogMessage(self, log)
454        self.flash(msg)
455        return
456
457    def render(self):
458        self.redirect(self.url(self.context, '@@index'))
459        return
Note: See TracBrowser for help on using the repository browser.