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

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

Catch connection errors.

  • Property svn:keywords set to Id
File size: 20.9 KB
Line 
1## $Id: browser.py 9745 2012-11-30 18:16:40Z 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.event import notify
24from zope.component import getUtility
25from waeup.kofa.browser.layout import KofaPage, UtilityView
26from waeup.kofa.accesscodes import create_accesscode
27from waeup.kofa.interfaces import RETURNING, IKofaUtils
28from waeup.kofa.utils.helpers import to_timezone
29from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
30from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
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    response = h.getresponse()
69    return response
70
71def get_SOAP_response(product_id, transref):
72    xml="""\
73<?xml version="1.0" encoding="utf-8"?>
74<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/">
75  <soap:Body>
76    <getTransactionData xmlns="http://tempuri.org/">
77      <product_id>%s</product_id>
78      <trans_ref>%s</trans_ref>
79    </getTransactionData>
80  </soap:Body>
81</soap:Envelope>""" % (product_id, transref)
82    response=SOAP_post("http://tempuri.org/getTransactionData",xml)
83    if response.status!=200:
84        return 'Connection error (%s, %s)' % (response.status, response.reason)
85    result_xml = response.read()
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    if sr.startswith('Connection error'):
93        msg = _('Connection error')
94        log = sr
95        return False, msg, log
96    wlist = sr.split(':')
97    if len(wlist) != 7:
98        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
99        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
100        return False, msg, log
101    payment.r_code = wlist[0]
102    payment.r_desc = wlist[1]
103    payment.r_amount_approved = float(wlist[2]) / 100
104    payment.r_card_num = wlist[3]
105    payment.r_pay_reference = wlist[5]
106    payment.r_company = u'interswitch'
107    if payment.r_code != '00':
108        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
109        log = 'unsuccessful callback for %s payment %s: %s' % (
110            payment.p_category, payment.p_id, sr)
111        payment.p_state = 'failed'
112        notify(grok.ObjectModifiedEvent(payment))
113        return False, msg, log
114    if payment.r_amount_approved != payment.amount_auth:
115        msg = _('Callback amount does not match.')
116        log = 'wrong callback for %s payment %s: %s' % (
117            payment.p_category, payment.p_id, sr)
118        payment.p_state = 'failed'
119        notify(grok.ObjectModifiedEvent(payment))
120        return False, msg, log
121    if wlist[4] != payment.p_id:
122        msg = _('Callback transaction id does not match.')
123        log = 'wrong callback for %s payment %s: %s' % (
124            payment.p_category, payment.p_id, sr)
125        payment.p_state = 'failed'
126        notify(grok.ObjectModifiedEvent(payment))
127        return False, msg, log
128    payment.p_state = 'paid'
129    payment.payment_date = datetime.utcnow()
130    msg = _('Successful callback received')
131    log = 'valid callback for %s payment %s: %s' % (
132        payment.p_category, payment.p_id, sr)
133    notify(grok.ObjectModifiedEvent(payment))
134    return True, msg, log
135
136class InterswitchActionButtonStudent(APABStudent):
137    grok.order(1)
138    grok.context(ICustomStudentOnlinePayment)
139    grok.require('waeup.payStudent')
140    icon = 'actionicon_pay.png'
141    text = _('CollegePAY')
142    target = 'goto_interswitch'
143
144    @property
145    def target_url(self):
146        if self.context.p_state != 'unpaid':
147            return ''
148        return self.view.url(self.view.context, self.target)
149
150class InterswitchActionButtonApplicant(APABApplicant):
151    grok.order(1)
152    grok.context(ICustomApplicantOnlinePayment)
153    grok.require('waeup.payApplicant')
154    icon = 'actionicon_pay.png'
155    text = _('CollegePAY')
156    target = 'goto_interswitch'
157
158    @property
159    def target_url(self):
160        if self.context.p_state != 'unpaid':
161            return ''
162        return self.view.url(self.view.context, self.target)
163
164# Deprecated
165#class InterswitchRequestCallbackActionButtonStudent(RCABStudent):
166#    grok.order(3)
167#    grok.context(ICustomStudentOnlinePayment)
168#    icon = 'actionicon_call.png'
169#    text = _('Request CollegePAY callback')
170
171#    def target_url(self):
172#        if self.context.p_state == 'paid':
173#            return ''
174#        site_redirect_url = self.view.url(self.view.context, 'isw_callback')
175#        args = {
176#            'transRef':self.context.p_id,
177#            'prodID':PRODUCT_ID,
178#            'redirectURL':site_redirect_url}
179#        return QUERY_URL + '?%s' % urllib.urlencode(args)
180
181# Alternative preferred solution
182class InterswitchRequestWebserviceActionButtonStudent(APABStudent):
183    grok.order(2)
184    grok.context(ICustomStudentOnlinePayment)
185    grok.require('waeup.payStudent')
186    icon = 'actionicon_call.png'
187    text = _('Requery CollegePAY')
188    target = 'request_webservice'
189
190class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
191    grok.order(2)
192    grok.context(ICustomApplicantOnlinePayment)
193    grok.require('waeup.payApplicant')
194    icon = 'actionicon_call.png'
195    text = _('Requery CollegePAY')
196    target = 'request_webservice'
197
198
199class InterswitchPageStudent(KofaPage):
200    """ View which sends a POST request to the Interswitch
201    CollegePAY payment gateway.
202    """
203    grok.context(ICustomStudentOnlinePayment)
204    grok.name('goto_interswitch')
205    grok.template('student_goto_interswitch')
206    grok.require('waeup.payStudent')
207    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
208    submit_button = _('Submit')
209    action = POST_ACTION
210    site_name = SITE_NAME
211    currency = CURRENCY
212    product_id = PRODUCT_ID
213
214    def update(self):
215        #if self.context.p_state != 'unpaid':
216        if self.context.p_state == 'paid':
217            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
218            self.redirect(self.url(self.context, '@@index'))
219            return
220
221        student = self.student = self.context.student
222        certificate = getattr(student['studycourse'],'certificate',None)
223        self.amount_auth = 100 * self.context.amount_auth
224        xmldict = {}
225        if certificate is not None:
226            xmldict['department'] = certificate.__parent__.__parent__.code
227            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
228        else:
229            xmldict['department'] = None
230            xmldict['faculty'] = None
231        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
232        tz = getUtility(IKofaUtils).tzinfo
233        self.local_date_time = to_timezone(
234            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
235        self.site_redirect_url = self.url(self.context, 'request_webservice')
236        # Provider data
237        xmldict['detail_ref'] = self.context.p_id
238        xmldict['provider_acct'] = PROVIDER_ACCT
239        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
240        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
241        # Institution data
242        xmldict['institution_acct'] = '000000000000'
243        xmldict['institution_bank_id'] = '00'
244        xmldict['institution_amt'] = '0.0'
245        if self.context.p_category == 'schoolfee':
246            provider_amt = 1500
247            if student.current_mode.endswith('_ft'):
248                self.pay_item_id = '5700'
249                if student.current_mode in ('ug_ft','de_ft','ct_ft','ume_ft'):
250                    xmldict['institution_acct'] = '2017506430'
251                    xmldict['institution_bank_id'] = '8'
252                elif student.current_mode in ('dp_ft'):
253                    xmldict['institution_acct'] = '9201805071'
254                    xmldict['institution_bank_id'] = '17'
255                elif student.current_mode in ('pg_ft'):
256                    xmldict['institution_acct'] = '5330832799'
257                    xmldict['institution_bank_id'] = '51'
258            elif student.current_mode.endswith('_pt'):
259                self.pay_item_id = '5701'
260                if student.current_mode in ('ug_pt','de_pt','ct_pt'):
261                    xmldict['institution_acct'] = '0122009929'
262                    xmldict['institution_bank_id'] = '16'
263                elif student.current_mode in ('dp_pt'):
264                    xmldict['institution_acct'] = '9201805071'
265                    xmldict['institution_bank_id'] = '17'
266                elif student.current_mode in ('pg_pt'):
267                    xmldict['institution_acct'] = '0031716047'
268                    xmldict['institution_bank_id'] = '10'
269        elif self.context.p_category == 'clearance':
270            self.pay_item_id = '5702'
271            provider_amt = 1500
272            if student.current_mode == 'pg_ft':
273                xmldict['institution_acct'] = '5330832799'
274                xmldict['institution_bank_id'] = '51'
275            elif student.current_mode == 'pg_pt':
276                xmldict['institution_acct'] = '0031716047'
277                xmldict['institution_bank_id'] = '10'
278            elif student.current_mode == 'dp_pt':
279                xmldict['institution_acct'] = '9201805071'
280                xmldict['institution_bank_id'] = '17'
281            else:
282                xmldict['institution_bank_id'] = '7'
283                xmldict['institution_acct'] = '1003475516'
284        elif self.context.p_category == 'gown':
285            self.pay_item_id = '5704'
286            provider_amt = 0
287            xmldict['institution_bank_id'] = '7'
288            xmldict['institution_acct'] = '1016232382'
289        elif self.context.p_category.startswith('hostel_maintenance'):
290            self.pay_item_id = '5705'
291            provider_amt = 0
292            xmldict['institution_bank_id'] = '129'
293            xmldict['institution_acct'] = '0014414547'
294
295        xmldict['provider_amt'] = 100 * provider_amt
296        xmldict['institution_item_name'] = self.category
297        xmldict['institution_name'] = INSTITUTION_NAME
298        xmldict['institution_amt'] = 100 * (
299            self.context.amount_auth - provider_amt - 150)
300        # Interswitch amount is not part of the xml data
301        if provider_amt == 0:
302            xmltext = """<payment_item_detail>
303<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
304<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" />
305</item_details>
306</payment_item_detail>""" % xmldict
307        else:
308            xmltext = """<payment_item_detail>
309<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
310<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" />
311<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" />
312</item_details>
313</payment_item_detail>""" % xmldict
314        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
315        return
316
317class InterswitchPageApplicant(KofaPage):
318    """ View which sends a POST request to the Interswitch
319    CollegePAY payment gateway.
320    """
321    grok.context(ICustomApplicantOnlinePayment)
322    grok.require('waeup.payApplicant')
323    grok.template('applicant_goto_interswitch')
324    grok.name('goto_interswitch')
325    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
326    submit_button = _('Submit')
327    action = POST_ACTION
328    site_name = SITE_NAME
329    currency = CURRENCY
330    pay_item_id = '5703'
331    product_id = PRODUCT_ID
332
333    def update(self):
334        if self.context.p_state != 'unpaid':
335            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
336            self.redirect(self.url(self.context, '@@index'))
337            return
338        if self.context.__parent__.__parent__.expired \
339            and self.context.__parent__.__parent__.strict_deadline:
340            self.flash(_("Payment ticket can't be send to CollegePAY. "
341                         "Application period has expired."))
342            self.redirect(self.url(self.context, '@@index'))
343            return
344        self.applicant = self.context.__parent__
345        self.amount_auth = 100 * self.context.amount_auth
346        xmldict = {}
347        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
348        tz = getUtility(IKofaUtils).tzinfo
349        self.local_date_time = to_timezone(
350            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
351        self.site_redirect_url = self.url(self.context, 'request_webservice')
352        provider_amt = 400
353        if self.applicant.applicant_id.startswith('pg'):
354            xmldict['institution_acct'] = '0031716030'
355            xmldict['institution_bank_id'] = '10'
356        elif self.applicant.applicant_id.startswith('dp'):
357            xmldict['institution_acct'] = '9201805071'
358            xmldict['institution_bank_id'] = '17'
359        else:
360            xmldict['institution_acct'] = '6220032503'
361            xmldict['institution_bank_id'] = '51'
362        xmldict['detail_ref'] = self.context.p_id
363        xmldict['provider_amt'] = 100 * provider_amt
364        xmldict['provider_acct'] = PROVIDER_ACCT
365        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
366        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
367        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
368        xmldict['institution_item_name'] = self.context.p_category
369        xmldict['institution_name'] = INSTITUTION_NAME
370        # Interswitch amount is not part of the xml data
371        xmltext = """<payment_item_detail>
372<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
373<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" />
374<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" />
375</item_details>
376</payment_item_detail>""" % xmldict
377        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
378        return
379
380# Deprecated
381#class InterswitchPaymentCallbackPageStudent(UtilityView, grok.View):
382#    """ Callback view for the CollegePAY gateway
383#    """
384#    grok.context(ICustomStudentOnlinePayment)
385#    grok.name('isw_callback')
386#    grok.require('waeup.payStudent')
387
388    # This view is not yet working for offline querying transactions
389    # since the query string differs from the query string sent after
390    # posting transactions. This Interswitch bug must be removed first.
391    # Alternatively, we could use the webservice only and replace
392    # the RequestCallbackActionButton by a RequestWebserviceActionButton
393
394#    def update(self):
395#        if self.context.p_state == 'paid':
396#            self.flash(_('This ticket has already been paid.'))
397#            return
398#        student = self.context.student
399#        query = self.request.form
400#        write_log_message(self,'callback received: %s' % query)
401#        self.context.r_card_num = query.get('cardNum', None)
402#        self.context.r_code = query.get('resp', None)
403#        self.context.r_pay_reference  = query.get('payRef', None)
404#        self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
405#        self.context.r_desc = query.get('desc', None)
406#        if self.context.r_code != '00':
407#            self.flash(_('Unsuccessful callback: ${a}',
408#                mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
409#            write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
410#            self.context.p_state = 'failed'
411#            return
412#        if self.context.r_amount_approved != payment.amount_auth:
413#            self.flash(_('Wrong amount'))
414#            write_log_message(
415#                self,'successful but wrong amount: %s' % self.context.p_id)
416#            self.context.p_state = 'failed'
417#            return
418#        try:
419#            validation_list = get_SOAP_response(
420#                PRODUCT_ID, self.context.p_id).split(':')
421            # Validation does not make sense yet since the query string
422            # formats are conflicting. We are only printing the validation
423            # string, nothing else.
424#            print 'WARNING: Webservice validation is not yet implemented'
425#            print 'validation list: %s' % validation_list
426#        except:
427#            print 'Connection to webservice failed.'
428        # Add webservice validation here
429#        write_log_message(self,'valid callback: %s' % self.context.p_id)
430#        self.context.p_state = 'paid'
431#        self.context.payment_date = datetime.utcnow()
432#        actions_after_student_payment(student, self.context, self)
433#        return
434
435#    def render(self):
436#        self.redirect(self.url(self.context, '@@index'))
437#        return
438
439# Alternative solution, replaces InterswitchPaymentCallbackPage
440class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
441    """ Request webservice view for the CollegePAY gateway
442    """
443    grok.context(ICustomStudentOnlinePayment)
444    grok.name('request_webservice')
445    grok.require('waeup.payStudent')
446
447    def update(self):
448        ob_class = self.__implemented__.__name__
449        if self.context.p_state == 'paid':
450            self.flash(_('This ticket has already been paid.'))
451            return
452        student = self.context.student
453        success, msg, log = query_interswitch(self.context)
454        student.writeLogMessage(self, log)
455        if not success:
456            self.flash(msg)
457            return
458        success, msg, log = self.context.doAfterStudentPayment()
459        if log is not None:
460            student.writeLogMessage(self, log)
461        self.flash(msg)
462        return
463
464    def render(self):
465        self.redirect(self.url(self.context, '@@index'))
466        return
467
468class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
469    """ Request webservice view for the CollegePAY gateway
470    """
471    grok.context(ICustomApplicantOnlinePayment)
472    grok.name('request_webservice')
473    grok.require('waeup.payApplicant')
474
475    def update(self):
476        if self.context.p_state == 'paid':
477            self.flash(_('This ticket has already been paid.'))
478            return
479        applicant = self.context.__parent__
480        success, msg, log = query_interswitch(self.context)
481        applicant.writeLogMessage(self, log)
482        if not success:
483            self.flash(msg)
484            return
485        success, msg, log = self.context.doAfterApplicantPayment()
486        if log is not None:
487            applicant.writeLogMessage(self, log)
488        self.flash(msg)
489        return
490
491    def render(self):
492        self.redirect(self.url(self.context, '@@index'))
493        return
Note: See TracBrowser for help on using the repository browser.