## $Id: browser.py 12975 2015-05-21 17:05:42Z 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
import urllib2
from xml.dom.minidom import parseString
import grok
from zope.component import getUtility
from zope.catalog.interfaces import ICatalog
from waeup.kofa.interfaces import IUniversity, CLEARED
from waeup.kofa.payments.interfaces import IPayer
from waeup.kofa.webservices import PaymentDataWebservice
from waeup.kofa.browser.layout import KofaPage, UtilityView
from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
from waeup.aaue.interfaces import academic_sessions_vocab
from kofacustom.nigeria.interswitch.browser import (
    InterswitchActionButtonStudent,
    InterswitchRequestWebserviceActionButtonStudent,
    InterswitchActionButtonApplicant,
    InterswitchRequestWebserviceActionButtonApplicant)
from waeup.aaue.interfaces import MessageFactory as _
from waeup.aaue.students.interfaces import ICustomStudentOnlinePayment
from waeup.aaue.applicants.interfaces import ICustomApplicantOnlinePayment

ERROR_PART1 = (
        'PayeeName=N/A~'
        + 'Faculty=N/A~'
        + 'Department=N/A~'
        + 'Level=N/A~'
        + 'ProgrammeType=N/A~'
        + 'StudyType=N/A~'
        + 'Session=N/A~'
        + 'PayeeID=N/A~'
        + 'Amount=N/A~'
        + 'FeeStatus=')
ERROR_PART2 = (
        '~Semester=N/A~'
        + 'PaymentType=N/A~'
        + 'MatricNumber=N/A~'
        + 'Email=N/A~'
        + 'PhoneNumber=N/A')

class CustomPaymentDataWebservice(PaymentDataWebservice):
    """A simple webservice to publish payment and payer details on request from
    accepted IP addresses without authentication.

    Etranzact is asking for the PAYEE_ID which is indeed misleading.
    These are not the data of the payee but of the payer. And it's
    not the id of the payer but of the payment.
    """
    grok.name('feerequest')

    #ACCEPTED_IP = ('195.219.3.181', '195.219.3.184')
    ACCEPTED_IP = None

    def update(self, PAYEE_ID=None, PAYMENT_TYPE=None):
        if PAYEE_ID == None:
            self.output = ERROR_PART1 + 'Missing PAYEE_ID' + ERROR_PART2
            return
        real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)
        # We can forego the logging once eTranzact payments run smoothly
        # and the accepted IP addresses are used.
        if real_ip:
            self.context.logger.info('PaymentDataWebservice called: %s' % real_ip)
        if real_ip  and self.ACCEPTED_IP:
            if real_ip not in  self.ACCEPTED_IP:
                self.output = ERROR_PART1 + 'Wrong IP address' + ERROR_PART2
                return
        if PAYMENT_TYPE not in ('SCHOOL-FEE-RETURNING', 'SCHOOL-FEE-NEW',
            'ACCEPTANCE-FEE', 'APPLICATION-FEE', 'LATE-REGISTRATION'):
            self.output = ERROR_PART1 + 'Invalid PAYMENT_TYPE' + ERROR_PART2
            return

        # It seems eTranzact sends a POST request with an empty body but the URL
        # contains a query string. So it's actually a GET request pretended
        # to be a POST request. Although this does not comply with the
        # RFC 2616 HTTP guidelines we may try to fetch the id from the QUERY_STRING
        # value of the request.
        #if PAYEE_ID is None:
        #    try:
        #        PAYEE_ID = self.request['QUERY_STRING'].split('=')[1]
        #    except:
        #        self.output = '-4'
        #        return

        cat = getUtility(ICatalog, name='payments_catalog')
        results = list(cat.searchResults(p_id=(PAYEE_ID, PAYEE_ID)))
        if len(results) != 1:
            self.output = ERROR_PART1 + 'Invalid PAYEE_ID' + ERROR_PART2
            return
        amount = results[0].amount_auth
        payment_type = results[0].category
        programme_type = results[0].p_item
        academic_session = academic_sessions_vocab.getTerm(
            results[0].p_session).title
        status = results[0].p_state
        if status == 'paid':
            self.output = ERROR_PART1 + 'PAYEE_ID already used' + ERROR_PART2
            return
        if PAYMENT_TYPE.startswith('SCHOOL-FEE'):
            try:
                student = results[0].student
            except AttributeError:
                self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
                return
            if not results[0].p_category.startswith('schoolfee'):
                self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
                return
            if PAYMENT_TYPE == 'SCHOOL-FEE-RETURNING' and student.state == CLEARED:
                self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
                return
            if PAYMENT_TYPE == 'SCHOOL-FEE-NEW' and student.state != CLEARED:
                self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
                return
        if PAYMENT_TYPE == 'ACCEPTANCE-FEE' \
            and not results[0].p_category == 'clearance':
            self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
            return
        if PAYMENT_TYPE == 'APPLICATION-FEE' \
            and not results[0].p_category == 'application':
            self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
            return
        if PAYMENT_TYPE == 'LATE-ADMISSION-PROCESSING' \
            and not results[0].p_category == 'late_registration':
            self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2
            return
        try:
            owner = IPayer(results[0])
            full_name = owner.display_fullname
            matric_no = owner.id
            faculty = owner.faculty
            department = owner.department
            study_type = owner.current_mode
            email = owner.email
            phone = owner.phone
            level = owner.current_level
        except (TypeError, AttributeError):
            self.output = ERROR_PART1 +  'Unknown error' + ERROR_PART2
            return
        self.output = (
            'PayeeName=%s~' +
            'Faculty=%s~' +
            'Department=%s~' +
            'Level=%s~' +
            'ProgrammeType=%s~' +
            'StudyType=%s~' +
            'Session=%s~' +
            'PayeeID=%s~' +
            'Amount=%s~' +
            'FeeStatus=%s~' +
            'Semester=N/A~' +
            'PaymentType=%s~' +
            'MatricNumber=%s~' +
            'Email=%s~' +
            'PhoneNumber=%s'

            ) % (full_name, faculty,
            department, level, programme_type, study_type,
            academic_session, PAYEE_ID, amount, status, payment_type,
            matric_no, email, phone)
        return


