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

Last change on this file since 8633 was 8630, checked in by Henrik Bettermann, 13 years ago

Customize CustomApplicantRegistrationPage?.

Uniben applicants do see the login credentials on registration landing page.

  • Property svn:keywords set to Id
File size: 19.1 KB
Line 
1## $Id: browser.py 8630 2012-06-05 15:07:26Z 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.browser import write_log_message
29from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
30from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
31from waeup.kofa.payments.interfaces import payment_categories
32from waeup.uniben.students.interfaces import ICustomStudentOnlinePayment
33from waeup.uniben.applicants.interfaces import ICustomApplicantOnlinePayment
34from waeup.uniben.interfaces import MessageFactory as _
35
36PRODUCT_ID = '57'
37SITE_NAME = 'uniben-kofa.waeup.org'
38PROVIDER_ACCT = '1010764827'
39PROVIDER_BANK_ID = '117'
40PROVIDER_ITEM_NAME = 'BT Education'
41INSTITUTION_NAME = 'Uniben'
42CURRENCY = '566'
43#QUERY_URL = 'https://webpay.interswitchng.com/paydirect/services/TransactionQueryURL.aspx'
44#QUERY_URL = 'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
45POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
46#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
47
48HOST = 'webpay.interswitchng.com'
49#HOST = 'testwebpay.interswitchng.com'
50URL = '/paydirect/services/TransactionQueryWs.asmx'
51#URL = '/test_paydirect/services/TransactionQueryWs.asmx'
52httplib.HTTPConnection.debuglevel = 0
53
54
55def SOAP_post(soap_action,xml):
56    """Handles making the SOAP request.
57
58    Further reading:
59    http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
60    """
61    h = httplib.HTTPConnection(HOST)
62    headers={
63        'Host':HOST,
64        'Content-Type':'text/xml; charset=utf-8',
65        'Content-Length':len(xml),
66        'SOAPAction':'"%s"' % soap_action,
67    }
68    h.request('POST', URL, body=xml,headers=headers)
69    r = h.getresponse()
70    d = r.read()
71    if r.status!=200:
72        raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
73    return d
74
75def get_SOAP_response(product_id, transref):
76    xml="""\
77<?xml version="1.0" encoding="utf-8"?>
78<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/">
79  <soap:Body>
80    <getTransactionData xmlns="http://tempuri.org/">
81      <product_id>%s</product_id>
82      <trans_ref>%s</trans_ref>
83    </getTransactionData>
84  </soap:Body>
85</soap:Envelope>""" % (product_id, transref)
86    result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
87    doc=parseString(result_xml)
88    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
89    return response
90
91def query_interswitch(payment):
92    sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
93    wlist = sr.split(':')
94    if len(wlist) != 7:
95        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
96        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
97        return False, msg, log
98    payment.r_code = wlist[0]
99    payment.r_desc = wlist[1]
100    payment.r_amount_approved = float(wlist[2]) / 100
101    payment.r_card_num = wlist[3]
102    payment.r_pay_reference = wlist[5]
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.getStudent()
211        certificate = getattr(self.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 studycourse.current_level in ('700','710','800','810','900','910'):
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 studycourse.current_level in ('700','710','800','810','900','910'):
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.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        self.applicant = self.context.__parent__
306        self.amount_auth = 100 * self.context.amount_auth
307        xmldict = {}
308        self.category = payment_categories.getTermByToken(
309            self.context.p_category).title
310        tz = getUtility(IKofaUtils).tzinfo
311        self.local_date_time = to_timezone(
312            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
313        self.site_redirect_url = self.url(self.context, 'request_webservice')
314        if self.applicant.applicant_id.startswith('pg'):
315            provider_amt = 400
316            xmldict['institution_acct'] = '0031716030'
317            xmldict['institution_bank_id'] = '10'
318        else:
319            provider_amt = 250
320            xmldict['institution_acct'] = '6220032503'
321            xmldict['institution_bank_id'] = '51'
322        xmldict['detail_ref'] = self.context.p_id
323        xmldict['provider_amt'] = 100 * provider_amt
324        xmldict['provider_acct'] = PROVIDER_ACCT
325        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
326        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
327        xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
328        xmldict['institution_item_name'] = self.context.p_category
329        xmldict['institution_name'] = INSTITUTION_NAME
330        # Interswitch amount is not part of the xml data
331        xmltext = """<payment_item_detail>
332<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
333<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" />
334<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" />
335</item_details>
336</payment_item_detail>""" % xmldict
337        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
338        return
339
340# Deprecated
341#class InterswitchPaymentCallbackPageStudent(UtilityView, grok.View):
342#    """ Callback view for the CollegePAY gateway
343#    """
344#    grok.context(ICustomStudentOnlinePayment)
345#    grok.name('isw_callback')
346#    grok.require('waeup.payStudent')
347
348    # This view is not yet working for offline querying transactions
349    # since the query string differs from the query string sent after
350    # posting transactions. This Interswitch bug must be removed first.
351    # Alternatively, we could use the webservice only and replace
352    # the RequestCallbackActionButton by a RequestWebserviceActionButton
353
354#    def update(self):
355#        if self.context.p_state == 'paid':
356#            self.flash(_('This ticket has already been paid.'))
357#            return
358#        student = self.context.getStudent()
359#        query = self.request.form
360#        write_log_message(self,'callback received: %s' % query)
361#        self.context.r_card_num = query.get('cardNum', None)
362#        self.context.r_code = query.get('resp', None)
363#        self.context.r_pay_reference  = query.get('payRef', None)
364#        self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
365#        self.context.r_desc = query.get('desc', None)
366#        if self.context.r_code != '00':
367#            self.flash(_('Unsuccessful callback: ${a}',
368#                mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
369#            write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
370#            self.context.p_state = 'failed'
371#            return
372#        if self.context.r_amount_approved != payment.amount_auth:
373#            self.flash(_('Wrong amount'))
374#            write_log_message(
375#                self,'successful but wrong amount: %s' % self.context.p_id)
376#            self.context.p_state = 'failed'
377#            return
378#        try:
379#            validation_list = get_SOAP_response(
380#                PRODUCT_ID, self.context.p_id).split(':')
381            # Validation does not make sense yet since the query string
382            # formats are conflicting. We are only printing the validation
383            # string, nothing else.
384#            print 'WARNING: Webservice validation is not yet implemented'
385#            print 'validation list: %s' % validation_list
386#        except:
387#            print 'Connection to webservice failed.'
388        # Add webservice validation here
389#        write_log_message(self,'valid callback: %s' % self.context.p_id)
390#        self.context.p_state = 'paid'
391#        self.context.payment_date = datetime.utcnow()
392#        actions_after_student_payment(student, self.context, self)
393#        return
394
395#    def render(self):
396#        self.redirect(self.url(self.context, '@@index'))
397#        return
398
399# Alternative solution, replaces InterswitchPaymentCallbackPage
400class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
401    """ Request webservice view for the CollegePAY gateway
402    """
403    grok.context(ICustomStudentOnlinePayment)
404    grok.name('request_webservice')
405    grok.require('waeup.payStudent')
406
407    def update(self):
408        ob_class = self.__implemented__.__name__
409        if self.context.p_state == 'paid':
410            self.flash(_('This ticket has already been paid.'))
411            return
412        student = self.context.getStudent()
413        success, msg, log = query_interswitch(self.context)
414        student.loggerInfo(ob_class, log)
415        if not success:
416            self.flash(msg)
417            return
418        success, msg, log = self.context.doAfterStudentPayment()
419        if log is not None:
420            student.loggerInfo(ob_class, log)
421        self.flash(msg)
422        return
423
424    def render(self):
425        self.redirect(self.url(self.context, '@@index'))
426        return
427
428class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
429    """ Request webservice view for the CollegePAY gateway
430    """
431    grok.context(ICustomApplicantOnlinePayment)
432    grok.name('request_webservice')
433    grok.require('waeup.payApplicant')
434
435    def update(self):
436        ob_class = self.__implemented__.__name__
437        if self.context.p_state == 'paid':
438            self.flash(_('This ticket has already been paid.'))
439            return
440        applicant = self.context.__parent__
441        success, msg, log = query_interswitch(self.context)
442        applicant.loggerInfo(ob_class, log)
443        if not success:
444            self.flash(msg)
445            return
446        success, msg, log = self.context.doAfterApplicantPayment()
447        if log is not None:
448            applicant.loggerInfo(ob_class, log)
449        self.flash(msg)
450        return
451
452    def render(self):
453        self.redirect(self.url(self.context, '@@index'))
454        return
Note: See TracBrowser for help on using the repository browser.