source: main/waeup.fceokene/trunk/src/waeup/fceokene/interswitch/browser.py @ 9727

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

Update payments catalog after interswitch payment.

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