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

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

Configure pay_item_id

  • Property svn:keywords set to Id
File size: 16.8 KB
Line 
1## $Id: browser.py 9626 2012-11-13 08:55: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 = '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 -300)
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        elif 'maintenance' in self.context.p_category:
262            self.pay_item_id = '102'
263            xmldict['institution_acct'] = "0039050937"
264            xmldict['institution_bank_id'] = '31'
265
266        hashargs = (
267            self.context.p_id +
268            PRODUCT_ID +
269            self.pay_item_id +
270            str(int(self.amount_auth)) +
271            self.site_redirect_url +
272            self.mac)
273        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
274
275        # Interswitch amount is not part of the xml data
276
277        if self.context.p_category == 'schoolfee':
278            xmltext = """<payment_item_detail>
279<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
280<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" />
281<item_detail item_id="2" item_name="Dalash" item_amt="%(dalash_amt)s" bank_id="117" acct_num="1013196791" />
282<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" />
283</item_details>
284</payment_item_detail>""" % xmldict
285
286        elif 'maintenance' in self.context.p_category:
287            xmltext = """<payment_item_detail>
288<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
289<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" />
290</item_details>
291</payment_item_detail>""" % xmldict
292
293        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
294        return
295
296class InterswitchPageApplicant(KofaPage):
297    """ View which sends a POST request to the Interswitch
298    CollegePAY payment gateway.
299    """
300    grok.context(ICustomApplicantOnlinePayment)
301    grok.require('waeup.payApplicant')
302    grok.template('applicant_goto_interswitch')
303    grok.name('goto_interswitch')
304    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
305    submit_button = _('Submit')
306    action = POST_ACTION
307    site_name = SITE_NAME
308    currency = CURRENCY
309    pay_item_id = ''
310    product_id = PRODUCT_ID
311
312    def interswitch_img_url(self):
313        return interswitch_img_url(self)
314
315    def update(self):
316        if self.context.p_state != 'unpaid':
317            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
318            self.redirect(self.url(self.context, '@@index'))
319            return
320        if self.context.__parent__.__parent__.expired \
321            and self.context.__parent__.__parent__.strict_deadline:
322            self.flash(_("Payment ticket can't be send to CollegePAY. "
323                         "Application period has expired."))
324            self.redirect(self.url(self.context, '@@index'))
325            return
326        self.applicant = self.context.__parent__
327        self.amount_auth = 100 * self.context.amount_auth
328        xmldict = {}
329        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
330        tz = getUtility(IKofaUtils).tzinfo
331        self.local_date_time = to_timezone(
332            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
333        self.site_redirect_url = self.url(self.context, 'request_webservice')
334        xmldict['detail_ref'] = self.context.p_id
335        # Provider data
336        xmldict['provider_amt'] = 100 * 500
337        xmldict['provider_acct'] = PROVIDER_ACCT
338        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
339        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
340        # Institution data
341        xmldict['institution_amt'] = 100 * (self.context.amount_auth - 500 - 150)
342        xmldict['institution_acct'] = '0'
343        xmldict['institution_bank_id'] = '0'
344        xmldict['institution_item_name'] = self.context.p_category
345        xmldict['institution_name'] = INSTITUTION_NAME
346        # Interswitch amount is not part of the xml data
347        xmltext = """<payment_item_detail>
348<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
349<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" />
350<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" />
351</item_details>
352</payment_item_detail>""" % xmldict
353        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
354        return
355
356
357class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
358    """ Request webservice view for the CollegePAY gateway
359    """
360    grok.context(ICustomStudentOnlinePayment)
361    grok.name('request_webservice')
362    grok.require('waeup.payStudent')
363
364    def update(self):
365        ob_class = self.__implemented__.__name__
366        if self.context.p_state == 'paid':
367            self.flash(_('This ticket has already been paid.'))
368            return
369        student = self.context.student
370        success, msg, log = query_interswitch(self.context)
371        student.writeLogMessage(self, log)
372        if not success:
373            self.flash(msg)
374            return
375        success, msg, log = self.context.doAfterStudentPayment()
376        if log is not None:
377            student.writeLogMessage(self, log)
378        self.flash(msg)
379        return
380
381    def render(self):
382        self.redirect(self.url(self.context, '@@index'))
383        return
384
385class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
386    """ Request webservice view for the CollegePAY gateway
387    """
388    grok.context(ICustomApplicantOnlinePayment)
389    grok.name('request_webservice')
390    grok.require('waeup.payApplicant')
391
392    def update(self):
393        if self.context.p_state == 'paid':
394            self.flash(_('This ticket has already been paid.'))
395            return
396        applicant = self.context.__parent__
397        success, msg, log = query_interswitch(self.context)
398        applicant.writeLogMessage(self, log)
399        if not success:
400            self.flash(msg)
401            return
402        success, msg, log = self.context.doAfterApplicantPayment()
403        if log is not None:
404            applicant.writeLogMessage(self, log)
405        self.flash(msg)
406        return
407
408    def render(self):
409        self.redirect(self.url(self.context, '@@index'))
410        return
Note: See TracBrowser for help on using the repository browser.