source: main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser.py @ 9464

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

Add Interswitch logo.

  • Property svn:keywords set to Id
File size: 15.8 KB
Line 
1## $Id: browser.py 9464 2012-10-30 09:27:19Z 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
21import hashlib
22from xml.dom.minidom import parseString
23import grok
24from zope.interface import Interface
25from zope.component import getUtility, queryAdapter
26from waeup.kofa.browser.layout import KofaPage, UtilityView
27from waeup.kofa.accesscodes import create_accesscode
28from waeup.kofa.interfaces import RETURNING, IKofaUtils
29from waeup.kofa.utils.helpers import to_timezone
30from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
31from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
32from waeup.kwarapoly.students.interfaces import ICustomStudentOnlinePayment
33from waeup.kwarapoly.applicants.interfaces import ICustomApplicantOnlinePayment
34from waeup.kwarapoly.interfaces import MessageFactory as _
35
36PRODUCT_ID = '3986'
37SITE_NAME = 'kwarapoly-kofa.waeup.org'
38PROVIDER_ACCT = '1010764827'
39PROVIDER_BANK_ID = '117'
40PROVIDER_ITEM_NAME = 'BT Education'
41INSTITUTION_NAME = 'KwaraPoly'
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'
45
46#POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
47POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
48
49#HOST = 'webpay.interswitchng.com'
50HOST = 'testwebpay.interswitchng.com'
51
52#URL = '/paydirect/services/TransactionQueryWs.asmx'
53URL = '/test_paydirect/services/TransactionQueryWs.asmx'
54httplib.HTTPConnection.debuglevel = 0
55
56
57def SOAP_post(soap_action,xml):
58    """Handles making the SOAP request.
59
60    Further reading:
61    http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
62    """
63    h = httplib.HTTPConnection(HOST)
64    headers={
65        'Host':HOST,
66        'Content-Type':'text/xml; charset=utf-8',
67        'Content-Length':len(xml),
68        'SOAPAction':'"%s"' % soap_action,
69    }
70    h.request('POST', URL, body=xml,headers=headers)
71    r = h.getresponse()
72    d = r.read()
73    if r.status!=200:
74        raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
75    return d
76
77def get_SOAP_response(product_id, transref):
78    xml="""\
79<?xml version="1.0" encoding="utf-8"?>
80<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/">
81  <soap:Body>
82    <getTransactionData xmlns="http://tempuri.org/">
83      <product_id>%s</product_id>
84      <trans_ref>%s</trans_ref>
85    </getTransactionData>
86  </soap:Body>
87</soap:Envelope>""" % (product_id, transref)
88    result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
89    doc=parseString(result_xml)
90    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
91    return response
92
93def query_interswitch(payment):
94    sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
95    wlist = sr.split(':')
96    if len(wlist) != 7:
97        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
98        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
99        return False, msg, log
100    payment.r_code = wlist[0]
101    payment.r_desc = wlist[1]
102    payment.r_amount_approved = float(wlist[2]) / 100
103    payment.r_card_num = wlist[3]
104    payment.r_pay_reference = wlist[5]
105    payment.r_company = u'interswitch'
106    if payment.r_code != '00':
107        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
108        log = 'unsuccessful callback for %s payment %s: %s' % (
109            payment.p_category, payment.p_id, sr)
110        payment.p_state = 'failed'
111        return False, msg, log
112    if payment.r_amount_approved != payment.amount_auth:
113        msg = _('Callback amount does not match.')
114        log = 'wrong callback for %s payment %s: %s' % (
115            payment.p_category, payment.p_id, sr)
116        payment.p_state = 'failed'
117        return False, msg, log
118    if wlist[4] != payment.p_id:
119        msg = _('Callback transaction id does not match.')
120        log = 'wrong callback for %s payment %s: %s' % (
121            payment.p_category, payment.p_id, sr)
122        payment.p_state = 'failed'
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    return True, msg, log
130
131def interswitch_img_url(view):
132    static = view.static
133    if static is None or static.get(
134        'interswitch_verve_mastercard.gif', None) is None:
135        static = queryAdapter(
136            self.request, Interface, name='waeup.kwarapoly.interswitch')
137    return static['interswitch_verve_mastercard.gif']()
138
139class InterswitchActionButtonStudent(APABStudent):
140    grok.order(1)
141    grok.context(ICustomStudentOnlinePayment)
142    grok.require('waeup.payStudent')
143    icon = 'actionicon_pay.png'
144    text = _('CollegePAY')
145    target = 'goto_interswitch'
146
147    @property
148    def target_url(self):
149        if self.context.p_state != 'unpaid':
150            return ''
151        return self.view.url(self.view.context, self.target)
152
153class InterswitchActionButtonApplicant(APABApplicant):
154    grok.order(1)
155    grok.context(ICustomApplicantOnlinePayment)
156    grok.require('waeup.payApplicant')
157    icon = 'actionicon_pay.png'
158    text = _('CollegePAY')
159    target = 'goto_interswitch'
160
161    @property
162    def target_url(self):
163        if self.context.p_state != 'unpaid':
164            return ''
165        return self.view.url(self.view.context, self.target)
166
167class InterswitchRequestWebserviceActionButtonStudent(APABStudent):
168    grok.order(2)
169    grok.context(ICustomStudentOnlinePayment)
170    grok.require('waeup.payStudent')
171    icon = 'actionicon_call.png'
172    text = _('Requery CollegePAY')
173    target = 'request_webservice'
174
175class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
176    grok.order(2)
177    grok.context(ICustomApplicantOnlinePayment)
178    grok.require('waeup.payApplicant')
179    icon = 'actionicon_call.png'
180    text = _('Requery CollegePAY')
181    target = 'request_webservice'
182
183class InterswitchPageStudent(KofaPage):
184    """ View which sends a POST request to the Interswitch
185    CollegePAY payment gateway.
186    """
187    grok.context(ICustomStudentOnlinePayment)
188    grok.name('goto_interswitch')
189    grok.template('student_goto_interswitch')
190    grok.require('waeup.payStudent')
191    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
192    submit_button = _('Submit')
193    action = POST_ACTION
194    site_name = SITE_NAME
195    currency = CURRENCY
196    pay_item_id = '101'
197    product_id = PRODUCT_ID
198    mac = '737396C271FB8E2BA1A612A942267553E16373658A5F83D90DAFFBB806C16E9E6284006C06CEFFB55768004179D7BC01CD237CCE6314B938A5A5D9F49369DE5A'
199
200    def interswitch_img_url(self):
201        return interswitch_img_url(self)
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.student
211        certificate = getattr(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 = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
221        tz = getUtility(IKofaUtils).tzinfo
222        self.local_date_time = to_timezone(
223            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
224        self.site_redirect_url = self.url(self.context, 'request_webservice')
225        # Provider data
226        xmldict['detail_ref'] = self.context.p_id
227        xmldict['provider_acct'] = PROVIDER_ACCT
228        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
229        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
230        xmldict['provider_amt'] = 100 * 1200
231        # Dalash data
232        xmldict['dalash_amt'] = 100 * 1800
233        # Institution data
234        if xmldict['faculty'] in ('CPGS',):
235            xmldict['institution_acct'] = "1771180233"
236            xmldict['institution_bank_id'] = '120'
237        elif xmldict['faculty'] in ('IBAS',):
238            xmldict['institution_acct'] = "0006772436"
239            xmldict['institution_bank_id'] = '121'
240        elif xmldict['faculty'] in ('IETS',):
241            xmldict['institution_acct'] = "0106259811"
242            xmldict['institution_bank_id'] = '10'
243        elif xmldict['faculty'] in ('IFMS',):
244            xmldict['institution_acct'] = "2013910271"
245            xmldict['institution_bank_id'] = '8'
246        elif xmldict['faculty'] in ('ITCH',):
247            xmldict['institution_acct'] = "1010445144"
248            xmldict['institution_bank_id'] = '117'
249        else:
250            xmldict['institution_acct'] = "0000000000000"
251            xmldict['institution_bank_id'] = '0'
252        xmldict['institution_amt'] = 100 * (
253            self.context.amount_auth - 1200 - 300 - 1800)
254        xmldict['institution_item_name'] = self.context.p_category
255        xmldict['institution_name'] = INSTITUTION_NAME
256
257        hashargs = (
258            self.context.p_id +
259            PRODUCT_ID +
260            self.pay_item_id +
261            str(int(self.amount_auth)) +
262            self.site_redirect_url +
263            self.mac)
264        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
265
266        # Interswitch amount is not part of the xml data
267        xmltext = """<payment_item_detail>
268<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
269<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" />
270<item_detail item_id="2" item_name="Dalash" item_amt="%(dalash_amt)s" bank_id="117" acct_num="1013196791" />
271<item_detail item_id="3" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
272</item_details>
273</payment_item_detail>""" % xmldict
274        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
275        return
276
277class InterswitchPageApplicant(KofaPage):
278    """ View which sends a POST request to the Interswitch
279    CollegePAY payment gateway.
280    """
281    grok.context(ICustomApplicantOnlinePayment)
282    grok.require('waeup.payApplicant')
283    grok.template('applicant_goto_interswitch')
284    grok.name('goto_interswitch')
285    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
286    submit_button = _('Submit')
287    action = POST_ACTION
288    site_name = SITE_NAME
289    currency = CURRENCY
290    pay_item_id = ''
291    product_id = PRODUCT_ID
292
293    def interswitch_img_url(self):
294        return interswitch_img_url(self)
295
296    def update(self):
297        if self.context.p_state != 'unpaid':
298            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
299            self.redirect(self.url(self.context, '@@index'))
300            return
301        if self.context.__parent__.__parent__.expired \
302            and self.context.__parent__.__parent__.strict_deadline:
303            self.flash(_("Payment ticket can't be send to CollegePAY. "
304                         "Application period has expired."))
305            self.redirect(self.url(self.context, '@@index'))
306            return
307        self.applicant = self.context.__parent__
308        self.amount_auth = 100 * self.context.amount_auth
309        xmldict = {}
310        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
311        tz = getUtility(IKofaUtils).tzinfo
312        self.local_date_time = to_timezone(
313            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
314        self.site_redirect_url = self.url(self.context, 'request_webservice')
315        xmldict['detail_ref'] = self.context.p_id
316        # Provider data
317        xmldict['provider_amt'] = 100 * 500
318        xmldict['provider_acct'] = PROVIDER_ACCT
319        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
320        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
321        # Institution data
322        xmldict['institution_amt'] = 100 * (self.context.amount_auth - 500 - 150)
323        xmldict['institution_acct'] = '0'
324        xmldict['institution_bank_id'] = '0'
325        xmldict['institution_item_name'] = self.context.p_category
326        xmldict['institution_name'] = INSTITUTION_NAME
327        # Interswitch amount is not part of the xml data
328        xmltext = """<payment_item_detail>
329<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
330<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" />
331<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" />
332</item_details>
333</payment_item_detail>""" % xmldict
334        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
335        return
336
337
338class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
339    """ Request webservice view for the CollegePAY gateway
340    """
341    grok.context(ICustomStudentOnlinePayment)
342    grok.name('request_webservice')
343    grok.require('waeup.payStudent')
344
345    def update(self):
346        ob_class = self.__implemented__.__name__
347        if self.context.p_state == 'paid':
348            self.flash(_('This ticket has already been paid.'))
349            return
350        student = self.context.student
351        success, msg, log = query_interswitch(self.context)
352        student.writeLogMessage(self, log)
353        if not success:
354            self.flash(msg)
355            return
356        success, msg, log = self.context.doAfterStudentPayment()
357        if log is not None:
358            student.writeLogMessage(self, log)
359        self.flash(msg)
360        return
361
362    def render(self):
363        self.redirect(self.url(self.context, '@@index'))
364        return
365
366class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
367    """ Request webservice view for the CollegePAY gateway
368    """
369    grok.context(ICustomApplicantOnlinePayment)
370    grok.name('request_webservice')
371    grok.require('waeup.payApplicant')
372
373    def update(self):
374        if self.context.p_state == 'paid':
375            self.flash(_('This ticket has already been paid.'))
376            return
377        applicant = self.context.__parent__
378        success, msg, log = query_interswitch(self.context)
379        applicant.writeLogMessage(self, log)
380        if not success:
381            self.flash(msg)
382            return
383        success, msg, log = self.context.doAfterApplicantPayment()
384        if log is not None:
385            applicant.writeLogMessage(self, log)
386        self.flash(msg)
387        return
388
389    def render(self):
390        self.redirect(self.url(self.context, '@@index'))
391        return
Note: See TracBrowser for help on using the repository browser.