# Requerying eTranzact payments

TERMINAL_ID = '0570000070'
QUERY_URL =   'https://www.etranzact.net/WebConnectPlus/query.jsp'

# Test environment
#QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
#TERMINAL_ID = '5009892289'

def query_etranzact(confirmation_number, payment):
    
    postdict = {}
    postdict['TERMINAL_ID'] = TERMINAL_ID
    #postdict['RESPONSE_URL'] = 'http://dummy'
    postdict['CONFIRMATION_NO'] = confirmation_number
    data = urllib.urlencode(postdict)
    payment.conf_number = confirmation_number
    try:
        # eTranzact only accepts HTTP 1.1 requests. Therefore
        # the urllib2 package is required here.
        f = urllib2.urlopen(url=QUERY_URL, data=data)
        success = f.read()
        success = success.replace('\r\n','')
        if 'CUSTOMER_ID' not in success:
            msg = _('Invalid or unsuccessful callback: ${a}',
                mapping = {'a': success})
            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
            payment.p_state = 'failed'
            return False, msg, log
        success = success.replace('%20',' ').split('&')
        # We expect at least two parameters
        if len(success) < 2:
            msg = _('Invalid callback: ${a}', mapping = {'a': success})
            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
            payment.p_state = 'failed'
            return False, msg, log
        try:
            success_dict = dict([tuple(i.split('=')) for i in success])
        except ValueError:
            msg = _('Invalid callback: ${a}', mapping = {'a': success})
            log = 'invalid callback for payment %s: %s' % (payment.p_id, success)
            payment.p_state = 'failed'
            return False, msg, log
    except IOError:
        msg = _('eTranzact IOError')
        log = 'eTranzact IOError'
        return False, msg, log
    payment.r_code = u'ET'
    payment.r_company = u'etranzact'
    payment.r_desc = u'%s' % success_dict.get('TRANS_DESCR')
    payment.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))
    payment.r_card_num = None
    payment.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')
    if payment.r_amount_approved != payment.amount_auth:
        msg = _('Wrong amount')
        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
        payment.p_state = 'failed'
        return False, msg, log
    customer_id = success_dict.get('CUSTOMER_ID')
    if payment.p_id != customer_id:
        msg = _('Wrong payment id')
        log = 'wrong callback for payment %s: %s' % (payment.p_id, success)
        payment.p_state = 'failed'
        return False, msg, log
    log = 'valid callback for payment %s: %s' % (payment.p_id, success)
    msg = _('Successful callback received')
    payment.p_state = 'paid'
    payment.payment_date = datetime.utcnow()
    return True, msg, log

