Index: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py
===================================================================
--- main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py	(revision 14780)
+++ main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py	(revision 14781)
@@ -19,4 +19,5 @@
 import unittest
 import random
+import json
 from datetime import datetime, timedelta, date
 from zope.component import createObject, getUtility
@@ -212,5 +213,5 @@
         self.assertTrue('Callback amount does not match.'
             in self.browser.contents)
-        self.payment.amount_auth = 1000.0
+        self.payment.amount_auth = self.amount
         self.browser.getLink("Requery Remita Payment Status").click()
         self.assertTrue('Callback order id does not match.'
@@ -224,4 +225,106 @@
         self.browser.getLink("Verify Remita Payment Status").click()
         self.assertTrue('Successful callback received' in self.browser.contents)
+        return
+
+class RemitaTestsWebserviceStudent(StudentsFullSetup):
+    """Tests for the Remita payment gateway.
+    """
+    layer = FunctionalLayer
+
+    merchantId = '2547916'
+    serviceTypeId = '4430731'
+    api_key = '1946'
+    responseurl = 'http://xxxx'
+    host = 'www.remitademo.net'
+
+    # successful transaction
+    # (hopefully this transaction remains in the Remita database)
+    orderId = '3456346346'
+    rrr = u'280007640804'
+    amount = 1000.0
+
+    @external_test
+    def test_payment_notification_listener_student(self):
+        payment = createObject('waeup.StudentOnlinePayment')
+        payment.p_category = u'schoolfee'
+        payment.p_session = self.student.current_session
+        payment.p_item = u'My Certificate'
+        payment.p_id = self.orderId
+        self.student['payments']['anykey'] = payment
+        data = [{'orderRef': self.orderId, 'rrr': self.rrr},]
+        # Send POST request with wrong payment amount
+        payment.amount_auth = 2000.0
+        self.browser.post('http://localhost/app/paymentnotificationlistener',
+            json.dumps(data), 'application/json; charset=utf-8')
+        self.assertEqual('0 (1)', self.browser.contents)
+        # Send POST request with correct payment amount
+        payment.amount_auth = self.amount
+        self.browser.post('http://localhost/app/paymentnotificationlistener',
+            json.dumps(data), 'application/json; charset=utf-8')
+        self.assertEqual('1 (1)', self.browser.contents)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'students.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.anybody - kofacustom.nigeria.remita.webservices.PaymentNotificationListenerWebservice'
+            ' - K1000000 - valid callback for schoolfee payment %s: '
+            % self.orderId   in logcontent)
+        self.assertTrue(
+            'zope.anybody - kofacustom.nigeria.remita.webservices.PaymentNotificationListenerWebservice'
+            ' - K1000000 - successful schoolfee payment: %s'
+            % self.orderId in logcontent)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'main.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.anybody - PaymentNotificationListenerWebservice called: '
+            '[{"rrr": "%s", "orderRef": "%s"}]'
+            % (self.rrr, self.orderId) in logcontent)
+        return
+
+class RemitaTestsWebserviceApplicant(ApplicantsFullSetup):
+    """Tests for the Remita payment gateway.
+    """
+    layer = FunctionalLayer
+
+    merchantId = '2547916'
+    serviceTypeId = '4430731'
+    api_key = '1946'
+    responseurl = 'http://xxxx'
+    host = 'www.remitademo.net'
+
+    # successful transaction
+    # (hopefully this transaction remains in the Remita database)
+    orderId = '3456346346'
+    rrr = u'280007640804'
+    amount = 1000.0
+
+    @external_test
+    def test_payment_notification_listener_applicant(self):
+        self.applicantscontainer.application_fee = self.amount
+        payment = createObject('waeup.ApplicantOnlinePayment')
+        payment.p_category = u'application'
+        payment.p_session = self.applicantscontainer.year
+        payment.p_item = u'My Certificate'
+        payment.p_id = self.orderId
+        payment.amount_auth = self.amount
+        self.applicant['anykey'] = payment
+        IWorkflowState(self.applicant).setState('started')
+        # Send POST request
+        data = [{'orderRef': self.orderId, 'rrr': self.rrr},]
+        self.browser.post('http://localhost/app/paymentnotificationlistener',
+            json.dumps(data), 'application/json; charset=utf-8')
+        self.assertEqual('1 (1)', self.browser.contents)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'applicants.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.anybody - kofacustom.nigeria.remita.webservices.PaymentNotificationListenerWebservice'
+            ' - %s - valid callback for application payment %s: '
+            % (self.applicant.applicant_id, self.orderId) in logcontent)
+        self.assertTrue(
+            'zope.anybody - kofacustom.nigeria.remita.webservices.PaymentNotificationListenerWebservice'
+            ' - %s - successful payment: %s'
+            % (self.applicant.applicant_id, self.orderId) in logcontent)
         return
 
