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

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

Add bank accounts for pg clearance.

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