Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/__init__.py
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/__init__.py	(revision 15585)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/__init__.py	(revision 15585)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/applicantsbrowser.py
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/applicantsbrowser.py	(revision 15585)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/applicantsbrowser.py	(revision 15585)
@@ -0,0 +1,117 @@
+## $Id: browser.py 14759 2017-08-03 09:09:54Z henrik $
+##
+## Copyright (C) 2017 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
+##
+import grok
+import hashlib
+from datetime import datetime, timedelta
+from zope.component import getUtility
+from zope.security import checkPermission
+from waeup.kofa.interfaces import IKofaUtils
+from waeup.kofa.utils.helpers import to_timezone
+from waeup.kofa.browser.layout import UtilityView, KofaPage
+from waeup.kofa.browser.viewlets import ManageActionButton
+from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
+from kofacustom.nigeria.etranzact.helpers import (write_payments_log)
+from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
+from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
+from kofacustom.nigeria.interfaces import MessageFactory as _
+
+from kofacustom.nigeria.etranzact.tests import (
+    TERMINAL_ID, HOST, HTTPS, SECRET_KEY, LOGO_URL)
+
+grok.templatedir('browser_templates')
+
+def module_activated(session):
+    try:
+        return getattr(grok.getSite()['configuration'][str(session)],
+            'etranzact_enabled', False)
+    except KeyError:
+        return False
+
+# Buttons
+
+class EtranzactActionButtonApplicant(ManageActionButton):
+    grok.order(1)
+    grok.context(INigeriaOnlinePayment)
+    grok.view(OPDPApplicant)
+    grok.require('waeup.payApplicant')
+    icon = 'actionicon_pay.png'
+    text = _('Pay via eTranzact')
+    target = 'goto_etranzact'
+
+    @property
+    def target_url(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return ''
+        if self.context.p_state != 'unpaid':
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+# Forwarding pages
+
+class EtranzactPageApplicant(KofaPage):
+    """ View which sends a POST request to the eTranzact payment gateway.
+    """
+    grok.context(INigeriaApplicantOnlinePayment)
+    grok.name('goto_etranzact')
+    grok.template('goto_etranzact')
+    grok.require('waeup.payApplicant')
+    label = _('Pay via eTranzact')
+    submit_button = _('Pay now')
+
+    host = HOST
+    https = HTTPS
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+    logo_url = LOGO_URL
+
+    @property
+    def action(self):
+        if self.https:
+            return 'https://' + self.host + '/webconnect/v3/caller.jsp'
+        return 'http://' + self.host + '/webconnect/v3/caller.jsp'
+
+    def init_update(self):
+        if self.context.p_state == 'paid':
+            return _("Payment ticket can't be re-sent to eTranzact.")
+        now = datetime.utcnow()
+        if self.context.creation_date.tzinfo is not None:
+            # That's bad. Please store timezone-naive datetimes only!
+            now = self.context.creation_date.tzinfo.localize(now)
+        time_delta = now - self.context.creation_date
+        if time_delta.days > 7:
+            return _("This payment ticket is too old. Please create a new ticket.")
+        self.responseurl = self.url(self.context, 'query_etranzact')
+        # Already now it becomes a eTranzact payment
+        self.context.r_company = u'etranzact'
+        hashargs = 	self.amount + self.terminal_id+self.transaction_id \
+            + self.responseurl + self.secret_key
+        self.hashvalue = hashlib.md5(hashargs).hexdigest()
+        self.customer = self.context.__parent__
+        return
+
+    def update(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return
+        self.transaction_id = self.context.p_id
+        self.amount = "%.1f" % self.context.amount_auth
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        return
Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/browser_templates/goto_etranzact.pt
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/browser_templates/goto_etranzact.pt	(revision 15585)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/browser_templates/goto_etranzact.pt	(revision 15585)
@@ -0,0 +1,58 @@
+<form i18n:domain="kofacustom.nigeria"
+  tal:attributes="action view/action" method="POST">
+
+  <input type=hidden name="TERMINAL_ID" tal:attributes="value view/terminal_id">
+  <input type=hidden name="RESPONSE_URL" tal:attributes="value view/responseurl">
+  <input type=hidden name="TRANSACTION_ID" tal:attributes="value view/transaction_id">
+  <input type=hidden name="AMOUNT" tal:attributes="value view/context/amount_auth">
+  <input type=hidden name="EMAIL" tal:attributes="value view/customer/email">
+  <input type=hidden name="PHONENO" tal:attributes="value view/customer/phone">
+  <input type=hidden name="FULL_NAME" tal:attributes="value view/customer/display_fullname">
+  <input type=hidden name="CURRENCY_CODE" value="NGN">
+  <input type=hidden name="CHECKSUM" tal:attributes="value view/hashvalue">
+  <input type=hidden name="LOGO_URL" tal:attributes="value view/logo_url" height="60" width="60">
+
+  <table class="form-table" i18n:domain="waeup.kofa">
+    <tbody>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Total Amount Authorized:</td>
+        <td>
+          <span tal:replace="view/context/amount_auth">AMOUNT</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Transaction Id:</td>
+        <td>
+          <span tal:replace="view/context/p_id">TRANSID</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Customer Name:</td>
+        <td>
+          <span tal:replace="view/customer/display_fullname">CUSTNAME</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Email Address:</td>
+        <td>
+          <span tal:replace="view/customer/email">EMAIL</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Phone Number:</td>
+        <td>
+          <span tal:replace="view/customer/phone">PHONE</span>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+  <br />
+  <input type="submit" class="btn btn-primary" tal:attributes="value view/submit_button"/>
+  <br /><br />
+  <img src="/static/img/etzLogo.png" /><img src="/static/img/webconnectLogo.png" />
+</form>
Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/helpers.py
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/helpers.py	(revision 15585)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/helpers.py	(revision 15585)
@@ -0,0 +1,91 @@
+## $Id: helpers.py 14916 2017-11-30 09:10:38Z henrik $
+##
+## Copyright (C) 2017 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
+##
+"""General helper functions for the etranzact module in custom packages.
+"""
+import grok
+import re
+from datetime import datetime
+from urllib import urlencode
+import httplib
+import hashlib
+import json
+
+from zope.event import notify
+from kofacustom.nigeria.interfaces import MessageFactory as _
+
+def write_payments_log(id, payment):
+    payment.logger.info(
+        '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
+        id, payment.p_id, payment.p_category,
+        payment.amount_auth, payment.r_code,
+        payment.provider_amt, payment.gateway_amt,
+        payment.thirdparty_amt))
+
+def get_caller_response(host, https, terminal_id, transaction_id, responseurl,
+                        amount_auth, email, phone, display_fullname, hashvalue,
+                        logo_url):
+    headers={"Content-type": "application/x-www-form-urlencoded",
+             "Accept": "text/plain"}
+    url = "/webconnect/v3/caller.jsp"
+    if https:
+        h = httplib.HTTPSConnection(host)
+    else:
+        h = httplib.HTTPConnection(host)
+    args = {'TERMINAL_ID': terminal_id,
+            'TRANSACTION_ID': transaction_id,
+            'RESPONSE_URL': responseurl,
+            'AMOUNT': amount_auth,
+            'EMAIL': email,
+            'PHONENO': phone,
+            'FULL_NAME': display_fullname,
+            'CURRENCY_CODE': 'NGN',
+            'CHECKSUM': hashvalue,
+            'LOGO_URL': logo_url,
+            }
+    h.request('POST', url, urlencode(args), headers)
+    return
+    response = h.getresponse()
+    if response.status!=200:
+        return 'Connection error (%s, %s)' % (response.status, response.reason)
+    resp = response.read()
+    return resp
+    #resp = resp.replace('\r\n','')
+    ## eTranzact sends strange HTML tags which must be removed.
+    #resp = re.sub("<.*?>", "", resp)
+    #return resp
+
+def get_query_response(host, terminal_id, transaction_id, https):
+    headers={"Content-type": "application/x-www-form-urlencoded",
+             "Accept": "text/plain"}
+    url = "/webconnect/v3/query.jsp"
+    if https:
+        h = httplib.HTTPSConnection(host)
+    else:
+        h = httplib.HTTPConnection(host)
+    args = {'TERMINAL_ID': terminal_id,
+            'TRANSACTION_ID': transaction_id,
+            'RESPONSE_URL': 'https://www.waeup.org'}
+    h.request('POST', url, urlencode(args), headers)
+    response = h.getresponse()
+    if response.status!=200:
+        return 'Connection error (%s, %s)' % (response.status, response.reason)
+    resp = response.read()
+    resp = resp.replace('\r\n','')
+    # eTranzact sends strange HTML tags which must be removed.
+    resp = re.sub("<.*?>", "", resp)
+    return resp
Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/tests.py
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/tests.py	(revision 15585)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/tests.py	(revision 15585)
@@ -0,0 +1,135 @@
+## $Id: tests.py 15468 2019-06-20 16:17:46Z henrik $
+##
+## Copyright (C) 2017 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
+##
+import os
+import unittest
+import random
+import json
+import hashlib
+from datetime import datetime, timedelta, date
+from zope.component import createObject, getUtility
+from zope.catalog.interfaces import ICatalog
+from hurry.workflow.interfaces import IWorkflowState
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kofa.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.kofa.configuration import SessionConfiguration
+from kofacustom.nigeria.students.payments import NigeriaStudentOnlinePayment
+from kofacustom.nigeria.testing import FunctionalLayer
+from kofacustom.nigeria.etranzact.helpers import (
+    get_query_response, get_caller_response)
+
+#from kofacustom.nigeria.etranzact.helpers import (query_etranzact)
+
+# Also run tests that send requests to external servers?
+#   If you enable this, please make sure the external services
+#   do exist really and are not bothered by being spammed by a test programme.
+
+EXTERNAL_TESTS = True
+
+TERMINAL_ID = '0000000001'
+HOST = 'demo.etranzact.com'
+HTTPS = True
+SECRET_KEY = 'DEMO_KEY'
+LOGO_URL = 'https://iuokada.waeup.org/static_custom/iou_logo.png'
+
+def external_test(func):
+    if not EXTERNAL_TESTS:
+        myself = __file__
+        if myself.endswith('.pyc'):
+            myself = myself[:-1]
+        print "WARNING: external tests are skipped!"
+        print "WARNING: edit %s to enable them." % myself
+        return
+    return func
+
+class HelperTests(unittest.TestCase):
+
+    terminal_id = TERMINAL_ID
+
+    def _create_transaction(self, transaction_id):
+        responseurl = 'http://xxxx'
+        amount = '4444.0'
+        email = 'aa@aa.ng'
+        phone = '12324'
+        display_fullname = 'Tester'
+        logo_url = 'http://xxxx'
+        hashargs = 	amount + self.terminal_id + transaction_id \
+            + responseurl + 'DEMO_KEY'
+        hashvalue = hashlib.md5(hashargs).hexdigest()
+        response = get_caller_response(HOST, HTTPS, self.terminal_id,
+                                       transaction_id, responseurl,
+                                       amount, email, phone,
+                                       display_fullname, hashvalue,
+                                       logo_url)
+        return response
+
+    @external_test
+    def test_query_etranzact(self):
+        transaction_id = str(random.randint(100000000, 999999999))
+        response = get_query_response(HOST, self.terminal_id,
+                                transaction_id, HTTPS)
+        self.assertEqual(
+            response,
+            'Transaction with the given transaction_id (%s) not found'
+            % transaction_id)
+        # Okay, let's create a transaction
+        caller_response = self._create_transaction(transaction_id)
+        self.assertEqual(caller_response, None)
+        # It seems that the transaction has been created but we don't get a
+        # useful response
+        query_response = get_query_response(HOST, self.terminal_id,
+                                transaction_id, HTTPS)
+        self.assertEqual(query_response, '')
+        # The same, an 'empty' response obviously means that the transaction
+        # was found. The result was probably sent to the response_url.
+        return
+
+class EtranzactTestsApplicants(ApplicantsFullSetup):
+    """Tests for the Etranzact payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(EtranzactTestsApplicants, self).setUp()
+        configuration = SessionConfiguration()
+        configuration.academic_session = datetime.now().year - 2
+        configuration.etranzact_enabled = True
+        self.app['configuration'].addSessionConfiguration(configuration)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.manage_path)
+        #IWorkflowState(self.student).setState('started')
+        super(EtranzactTestsApplicants, self).fill_correct_values()
+        self.applicantscontainer.application_fee = 1000.0
+        self.browser.getControl(name="form.nationality").value = ['NG']
+        self.browser.getControl(name="transition").value = ['start']
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Add online").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        self.payment = self.applicant.values()[0]
+        self.payment_url = self.browser.url
+
+    @external_test
+    def test_applicant_etranzact_form(self):
+        # Manager can access Etranzact form
+        self.browser.getLink("Pay via eTranzact").click()
+        self.assertTrue("Pay now" in self.browser.contents)
+        # Means of testing end here.
+        return
+
+
Index: /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interfaces.py
===================================================================
--- /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interfaces.py	(revision 15584)
+++ /main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interfaces.py	(revision 15585)
@@ -129,4 +129,9 @@
         )
 
+    etranzact_enabled = schema.Bool(
+        title = _(u'eTranzact integration enabled'),
+        default = False,
+        )
+
 class ICustomSessionConfigurationAdd(ICustomSessionConfiguration):
     """A session configuration object in add mode.
