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

Last change on this file since 9081 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
RevLine 
[7894]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
[7898]19import httplib
20import urllib
21from xml.dom.minidom import parseString
[7894]22import grok
[8281]23from zope.component import getUtility
[7894]24from waeup.kofa.browser.layout import KofaPage, UtilityView
25from waeup.kofa.accesscodes import create_accesscode
[8281]26from waeup.kofa.interfaces import RETURNING, IKofaUtils
27from waeup.kofa.utils.helpers import to_timezone
[8421]28from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
29from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
[8259]30from waeup.kofa.payments.interfaces import payment_categories
[8263]31from waeup.uniben.students.interfaces import ICustomStudentOnlinePayment
32from waeup.uniben.applicants.interfaces import ICustomApplicantOnlinePayment
[8020]33from waeup.uniben.interfaces import MessageFactory as _
[7894]34
35PRODUCT_ID = '57'
[8263]36SITE_NAME = 'uniben-kofa.waeup.org'
[8424]37PROVIDER_ACCT = '1010764827'
38PROVIDER_BANK_ID = '117'
[8263]39PROVIDER_ITEM_NAME = 'BT Education'
40INSTITUTION_NAME = 'Uniben'
[7894]41CURRENCY = '566'
[8401]42#QUERY_URL = 'https://webpay.interswitchng.com/paydirect/services/TransactionQueryURL.aspx'
[8293]43#QUERY_URL = 'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
[8385]44POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
[8293]45#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
[7894]46
[8293]47HOST = 'webpay.interswitchng.com'
48#HOST = 'testwebpay.interswitchng.com'
49URL = '/paydirect/services/TransactionQueryWs.asmx'
50#URL = '/test_paydirect/services/TransactionQueryWs.asmx'
[7898]51httplib.HTTPConnection.debuglevel = 0
52
[8256]53
[7898]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
[8430]90def query_interswitch(payment):
[8256]91    sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
92    wlist = sr.split(':')
93    if len(wlist) != 7:
[8430]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
[8256]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]
[8955]102    payment.r_company = u'interswitch'
[8256]103    if payment.r_code != '00':
[8430]104        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
[8630]105        log = 'unsuccessful callback for payment %s: %s' % (payment.p_id, sr)
[8256]106        payment.p_state = 'failed'
[8430]107        return False, msg, log
[8263]108    if payment.r_amount_approved != payment.amount_auth:
[8430]109        msg = _('Callback amount does not match.')
110        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
[8256]111        payment.p_state = 'failed'
[8430]112        return False, msg, log
[8256]113    if wlist[4] != payment.p_id:
[8430]114        msg = _('Callback transaction id does not match.')
115        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
[8256]116        payment.p_state = 'failed'
[8430]117        return False, msg, log
[8256]118    payment.p_state = 'paid'
[8433]119    payment.payment_date = datetime.utcnow()
[8430]120    msg = _('Successful callback received')
121    log = 'valid callback for payment %s: %s' % (payment.p_id, sr)
122    return True, msg, log
[8256]123
[8421]124class InterswitchActionButtonStudent(APABStudent):
[8259]125    grok.order(1)
[8255]126    grok.context(ICustomStudentOnlinePayment)
[8430]127    grok.require('waeup.payStudent')
[7894]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
[8421]138class InterswitchActionButtonApplicant(APABApplicant):
[8259]139    grok.order(1)
[8256]140    grok.context(ICustomApplicantOnlinePayment)
[8430]141    grok.require('waeup.payApplicant')
[8256]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
[8259]153#class InterswitchRequestCallbackActionButtonStudent(RCABStudent):
154#    grok.order(3)
155#    grok.context(ICustomStudentOnlinePayment)
156#    icon = 'actionicon_call.png'
157#    text = _('Request CollegePAY callback')
[7894]158
[8259]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)
[7894]168
[7919]169# Alternative preferred solution
[8421]170class InterswitchRequestWebserviceActionButtonStudent(APABStudent):
[8259]171    grok.order(2)
[8255]172    grok.context(ICustomStudentOnlinePayment)
[8430]173    grok.require('waeup.payStudent')
[7919]174    icon = 'actionicon_call.png'
[8421]175    text = _('Requery CollegePAY')
[7919]176    target = 'request_webservice'
177
[8421]178class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
[8259]179    grok.order(2)
[8256]180    grok.context(ICustomApplicantOnlinePayment)
[8430]181    grok.require('waeup.payApplicant')
[8256]182    icon = 'actionicon_call.png'
[8421]183    text = _('Requery CollegePAY')
[8256]184    target = 'request_webservice'
[7919]185
[8256]186
187class InterswitchPageStudent(KofaPage):
[7894]188    """ View which sends a POST request to the Interswitch
189    CollegePAY payment gateway.
190    """
[8255]191    grok.context(ICustomStudentOnlinePayment)
[7894]192    grok.name('goto_interswitch')
[8256]193    grok.template('student_goto_interswitch')
[7894]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
[8263]200    pay_item_id = '5700'
[7894]201    product_id = PRODUCT_ID
202
203    def update(self):
[8256]204        #if self.context.p_state != 'unpaid':
205        if self.context.p_state == 'paid':
[7894]206            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
207            self.redirect(self.url(self.context, '@@index'))
208            return
[8256]209
[8741]210        student = self.student = self.context.student
211        certificate = getattr(student['studycourse'],'certificate',None)
[8276]212        self.amount_auth = 100 * self.context.amount_auth
[7894]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
[8259]220        self.category = payment_categories.getTermByToken(
221            self.context.p_category).title
[8281]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")
[8256]225        self.site_redirect_url = self.url(self.context, 'request_webservice')
[8263]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
[9081]243            if student.is_postgrad:
[8263]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
[9081]261            if student.is_postgrad:
[8263]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'
[8955]271        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
[8263]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
[7894]282        return
283
[8263]284class InterswitchPageApplicant(KofaPage):
[8256]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')
[8263]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
[8274]297    pay_item_id = '5703'
[8263]298    product_id = PRODUCT_ID
[8256]299
300    def update(self):
[8263]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
[8829]305        if self.context.__parent__.__parent__.expired \
306            and self.context.__parent__.__parent__.strict_deadline:
[8694]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
[8256]311        self.applicant = self.context.__parent__
[8276]312        self.amount_auth = 100 * self.context.amount_auth
[8256]313        xmldict = {}
[8259]314        self.category = payment_categories.getTermByToken(
315            self.context.p_category).title
[8281]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")
[8256]319        self.site_redirect_url = self.url(self.context, 'request_webservice')
[8545]320        if self.applicant.applicant_id.startswith('pg'):
321            provider_amt = 400
[8568]322            xmldict['institution_acct'] = '0031716030'
323            xmldict['institution_bank_id'] = '10'
[8545]324        else:
325            provider_amt = 250
[8568]326            xmldict['institution_acct'] = '6220032503'
327            xmldict['institution_bank_id'] = '51'
[8263]328        xmldict['detail_ref'] = self.context.p_id
[8545]329        xmldict['provider_amt'] = 100 * provider_amt
[8263]330        xmldict['provider_acct'] = PROVIDER_ACCT
331        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
332        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
[8545]333        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
[8263]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
[8256]344        return
345
346# Deprecated
[8263]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')
[7894]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
[8263]360#    def update(self):
361#        if self.context.p_state == 'paid':
362#            self.flash(_('This ticket has already been paid.'))
363#            return
[8741]364#        student = self.context.student
[8263]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(':')
[7934]387            # Validation does not make sense yet since the query string
[7970]388            # formats are conflicting. We are only printing the validation
389            # string, nothing else.
[8263]390#            print 'WARNING: Webservice validation is not yet implemented'
391#            print 'validation list: %s' % validation_list
392#        except:
393#            print 'Connection to webservice failed.'
[7970]394        # Add webservice validation here
[8263]395#        write_log_message(self,'valid callback: %s' % self.context.p_id)
396#        self.context.p_state = 'paid'
[8433]397#        self.context.payment_date = datetime.utcnow()
[8263]398#        actions_after_student_payment(student, self.context, self)
399#        return
[7970]400
[8263]401#    def render(self):
402#        self.redirect(self.url(self.context, '@@index'))
403#        return
[7894]404
[8256]405# Alternative solution, replaces InterswitchPaymentCallbackPage
406class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
[7919]407    """ Request webservice view for the CollegePAY gateway
408    """
[8255]409    grok.context(ICustomStudentOnlinePayment)
[7919]410    grok.name('request_webservice')
411    grok.require('waeup.payStudent')
412
413    def update(self):
[8430]414        ob_class = self.__implemented__.__name__
[7919]415        if self.context.p_state == 'paid':
416            self.flash(_('This ticket has already been paid.'))
417            return
[8741]418        student = self.context.student
[8430]419        success, msg, log = query_interswitch(self.context)
[8741]420        student.writeLogMessage(self, log)
[8430]421        if not success:
422            self.flash(msg)
423            return
424        success, msg, log = self.context.doAfterStudentPayment()
425        if log is not None:
[8741]426            student.writeLogMessage(self, log)
[8430]427        self.flash(msg)
[8256]428        return
[7919]429
[8256]430    def render(self):
431        self.redirect(self.url(self.context, '@@index'))
432        return
[7926]433
[8256]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')
[7919]440
[8256]441    def update(self):
442        if self.context.p_state == 'paid':
443            self.flash(_('This ticket has already been paid.'))
[7919]444            return
[8256]445        applicant = self.context.__parent__
[8430]446        success, msg, log = query_interswitch(self.context)
[8743]447        applicant.writeLogMessage(self, log)
[8430]448        if not success:
449            self.flash(msg)
450            return
451        success, msg, log = self.context.doAfterApplicantPayment()
452        if log is not None:
[8743]453            applicant.writeLogMessage(self, log)
[8430]454        self.flash(msg)
[7919]455        return
456
457    def render(self):
458        self.redirect(self.url(self.context, '@@index'))
[9081]459        return
Note: See TracBrowser for help on using the repository browser.