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

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

Reorganize payment customizatiom. Tests will follow.

Let also applicants pay via eTranzact. Show transaction code on display view (slip view will follow).

  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1## $Id: browser.py 8247 2012-04-22 12:56:07Z 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 waeup.kofa.browser.layout import KofaPage, UtilityView
24from waeup.kofa.accesscodes import create_accesscode
25from waeup.kofa.students.interfaces import IStudentOnlinePayment
26from waeup.kofa.students.browser import write_log_message
27from waeup.kofa.students.viewlets import RequestCallbackActionButton
28from waeup.uniben.students.utils import actions_after_student_payment
29from waeup.uniben.interfaces import MessageFactory as _
30
31PRODUCT_ID = '57'
32SITE_NAME = 'xyz.waeup.org'
33PROVIDER_ACCT = '2345'
34PROVIDER_BANK_ID = '8'
35PROVIDER_ITEM_NAME = 'Kofa Provider Fee'
36INSTITUTION_ACCT = '1234'
37INSTITUTION_BANK_ID = '9'
38INSTITUTION_NAME = 'Sample University'
39CURRENCY = '566'
40PAY_ITEM_ID = '5700'
41QUERY_URL =   'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
42POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
43
44HOST = 'testwebpay.interswitchng.com'
45URL = '/test_paydirect/services/TransactionQueryWs.asmx'
46httplib.HTTPConnection.debuglevel = 0
47
48def SOAP_post(soap_action,xml):
49    """Handles making the SOAP request.
50
51    Further reading:
52    http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
53    """
54    h = httplib.HTTPConnection(HOST)
55    headers={
56        'Host':HOST,
57        'Content-Type':'text/xml; charset=utf-8',
58        'Content-Length':len(xml),
59        'SOAPAction':'"%s"' % soap_action,
60    }
61    h.request('POST', URL, body=xml,headers=headers)
62    r = h.getresponse()
63    d = r.read()
64    if r.status!=200:
65        raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
66    return d
67
68def get_SOAP_response(product_id, transref):
69    xml="""\
70<?xml version="1.0" encoding="utf-8"?>
71<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/">
72  <soap:Body>
73    <getTransactionData xmlns="http://tempuri.org/">
74      <product_id>%s</product_id>
75      <trans_ref>%s</trans_ref>
76    </getTransactionData>
77  </soap:Body>
78</soap:Envelope>""" % (product_id, transref)
79    result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
80    doc=parseString(result_xml)
81    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
82    return response
83
84class InterswitchActionButton(RequestCallbackActionButton):
85    grok.order(2)
86    icon = 'actionicon_pay.png'
87    text = _('CollegePAY')
88    target = 'goto_interswitch'
89
90    @property
91    def target_url(self):
92        if self.context.p_state != 'unpaid':
93            return ''
94        return self.view.url(self.view.context, self.target)
95
96class InterswitchRequestCallbackActionButton(RequestCallbackActionButton):
97    grok.order(3)
98    icon = 'actionicon_call.png'
99    text = _('Request CollegePAY callback')
100
101    def target_url(self):
102        if self.context.p_state == 'paid':
103            return ''
104        site_redirect_url = self.view.url(self.view.context, 'isw_callback')
105        args = {
106            'transRef':self.context.p_id,
107            'prodID':PRODUCT_ID,
108            'redirectURL':site_redirect_url}
109        return QUERY_URL + '?%s' % urllib.urlencode(args)
110
111# Alternative preferred solution
112class InterswitchRequestWebserviceActionButton(RequestCallbackActionButton):
113    grok.order(4)
114    icon = 'actionicon_call.png'
115    text = _('Request CollegePAY webservice')
116    target = 'request_webservice'
117
118
119class InterswitchPage(KofaPage):
120    """ View which sends a POST request to the Interswitch
121    CollegePAY payment gateway.
122    """
123    grok.context(IStudentOnlinePayment)
124    grok.name('goto_interswitch')
125    grok.template('goto_interswitch')
126    grok.require('waeup.payStudent')
127    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
128    submit_button = _('Submit')
129    action = POST_ACTION
130    site_name = SITE_NAME
131    currency = CURRENCY
132    pay_item_id = PAY_ITEM_ID
133    product_id = PRODUCT_ID
134
135    def update(self):
136        if self.context.p_state != 'unpaid':
137            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
138            self.redirect(self.url(self.context, '@@index'))
139            return
140        self.student = self.context.getStudent()
141        self.amount = (self.context.amount_auth + self.context.surcharge_1 +
142            self.context.surcharge_2 + self.context.surcharge_3)
143        self.amount_100 = 100 * self.amount
144        self.local_date_time = str(self.context.creation_date)
145        self.site_redirect_url = self.url(self.context, 'isw_callback')
146        certificate = getattr(self.student['studycourse'],'certificate',None)
147        xmldict = {}
148        if certificate is not None:
149            xmldict['department'] = certificate.__parent__.__parent__.code
150            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
151        else:
152            xmldict['department'] = None
153            xmldict['faculty'] = None
154        xmldict['detail_ref'] = self.context.p_id
155        xmldict['provider_amt'] = 100 * self.context.surcharge_1
156        xmldict['provider_acct'] = PROVIDER_ACCT
157        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
158        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
159        xmldict['institution_amt'] = 100 * self.context.amount_auth
160        xmldict['institution_acct'] = INSTITUTION_ACCT
161        xmldict['institution_bank_id'] = INSTITUTION_BANK_ID
162        xmldict['institution_item_name'] = self.context.p_category
163        xmldict['institution_name'] = INSTITUTION_NAME
164        # Interswitch amount is not part of the xml data
165        xmltext = """<payment_item_detail>
166<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
167<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" />
168<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" />
169</item_details>
170</payment_item_detail>""" % xmldict
171        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
172        return
173
174class InterswitchPaymentCallbackPage(UtilityView, grok.View):
175    """ Callback view for the CollegePAY gateway
176    """
177    grok.context(IStudentOnlinePayment)
178    grok.name('isw_callback')
179    grok.require('waeup.payStudent')
180
181    # This view is not yet working for offline querying transactions
182    # since the query string differs from the query string sent after
183    # posting transactions. This Interswitch bug must be removed first.
184    # Alternatively, we could use the webservice only and replace
185    # the RequestCallbackActionButton by a RequestWebserviceActionButton
186
187    def update(self):
188        if self.context.p_state == 'paid':
189            self.flash(_('This ticket has already been paid.'))
190            return
191        student = self.context.getStudent()
192        query = self.request.form
193        write_log_message(self,'callback received: %s' % query)
194
195        self.context.r_card_num = query.get('cardNum', None)
196        self.context.r_code = query.get('resp', None)
197        self.context.r_pay_reference  = query.get('payRef', None)
198        self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
199        self.context.r_desc = query.get('desc', None)
200
201        if self.context.r_code != '00':
202            self.flash(_('Unsuccessful callback: ${a}',
203                mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
204            write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
205            self.context.p_state = 'failed'
206            return
207
208        total_amount_auth = (
209            self.context.amount_auth
210            + self.context.surcharge_1
211            + self.context.surcharge_2)
212
213        if self.context.r_amount_approved != total_amount_auth:
214            self.flash(_('Wrong amount'))
215            write_log_message(
216                self,'successful but wrong amount: %s' % self.context.p_id)
217            self.context.p_state = 'failed'
218            return
219
220        try:
221            validation_list = get_SOAP_response(
222                PRODUCT_ID, self.context.p_id).split(':')
223            # Validation does not make sense yet since the query string
224            # formats are conflicting. We are only printing the validation
225            # string, nothing else.
226            print 'WARNING: Webservice validation is not yet implemented'
227            print 'validation list: %s' % validation_list
228        except:
229            print 'Connection to webservice failed.'
230
231        # Add webservice validation here
232
233        write_log_message(self,'valid callback: %s' % self.context.p_id)
234        self.context.p_state = 'paid'
235        self.context.payment_date = datetime.now()
236        actions_after_payment(student, self.context, self)
237        return
238
239    def render(self):
240        self.redirect(self.url(self.context, '@@index'))
241        return
242
243# Alternative solution, replaces OnlinePaymentCallbackPage
244class InterswitchPaymentRequestWebservicePage(UtilityView, grok.View):
245    """ Request webservice view for the CollegePAY gateway
246    """
247    grok.context(IStudentOnlinePayment)
248    grok.name('request_webservice')
249    grok.require('waeup.payStudent')
250
251    def update(self):
252        if self.context.p_state == 'paid':
253            self.flash(_('This ticket has already been paid.'))
254            return
255        student = self.context.getStudent()
256        sr = get_SOAP_response(PRODUCT_ID, self.context.p_id)
257        write_log_message(self,'callback received: %s' % sr)
258        wlist = sr.split(':')
259        if len(wlist) != 7:
260            self.flash(_('Invalid callback: ${a}',
261                mapping = {'a': wlist}))
262            write_log_message(self,'invalid callback: %s' % self.context.p_id)
263            return
264        self.context.r_code = wlist[0]
265        self.context.r_desc = wlist[1]
266        self.context.r_amount_approved = float(wlist[2]) / 100
267        self.context.r_card_num = wlist[3]
268        self.context.r_pay_reference = wlist[5]
269       
270        if self.context.r_code != '00':
271            self.flash(_('Unsuccessful callback: ${a}',
272                mapping = {'a': wlist[1]}))
273            write_log_message(
274                self,'unsuccessful callback: %s' % self.context.p_id)
275            self.context.p_state = 'failed'
276            return
277
278        total_amount_auth = (
279            self.context.amount_auth
280            + self.context.surcharge_1
281            + self.context.surcharge_2)
282
283        if self.context.r_amount_approved != total_amount_auth:
284            self.flash(_('Wrong amount'))
285            write_log_message(
286                self,'successful callback but wrong amount: %s'
287                % self.context.p_id)
288            self.context.p_state = 'failed'
289            return
290
291        if wlist[4] != self.context.p_id:
292            self.flash(_('Wrong transaction id'))
293            write_log_message(
294                self,'successful callback but wrong transaction id: %s'
295                % self.context.p_id)
296            self.context.p_state = 'failed'
297            return
298
299        write_log_message(self,'successful callback: %s' % self.context.p_id)
300
301        self.context.p_state = 'paid'
302        self.context.payment_date = datetime.now()
303
304        actions_after_payment(student, self.context, self)
305
306        return
307
308    def render(self):
309        self.redirect(self.url(self.context, '@@index'))
310        return
Note: See TracBrowser for help on using the repository browser.