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

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

Update catalog after querying interswitch.

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