## $Id: browser.py 9713 2012-11-23 07:58:22Z henrik $
##
## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
from datetime import datetime
import httplib
import urllib
from xml.dom.minidom import parseString
import grok
from zope.event import notify
from zope.component import getUtility
from waeup.kofa.browser.layout import KofaPage, UtilityView
from waeup.kofa.accesscodes import create_accesscode
from waeup.kofa.interfaces import RETURNING, IKofaUtils
from waeup.kofa.utils.helpers import to_timezone
from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
from waeup.uniben.students.interfaces import ICustomStudentOnlinePayment
from waeup.uniben.applicants.interfaces import ICustomApplicantOnlinePayment
from waeup.uniben.interfaces import MessageFactory as _
PRODUCT_ID = '57'
SITE_NAME = 'uniben-kofa.waeup.org'
PROVIDER_ACCT = '1010764827'
PROVIDER_BANK_ID = '117'
PROVIDER_ITEM_NAME = 'BT Education'
INSTITUTION_NAME = 'Uniben'
CURRENCY = '566'
#QUERY_URL = 'https://webpay.interswitchng.com/paydirect/services/TransactionQueryURL.aspx'
#QUERY_URL = 'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
HOST = 'webpay.interswitchng.com'
#HOST = 'testwebpay.interswitchng.com'
URL = '/paydirect/services/TransactionQueryWs.asmx'
#URL = '/test_paydirect/services/TransactionQueryWs.asmx'
httplib.HTTPConnection.debuglevel = 0
def SOAP_post(soap_action,xml):
"""Handles making the SOAP request.
Further reading:
http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
"""
h = httplib.HTTPConnection(HOST)
headers={
'Host':HOST,
'Content-Type':'text/xml; charset=utf-8',
'Content-Length':len(xml),
'SOAPAction':'"%s"' % soap_action,
}
h.request('POST', URL, body=xml,headers=headers)
r = h.getresponse()
d = r.read()
if r.status!=200:
raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
return d
def get_SOAP_response(product_id, transref):
xml="""\
%s
%s
""" % (product_id, transref)
result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
doc=parseString(result_xml)
response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
return response
def query_interswitch(payment):
sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
wlist = sr.split(':')
if len(wlist) != 7:
msg = _('Invalid callback: ${a}', mapping = {'a': sr})
log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
return False, msg, log
payment.r_code = wlist[0]
payment.r_desc = wlist[1]
payment.r_amount_approved = float(wlist[2]) / 100
payment.r_card_num = wlist[3]
payment.r_pay_reference = wlist[5]
payment.r_company = u'interswitch'
if payment.r_code != '00':
msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
log = 'unsuccessful callback for %s payment %s: %s' % (
payment.p_category, payment.p_id, sr)
payment.p_state = 'failed'
notify(grok.ObjectModifiedEvent(payment))
return False, msg, log
if payment.r_amount_approved != payment.amount_auth:
msg = _('Callback amount does not match.')
log = 'wrong callback for %s payment %s: %s' % (
payment.p_category, payment.p_id, sr)
payment.p_state = 'failed'
notify(grok.ObjectModifiedEvent(payment))
return False, msg, log
if wlist[4] != payment.p_id:
msg = _('Callback transaction id does not match.')
log = 'wrong callback for %s payment %s: %s' % (
payment.p_category, payment.p_id, sr)
payment.p_state = 'failed'
notify(grok.ObjectModifiedEvent(payment))
return False, msg, log
payment.p_state = 'paid'
payment.payment_date = datetime.utcnow()
msg = _('Successful callback received')
log = 'valid callback for %s payment %s: %s' % (
payment.p_category, payment.p_id, sr)
notify(grok.ObjectModifiedEvent(payment))
return True, msg, log
class InterswitchActionButtonStudent(APABStudent):
grok.order(1)
grok.context(ICustomStudentOnlinePayment)
grok.require('waeup.payStudent')
icon = 'actionicon_pay.png'
text = _('CollegePAY')
target = 'goto_interswitch'
@property
def target_url(self):
if self.context.p_state != 'unpaid':
return ''
return self.view.url(self.view.context, self.target)
class InterswitchActionButtonApplicant(APABApplicant):
grok.order(1)
grok.context(ICustomApplicantOnlinePayment)
grok.require('waeup.payApplicant')
icon = 'actionicon_pay.png'
text = _('CollegePAY')
target = 'goto_interswitch'
@property
def target_url(self):
if self.context.p_state != 'unpaid':
return ''
return self.view.url(self.view.context, self.target)
# Deprecated
#class InterswitchRequestCallbackActionButtonStudent(RCABStudent):
# grok.order(3)
# grok.context(ICustomStudentOnlinePayment)
# icon = 'actionicon_call.png'
# text = _('Request CollegePAY callback')
# def target_url(self):
# if self.context.p_state == 'paid':
# return ''
# site_redirect_url = self.view.url(self.view.context, 'isw_callback')
# args = {
# 'transRef':self.context.p_id,
# 'prodID':PRODUCT_ID,
# 'redirectURL':site_redirect_url}
# return QUERY_URL + '?%s' % urllib.urlencode(args)
# Alternative preferred solution
class InterswitchRequestWebserviceActionButtonStudent(APABStudent):
grok.order(2)
grok.context(ICustomStudentOnlinePayment)
grok.require('waeup.payStudent')
icon = 'actionicon_call.png'
text = _('Requery CollegePAY')
target = 'request_webservice'
class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
grok.order(2)
grok.context(ICustomApplicantOnlinePayment)
grok.require('waeup.payApplicant')
icon = 'actionicon_call.png'
text = _('Requery CollegePAY')
target = 'request_webservice'
class InterswitchPageStudent(KofaPage):
""" View which sends a POST request to the Interswitch
CollegePAY payment gateway.
"""
grok.context(ICustomStudentOnlinePayment)
grok.name('goto_interswitch')
grok.template('student_goto_interswitch')
grok.require('waeup.payStudent')
label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
submit_button = _('Submit')
action = POST_ACTION
site_name = SITE_NAME
currency = CURRENCY
product_id = PRODUCT_ID
def update(self):
#if self.context.p_state != 'unpaid':
if self.context.p_state == 'paid':
self.flash(_("Payment ticket can't be re-send to CollegePAY."))
self.redirect(self.url(self.context, '@@index'))
return
student = self.student = self.context.student
certificate = getattr(student['studycourse'],'certificate',None)
self.amount_auth = 100 * self.context.amount_auth
xmldict = {}
if certificate is not None:
xmldict['department'] = certificate.__parent__.__parent__.code
xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
else:
xmldict['department'] = None
xmldict['faculty'] = None
self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
tz = getUtility(IKofaUtils).tzinfo
self.local_date_time = to_timezone(
self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
self.site_redirect_url = self.url(self.context, 'request_webservice')
# Provider data
xmldict['detail_ref'] = self.context.p_id
xmldict['provider_acct'] = PROVIDER_ACCT
xmldict['provider_bank_id'] = PROVIDER_BANK_ID
xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
# Institution data
xmldict['institution_acct'] = '000000000000'
xmldict['institution_bank_id'] = '00'
xmldict['institution_amt'] = '0.0'
if self.context.p_category == 'schoolfee':
provider_amt = 1500
if student.current_mode.endswith('_ft'):
self.pay_item_id = '5700'
if student.current_mode in ('ug_ft','de_ft','ct_ft','ume_ft'):
xmldict['institution_acct'] = '2017506430'
xmldict['institution_bank_id'] = '8'
elif student.current_mode in ('dp_ft'):
xmldict['institution_acct'] = '9201805071'
xmldict['institution_bank_id'] = '17'
elif student.current_mode in ('pg_ft'):
xmldict['institution_acct'] = '5330832799'
xmldict['institution_bank_id'] = '51'
elif student.current_mode.endswith('_pt'):
self.pay_item_id = '5701'
if student.current_mode in ('ug_pt','de_pt','ct_pt'):
xmldict['institution_acct'] = '0122009929'
xmldict['institution_bank_id'] = '16'
elif student.current_mode in ('dp_pt'):
xmldict['institution_acct'] = '9201805071'
xmldict['institution_bank_id'] = '17'
elif student.current_mode in ('pg_pt'):
xmldict['institution_acct'] = '0031716047'
xmldict['institution_bank_id'] = '10'
elif self.context.p_category == 'clearance':
self.pay_item_id = '5702'
provider_amt = 1500
xmldict['institution_bank_id'] = '7'
xmldict['institution_acct'] = '1003475516'
if self.context.p_category == 'gown':
self.pay_item_id = '5704'
provider_amt = 0
xmldict['institution_bank_id'] = '7'
xmldict['institution_acct'] = '1016232382'
xmldict['provider_amt'] = 100 * provider_amt
xmldict['institution_item_name'] = self.category
xmldict['institution_name'] = INSTITUTION_NAME
xmldict['institution_amt'] = 100 * (
self.context.amount_auth - provider_amt - 150)
# Interswitch amount is not part of the xml data
if provider_amt == 0:
xmltext = """
""" % xmldict
else:
xmltext = """
""" % xmldict
self.xml_data = """""" % xmltext
return
class InterswitchPageApplicant(KofaPage):
""" View which sends a POST request to the Interswitch
CollegePAY payment gateway.
"""
grok.context(ICustomApplicantOnlinePayment)
grok.require('waeup.payApplicant')
grok.template('applicant_goto_interswitch')
grok.name('goto_interswitch')
label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
submit_button = _('Submit')
action = POST_ACTION
site_name = SITE_NAME
currency = CURRENCY
pay_item_id = '5703'
product_id = PRODUCT_ID
def update(self):
if self.context.p_state != 'unpaid':
self.flash(_("Payment ticket can't be re-send to CollegePAY."))
self.redirect(self.url(self.context, '@@index'))
return
if self.context.__parent__.__parent__.expired \
and self.context.__parent__.__parent__.strict_deadline:
self.flash(_("Payment ticket can't be send to CollegePAY. "
"Application period has expired."))
self.redirect(self.url(self.context, '@@index'))
return
self.applicant = self.context.__parent__
self.amount_auth = 100 * self.context.amount_auth
xmldict = {}
self.category = getUtility(IKofaUtils).PAYMENT_CATEGORIES[self.context.p_category]
tz = getUtility(IKofaUtils).tzinfo
self.local_date_time = to_timezone(
self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
self.site_redirect_url = self.url(self.context, 'request_webservice')
provider_amt = 400
if self.applicant.applicant_id.startswith('pg'):
xmldict['institution_acct'] = '0031716030'
xmldict['institution_bank_id'] = '10'
elif self.applicant.applicant_id.startswith('dp'):
xmldict['institution_acct'] = '9201805071'
xmldict['institution_bank_id'] = '17'
else:
xmldict['institution_acct'] = '6220032503'
xmldict['institution_bank_id'] = '51'
xmldict['detail_ref'] = self.context.p_id
xmldict['provider_amt'] = 100 * provider_amt
xmldict['provider_acct'] = PROVIDER_ACCT
xmldict['provider_bank_id'] = PROVIDER_BANK_ID
xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
xmldict['institution_amt'] = 100 * (self.context.amount_auth - provider_amt - 150)
xmldict['institution_item_name'] = self.context.p_category
xmldict['institution_name'] = INSTITUTION_NAME
# Interswitch amount is not part of the xml data
xmltext = """
""" % xmldict
self.xml_data = """""" % xmltext
return
# Deprecated
#class InterswitchPaymentCallbackPageStudent(UtilityView, grok.View):
# """ Callback view for the CollegePAY gateway
# """
# grok.context(ICustomStudentOnlinePayment)
# grok.name('isw_callback')
# grok.require('waeup.payStudent')
# This view is not yet working for offline querying transactions
# since the query string differs from the query string sent after
# posting transactions. This Interswitch bug must be removed first.
# Alternatively, we could use the webservice only and replace
# the RequestCallbackActionButton by a RequestWebserviceActionButton
# def update(self):
# if self.context.p_state == 'paid':
# self.flash(_('This ticket has already been paid.'))
# return
# student = self.context.student
# query = self.request.form
# write_log_message(self,'callback received: %s' % query)
# self.context.r_card_num = query.get('cardNum', None)
# self.context.r_code = query.get('resp', None)
# self.context.r_pay_reference = query.get('payRef', None)
# self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
# self.context.r_desc = query.get('desc', None)
# if self.context.r_code != '00':
# self.flash(_('Unsuccessful callback: ${a}',
# mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
# write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
# self.context.p_state = 'failed'
# return
# if self.context.r_amount_approved != payment.amount_auth:
# self.flash(_('Wrong amount'))
# write_log_message(
# self,'successful but wrong amount: %s' % self.context.p_id)
# self.context.p_state = 'failed'
# return
# try:
# validation_list = get_SOAP_response(
# PRODUCT_ID, self.context.p_id).split(':')
# Validation does not make sense yet since the query string
# formats are conflicting. We are only printing the validation
# string, nothing else.
# print 'WARNING: Webservice validation is not yet implemented'
# print 'validation list: %s' % validation_list
# except:
# print 'Connection to webservice failed.'
# Add webservice validation here
# write_log_message(self,'valid callback: %s' % self.context.p_id)
# self.context.p_state = 'paid'
# self.context.payment_date = datetime.utcnow()
# actions_after_student_payment(student, self.context, self)
# return
# def render(self):
# self.redirect(self.url(self.context, '@@index'))
# return
# Alternative solution, replaces InterswitchPaymentCallbackPage
class InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
""" Request webservice view for the CollegePAY gateway
"""
grok.context(ICustomStudentOnlinePayment)
grok.name('request_webservice')
grok.require('waeup.payStudent')
def update(self):
ob_class = self.__implemented__.__name__
if self.context.p_state == 'paid':
self.flash(_('This ticket has already been paid.'))
return
student = self.context.student
success, msg, log = query_interswitch(self.context)
student.writeLogMessage(self, log)
if not success:
self.flash(msg)
return
success, msg, log = self.context.doAfterStudentPayment()
if log is not None:
student.writeLogMessage(self, log)
self.flash(msg)
return
def render(self):
self.redirect(self.url(self.context, '@@index'))
return
class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
""" Request webservice view for the CollegePAY gateway
"""
grok.context(ICustomApplicantOnlinePayment)
grok.name('request_webservice')
grok.require('waeup.payApplicant')
def update(self):
if self.context.p_state == 'paid':
self.flash(_('This ticket has already been paid.'))
return
applicant = self.context.__parent__
success, msg, log = query_interswitch(self.context)
applicant.writeLogMessage(self, log)
if not success:
self.flash(msg)
return
success, msg, log = self.context.doAfterApplicantPayment()
if log is not None:
applicant.writeLogMessage(self, log)
self.flash(msg)
return
def render(self):
self.redirect(self.url(self.context, '@@index'))
return