## $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="""\
<?xml version="1.0" encoding="utf-8"?>
<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/">
  <soap:Body>
    <getTransactionData xmlns="http://tempuri.org/">
      <product_id>%s</product_id>
      <trans_ref>%s</trans_ref>
    </getTransactionData>
  </soap:Body>
</soap:Envelope>""" % (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 = """<payment_item_detail>
<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
<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" />
</item_details>
</payment_item_detail>""" % xmldict
        else:
            xmltext = """<payment_item_detail>
<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
<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" />
<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" />
</item_details>
</payment_item_detail>""" % xmldict
        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % 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 = """<payment_item_detail>
<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
<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" />
<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" />
</item_details>
</payment_item_detail>""" % xmldict
        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % 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
