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

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

Prepare InterswitchPageStudent? for maintenance fee payment.

  • Property svn:keywords set to Id
File size: 16.1 KB
Line 
1## $Id: browser.py 9616 2012-11-11 16:53:20Z 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 = '3930'
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
46POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
47#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
48
49HOST = 'webpay.interswitchng.com'
50#HOST = 'testwebpay.interswitchng.com'
51
52URL = '/paydirect/services/TransactionQueryWs.asmx'
53#URL = '/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    product_id = PRODUCT_ID
197    mac = 'E6BA6CBBA9AF2871EE25C32C8D57C98895B9B001DC5B9CB2C463E2A9BDA44A3F1260C8A364F33789CDF74CB3EE7E6EF5D94F48D3AF7B727E75D97F07618DFA6D'
198
199    def interswitch_img_url(self):
200        return interswitch_img_url(self)
201
202    def update(self):
203        #if self.context.p_state != 'unpaid':
204        if self.context.p_state == 'paid':
205            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
206            self.redirect(self.url(self.context, '@@index'))
207            return
208
209        student = self.student = self.context.student
210        certificate = getattr(student['studycourse'],'certificate',None)
211        self.amount_auth = 100 * self.context.amount_auth
212        xmldict = {}
213        if certificate is not None:
214            xmldict['department'] = certificate.__parent__.__parent__.code
215            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
216        else:
217            xmldict['department'] = None
218            xmldict['faculty'] = None
219        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
220        tz = getUtility(IKofaUtils).tzinfo
221        self.local_date_time = to_timezone(
222            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
223        self.site_redirect_url = self.url(self.context, 'request_webservice')
224        # Provider data
225        xmldict['detail_ref'] = self.context.p_id
226        xmldict['provider_acct'] = PROVIDER_ACCT
227        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
228        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
229        xmldict['provider_amt'] = 100 * 1200
230        # Institution data
231        xmldict['institution_acct'] = "0000000000000"
232        xmldict['institution_bank_id'] = '0'
233        xmldict['institution_item_name'] = self.category
234        xmldict['institution_name'] = INSTITUTION_NAME
235        xmldict['institution_amt'] = 100 * self.context.amount_auth
236        xmldict['dalash_amt'] = 0
237        self.pay_item_id = '000'
238        if self.context.p_category == 'schoolfee':
239            self.pay_item_id = '101'
240            # Dalash data
241            xmldict['dalash_amt'] = 100 * 1800
242            if xmldict['faculty'] in ('CPGS',):
243                xmldict['institution_acct'] = "1771180233"
244                xmldict['institution_bank_id'] = '120'
245            elif xmldict['faculty'] in ('IBAS',):
246                xmldict['institution_acct'] = "0006772436"
247                xmldict['institution_bank_id'] = '121'
248            elif xmldict['faculty'] in ('IETS',):
249                xmldict['institution_acct'] = "0106259811"
250                xmldict['institution_bank_id'] = '10'
251            elif xmldict['faculty'] in ('IFMS',):
252                xmldict['institution_acct'] = "2013910271"
253                xmldict['institution_bank_id'] = '8'
254            elif xmldict['faculty'] in ('ITCH',):
255                #acct. number changed from Zenith to FBN by gbenga Nov. 11, 2012
256                xmldict['institution_acct'] = "2013910271"
257                xmldict['institution_bank_id'] = '8'
258            xmldict['institution_amt'] = 100 * (
259                self.context.amount_auth - 1200 - 300 - 1800)
260
261        hashargs = (
262            self.context.p_id +
263            PRODUCT_ID +
264            self.pay_item_id +
265            str(int(self.amount_auth)) +
266            self.site_redirect_url +
267            self.mac)
268        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
269
270        # Interswitch amount is not part of the xml data
271        xmltext = """<payment_item_detail>
272<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
273<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" />
274<item_detail item_id="2" item_name="Dalash" item_amt="%(dalash_amt)s" bank_id="117" acct_num="1013196791" />
275<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" />
276</item_details>
277</payment_item_detail>""" % xmldict
278        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
279        return
280
281class InterswitchPageApplicant(KofaPage):
282    """ View which sends a POST request to the Interswitch
283    CollegePAY payment gateway.
284    """
285    grok.context(ICustomApplicantOnlinePayment)
286    grok.require('waeup.payApplicant')
287    grok.template('applicant_goto_interswitch')
288    grok.name('goto_interswitch')
289    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
290    submit_button = _('Submit')
291    action = POST_ACTION
292    site_name = SITE_NAME
293    currency = CURRENCY
294    pay_item_id = ''
295    product_id = PRODUCT_ID
296
297    def interswitch_img_url(self):
298        return interswitch_img_url(self)
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        if self.context.__parent__.__parent__.expired \
306            and self.context.__parent__.__parent__.strict_deadline:
307            self.flash(_("Payment ticket can't be send to CollegePAY. "
308                         "Application period has expired."))
309            self.redirect(self.url(self.context, '@@index'))
310            return
311        self.applicant = self.context.__parent__
312        self.amount_auth = 100 * self.context.amount_auth
313        xmldict = {}
314        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
315        tz = getUtility(IKofaUtils).tzinfo
316        self.local_date_time = to_timezone(
317            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
318        self.site_redirect_url = self.url(self.context, 'request_webservice')
319        xmldict['detail_ref'] = self.context.p_id
320        # Provider data
321        xmldict['provider_amt'] = 100 * 500
322        xmldict['provider_acct'] = PROVIDER_ACCT
323        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
324        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
325        # Institution data
326        xmldict['institution_amt'] = 100 * (self.context.amount_auth - 500 - 150)
327        xmldict['institution_acct'] = '0'
328        xmldict['institution_bank_id'] = '0'
329        xmldict['institution_item_name'] = self.context.p_category
330        xmldict['institution_name'] = INSTITUTION_NAME
331        # Interswitch amount is not part of the xml data
332        xmltext = """<payment_item_detail>
333<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
334<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" />
335<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" />
336</item_details>
337</payment_item_detail>""" % xmldict
338        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
339        return
340
341
342class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
343    """ Request webservice view for the CollegePAY gateway
344    """
345    grok.context(ICustomStudentOnlinePayment)
346    grok.name('request_webservice')
347    grok.require('waeup.payStudent')
348
349    def update(self):
350        ob_class = self.__implemented__.__name__
351        if self.context.p_state == 'paid':
352            self.flash(_('This ticket has already been paid.'))
353            return
354        student = self.context.student
355        success, msg, log = query_interswitch(self.context)
356        student.writeLogMessage(self, log)
357        if not success:
358            self.flash(msg)
359            return
360        success, msg, log = self.context.doAfterStudentPayment()
361        if log is not None:
362            student.writeLogMessage(self, log)
363        self.flash(msg)
364        return
365
366    def render(self):
367        self.redirect(self.url(self.context, '@@index'))
368        return
369
370class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
371    """ Request webservice view for the CollegePAY gateway
372    """
373    grok.context(ICustomApplicantOnlinePayment)
374    grok.name('request_webservice')
375    grok.require('waeup.payApplicant')
376
377    def update(self):
378        if self.context.p_state == 'paid':
379            self.flash(_('This ticket has already been paid.'))
380            return
381        applicant = self.context.__parent__
382        success, msg, log = query_interswitch(self.context)
383        applicant.writeLogMessage(self, log)
384        if not success:
385            self.flash(msg)
386            return
387        success, msg, log = self.context.doAfterApplicantPayment()
388        if log is not None:
389            applicant.writeLogMessage(self, log)
390        self.flash(msg)
391        return
392
393    def render(self):
394        self.redirect(self.url(self.context, '@@index'))
395        return
Note: See TracBrowser for help on using the repository browser.