Index: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/webservices.py
===================================================================
--- main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/webservices.py	(revision 14781)
+++ main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/webservices.py	(revision 14781)
@@ -0,0 +1,128 @@
+## $Id$
+##
+## 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 json
+from zope.component import getUtility
+from zope.catalog.interfaces import ICatalog
+from waeup.kofa.interfaces import IUniversity
+from kofacustom.nigeria.remita.helpers import query_remita, write_payments_log
+
+class PaymentNotificationListenerWebservice(grok.View):
+    """A webservice to receive payment notifications from
+    accepted IP addresses without authentication which trigger
+    a query of a Remita transaction status.
+    """
+    grok.context(IUniversity)
+    grok.name('paymentnotificationlistener')
+    grok.require('waeup.Public')
+
+    ACCEPTED_IP = ('127.0.0.1', '209.235.215.199', '162.13.211.91',
+                   '162.13.211.92', '83.138.167.2')
+    # ACCEPTED_IP = ('127.0.0.1', )
+
+    # Here we use Remita test portal data
+    merchantId = '2547916'
+    host = 'www.remitademo.net'
+    https = False
+    api_key = '1946'
+
+    def update(self, P_ID=None):
+
+        # Remita sends a JSON POST request with an array with
+        # multiple JSON objects:
+        # [
+        #    {
+        #       "rrr": "A39359490",
+        #       "channnel": "BRANCH",
+        #       "amount": "6500.00",
+        #       "transactiondate": "19/01/2015",
+        #       "debitdate": "19/01/2015",
+        #       "bank": "033",
+        #       "branch": "033152763",
+        #       "serviceTypeId": "4430731",
+        #       "dateSent": "20/01/2015",
+        #       "dateRequested": "19/01/2015",
+        #       "orderRef": "4086852511",
+        #       "payerName": "mujib ishola",
+        #       "payerEmail": "ishola@specs.com",
+        #       "payerPhoneNumber": "08141376868",
+        #       "uniqueIdentifier": 10001
+        #    },
+        #    {...}
+        # ]
+
+        real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)
+        if real_ip:
+            self.context.logger.info(
+                'PaymentNotificationListenerWebservice called: %s' % real_ip)
+        if real_ip and self.ACCEPTED_IP:
+            if real_ip not in  self.ACCEPTED_IP:
+                self.output = '-1'
+                return
+        resp = self.request.bodyStream.read()
+        self.context.logger.info(
+            'PaymentNotificationListenerWebservice called: %s' % resp)
+        try:
+            parsed_json = json.loads(resp)
+        except ValueError:
+            self.output = '-2'
+            return
+        successful = 0
+        failed = 0
+        for payment in parsed_json:
+            orderRef =  payment.get('orderRef')
+            rrr = payment.get('rrr')
+            if not orderRef or not rrr:
+                failed += 1
+                continue
+            cat = getUtility(ICatalog, name='payments_catalog')
+            results = list(cat.searchResults(p_id=(orderRef, orderRef)))
+            if len(results) != 1:
+                failed += 1
+                continue
+            ticket = results[0]
+            success, msg, log = query_remita(
+                ticket, self.merchantId, self.api_key,
+                rrr, self.host, self.https, False)
+            if hasattr(ticket, 'doAfterStudentPayment'):
+                ticket.student.writeLogMessage(self, log)
+                if not success:
+                    failed += 1
+                    continue
+                successful += 1
+                write_payments_log(ticket.student.student_id, ticket)
+                flashtype, msg, log = ticket.doAfterStudentPayment()
+                if log is not None:
+                    ticket.student.writeLogMessage(self, log)
+            else:
+                applicant = ticket.__parent__
+                applicant.writeLogMessage(self, log)
+                if not success:
+                    failed += 1
+                    continue
+                successful += 1
+                write_payments_log(applicant.applicant_id, ticket)
+                flashtype, msg, log = ticket.doAfterApplicantPayment()
+                if log is not None:
+                    applicant.writeLogMessage(self, log)
+        self.output = '%s (%s)' % (successful, failed + successful)
+        return
+
+    def render(self):
+        return self.output
