source: main/waeup.futminna/trunk/src/waeup/futminna/interswitch/browser.py @ 9714

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

Update payments catalog after interswitch payment.

  • Property svn:keywords set to Id
File size: 14.5 KB
Line 
1## $Id: browser.py 9712 2012-11-23 07:58:11Z 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, CLEARED, 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.futminna.students.interfaces import ICustomStudentOnlinePayment
32from waeup.futminna.applicants.interfaces import ICustomApplicantOnlinePayment
33from waeup.futminna.interfaces import MessageFactory as _
34
35PRODUCT_ID = '117'
36SITE_NAME = 'futminna-kofa.waeup.org'
37PROVIDER_ACCT = '0026781725'
38PROVIDER_BANK_ID = '31'
39PROVIDER_ITEM_NAME = 'BT Education'
40INSTITUTION_NAME = 'FUTMinna'
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 InterswitchRequestWebserviceActionButtonStudent(APABStudent):
147    grok.order(2)
148    grok.context(ICustomStudentOnlinePayment)
149    grok.require('waeup.payStudent')
150    icon = 'actionicon_call.png'
151    text = _('Requery CollegePAY')
152    target = 'request_webservice'
153
154class InterswitchPageStudent(KofaPage):
155    """ View which sends a POST request to the Interswitch
156    CollegePAY payment gateway.
157    """
158    grok.context(ICustomStudentOnlinePayment)
159    grok.name('goto_interswitch')
160    grok.template('student_goto_interswitch')
161    grok.require('waeup.payStudent')
162    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
163    submit_button = _('Submit')
164    action = POST_ACTION
165    site_name = SITE_NAME
166    currency = CURRENCY
167    pay_item_id = ''
168    product_id = PRODUCT_ID
169
170    def update(self):
171        #if self.context.p_state != 'unpaid':
172        if self.context.p_state == 'paid':
173            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
174            self.redirect(self.url(self.context, '@@index'))
175            return
176
177        student = self.student = self.context.student
178        certificate = getattr(student['studycourse'],'certificate',None)
179        self.amount_auth = 100 * self.context.amount_auth
180        xmldict = {}
181        if certificate is not None:
182            xmldict['department'] = certificate.__parent__.__parent__.code
183            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
184        else:
185            xmldict['department'] = None
186            xmldict['faculty'] = None
187        self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[
188            self.context.p_category]
189        tz = getUtility(IKofaUtils).tzinfo
190        self.local_date_time = to_timezone(
191            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
192        self.site_redirect_url = self.url(self.context, 'request_webservice')
193        # Provider data
194        xmldict['detail_ref'] = self.context.p_id
195        xmldict['provider_acct'] = PROVIDER_ACCT
196        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
197        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
198        provider_amt = 1500
199        xmldict['provider_amt'] = 100 * provider_amt
200
201        # Institution data. Account numbers were changed to the new
202        # NUBAN 10 digit number system
203        xmldict['institution_acct'] = ''
204        xmldict['institution_bank_id'] = ''
205        xmldict['institution_acct'] = '000000000000'
206        xmldict['institution_bank_id'] = '00'
207        xmldict['institution_name'] = INSTITUTION_NAME
208       
209        if self.context.p_category == 'schoolfee':
210            xmldict['institution_amt'] = 100 * (
211                self.context.amount_auth - provider_amt - 300)
212            if self.context.student.current_mode in ('pg_ft'):
213                self.pay_item_id = "11703"
214            elif self.context.student.state == CLEARED and \
215                self.context.student.current_level == 100:
216                self.pay_item_id = "11700"
217            elif self.context.student.state == CLEARED and \
218                self.context.student.current_level == 200:
219                self.pay_item_id = "11701"
220            elif self.context.student.state == CLEARED and \
221                self.context.student.current_level == 300:
222                self.pay_item_id = "11702"
223            elif self.context.student.state == RETURNING and \
224                self.context.student.current_level in (100,110):
225                self.pay_item_id = "11701"
226            elif self.context.student.state == RETURNING and \
227                self.context.student.current_level in (200,210):
228                self.pay_item_id = "11702"
229            elif self.context.student.state == RETURNING and \
230                self.context.student.current_level in (300,310):
231                self.pay_item_id = "11703"
232            elif self.context.student.state == RETURNING and \
233                self.context.student.current_level in (400,410,500,510,600):
234                self.pay_item_id = "11704"
235
236            if self.context.student.current_mode == 'jm_ft':
237                xmldict['institution_acct'] = "000000000000"
238                xmldict['institution_bank_id'] = '00'
239            elif self.context.student.current_mode == 'pg_ft':
240                xmldict['institution_acct'] = "2005910931"
241                xmldict['institution_bank_id'] = '8'
242            elif self.context.student.state == CLEARED and \
243                self.context.student.current_level == 100:
244                xmldict['institution_acct'] = "0021030851"
245                xmldict['institution_bank_id'] = '31'
246            elif self.context.student.state == CLEARED and \
247                self.context.student.current_level == 200:
248                xmldict['institution_acct'] = "0005646299"
249                xmldict['institution_bank_id'] = '47'
250            elif self.context.student.state == CLEARED and \
251                self.context.student.current_level == 300:
252                xmldict['institution_acct'] = "1010500151"
253                xmldict['institution_bank_id'] = '117'
254            elif self.context.student.state == RETURNING and \
255                self.context.student.current_level in (100,110):
256                xmldict['institution_acct'] = "0005646299"
257                xmldict['institution_bank_id'] = '47'
258            elif self.context.student.current_level in (200,210):
259                xmldict['institution_acct'] = "1010500151"
260                xmldict['institution_bank_id'] = '117'
261            elif self.context.student.current_level in (300,310):
262                xmldict['institution_acct'] = "2005910931"
263                xmldict['institution_bank_id'] = '8'
264            elif self.context.student.current_level in (400,410,500,510,600):
265                xmldict['institution_acct'] = "0027490487"
266                xmldict['institution_bank_id'] = '10'
267
268        elif self.context.p_category == 'clearance':
269            xmldict['institution_amt'] = 100 * (
270                self.context.amount_auth - 300)
271            xmldict['institution_acct'] = "1750005063"
272            xmldict['institution_bank_id'] = '120'
273            self.pay_item_id = "11706"
274
275        elif 'maintenance' in self.context.p_category:
276            xmldict['institution_amt'] = 100 * (
277                self.context.amount_auth - 300)
278            xmldict['institution_acct'] = "2018856637"
279            xmldict['institution_bank_id'] = '8'
280            self.pay_item_id = "11705"
281
282        # Interswitch amount is not part of the xml data
283        if self.context.p_category == 'schoolfee':
284            xmltext = """<payment_item_detail>
285<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
286<item_detail item_id="1" item_name="School Fee" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
287<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" />
288</item_details>
289</payment_item_detail>""" % xmldict
290
291        elif self.context.p_category == 'clearance':
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="Acceptance Fee" 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        elif 'maintenance' in self.context.p_category:
299            xmltext = """<payment_item_detail>
300<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
301<item_detail item_id="1" item_name="Hostel Maintenance Fee" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
302</item_details>
303</payment_item_detail>""" % xmldict
304
305
306        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
307        return
308
309class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
310    """ Request webservice view for the CollegePAY gateway
311    """
312    grok.context(ICustomStudentOnlinePayment)
313    grok.name('request_webservice')
314    grok.require('waeup.payStudent')
315
316    def update(self):
317        ob_class = self.__implemented__.__name__
318        if self.context.p_state == 'paid':
319            self.flash(_('This ticket has already been paid.'))
320            return
321        student = self.context.student
322        success, msg, log = query_interswitch(self.context)
323        student.writeLogMessage(self, log)
324        if not success:
325            self.flash(msg)
326            return
327        success, msg, log = self.context.doAfterStudentPayment()
328        if log is not None:
329            student.writeLogMessage(self, log)
330        self.flash(msg)
331        return
332
333    def render(self):
334        self.redirect(self.url(self.context, '@@index'))
335        return
Note: See TracBrowser for help on using the repository browser.