class EtranzactEnterPinActionButtonApplicant(APABApplicant):
    grok.context(ICustomApplicantOnlinePayment)
    grok.require('waeup.payApplicant')
    grok.order(3)
    icon = 'actionicon_call.png'
    text = _('Query eTranzact History')
    target = 'enterpin'

class EtranzactEnterPinActionButtonStudent(APABStudent):
    grok.context(ICustomStudentOnlinePayment)
    grok.require('waeup.payStudent')
    grok.order(3)
    icon = 'actionicon_call.png'
    text = _('Query eTranzact History')
    target = 'enterpin'

class EtranzactEnterPinPageStudent(KofaPage):
    """
    """
    grok.context(ICustomStudentOnlinePayment)
    grok.name('enterpin')
    grok.template('enterpin')
    grok.require('waeup.payStudent')

    buttonname = _('Submit to eTranzact')
    label = _('Requery eTranzact History')
    action = 'query_history'
    placeholder = _('Confirmation Number (PIN)')

    def update(self):
        super(EtranzactEnterPinPageStudent, self).update()
        if self.context.p_category != 'schoolfee':
            return
        student = self.context.student
        if student.state != CLEARED:
            return
        if student.entry_session < 2013:
            return
        for ticket in student['payments'].values():
            if ticket.p_state == 'paid' and \
                ticket.p_category == 'clearance':
                return
        self.flash(_('Please pay acceptance fee first.'), type="danger")
        self.redirect(self.url(self.context, '@@index'))
        return

class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):
    """
    """
    grok.require('waeup.payApplicant')
    grok.context(ICustomApplicantOnlinePayment)

class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):
    """ Query history of eTranzact payments
    """
    grok.context(ICustomStudentOnlinePayment)
    grok.name('query_history')
    grok.require('waeup.payStudent')

    def update(self, confirmation_number=None):
        if self.context.p_state == 'paid':
            self.flash(_('This ticket has already been paid.'))
            return
        student = self.context.student
        success, msg, log = query_etranzact(confirmation_number,self.context)
        student.writeLogMessage(self, log)
        if not success:
            self.flash(msg)
            return
        flashtype, msg, log = self.context.doAfterStudentPayment()
        if log is not None:
            student.writeLogMessage(self, log)
        self.flash(msg, type=flashtype)
        return

    def render(self):
        self.redirect(self.url(self.context, '@@index'))
        return

class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):
    """ Query history of eTranzact payments
    """
    grok.context(ICustomApplicantOnlinePayment)
    grok.name('query_history')
    grok.require('waeup.payApplicant')

    def update(self, confirmation_number=None):
        ob_class = self.__implemented__.__name__
        if self.context.p_state == 'paid':
            self.flash(_('This ticket has already been paid.'))
            return
        applicant = self.context.__parent__
        success, msg, log = query_etranzact(confirmation_number,self.context)
        applicant.writeLogMessage(self, log)
        if not success:
            self.flash(msg)
            return
        flashtype, msg, log = self.context.doAfterApplicantPayment()
        if log is not None:
            applicant.writeLogMessage(self, log)
        self.flash(msg, type=flashtype)
        return

    def render(self):
        self.redirect(self.url(self.context, '@@index'))
        return

# Disable Interswitch viewlets. This could be avoided by defining the
# action button viewlets of kofacustom.nigeria.interswitch.browser in the
# context of INigeriaStudentOnlinePayment or INigeriaApplicantOnlinePayment
# respectively. But then all interswitch.browser modules have to be extended.

#class InterswitchActionButtonStudent(InterswitchActionButtonStudent):

#    @property
#    def target_url(self):
#        return ''

#class InterswitchRequestWebserviceActionButtonStudent(
#    InterswitchRequestWebserviceActionButtonStudent):

#    @property
#    def target_url(self):
#        return ''

#class InterswitchActionButtonApplicant(InterswitchActionButtonApplicant):

#    @property
#    def target_url(self):
#        return ''

#class InterswitchRequestWebserviceActionButtonApplicant(
#    InterswitchRequestWebserviceActionButtonApplicant):

#    @property
#    def target_url(self):
#        return ''