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

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

Prvide correct bank details for maintenance fee payment. Do not use school fee banks.

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