## $Id: paydirectbrowser.py 16496 2021-05-31 13:22:19Z henrik $
##
## Copyright (C) 2021 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
from datetime import datetime, timedelta
from zope.i18n import translate
from zope.component import getUtility
from zope.security import checkPermission
from xml.dom.minidom import parseString
from waeup.kofa.interfaces import IKofaUtils
from waeup.kofa.utils.helpers import to_timezone
from waeup.kofa.browser.layout import UtilityView, KofaPage, KofaFormPage, action
from waeup.kofa.browser.viewlets import ManageActionButton
from waeup.kofa.students.interfaces import IStudentsUtils
from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
from waeup.kofa.students.browser import StudentBasePDFFormPage
from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
from waeup.kofa.applicants.browser import ApplicantBaseDisplayFormPage
from kofacustom.nigeria.interswitch.helpers import (
query_interswitch, write_payments_log,
fetch_booking_details, create_paydirect_booking)
from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
from kofacustom.nigeria.interswitch.tests import (
PAYDIRECT_URL, PAYDIRECT_HOST, MERCHANT_ID)
from kofacustom.nigeria.interfaces import MessageFactory as _
GATEWAY_AMT = 300.0
grok.templatedir('browser_templates')
def paydirect_module_activated(session, payment):
if payment.r_company and payment.r_company != 'interswitch':
return False
try:
return getattr(grok.getSite()['configuration'][str(session)],
'interswitch_paydirect_enabled', False)
except KeyError:
session = datetime.now().year
try:
return getattr(grok.getSite()['configuration'][str(session)],
'interswitch_paydirect_enabled', False)
except KeyError:
return False
class PAYDirectActionButtonStudent(ManageActionButton):
grok.order(2)
grok.context(INigeriaOnlinePayment)
grok.view(OPDPStudent)
grok.require('waeup.payStudent')
icon = 'actionicon_pay.png'
text = _('Pay via Interswitch PAYDirect')
target = 'paydirect'
@property
def target_url(self):
if not paydirect_module_activated(
self.context.student.current_session, self.context):
return ''
if self.context.p_state == 'paid':
return ''
return self.view.url(self.view.context, self.target)
class PAYDirectActionButtonApplicant(PAYDirectActionButtonStudent):
grok.view(OPDPApplicant)
grok.require('waeup.payApplicant')
@property
def target_url(self):
if not paydirect_module_activated(
self.context.__parent__.__parent__.year, self.context):
return ''
if self.context.p_state != 'unpaid':
return ''
return self.view.url(self.view.context, self.target)
class PAYDirectPageStudent(KofaFormPage):
"""Inform student how to proceed
"""
grok.context(INigeriaStudentOnlinePayment)
grok.template('paydirect')
grok.name('paydirect')
grok.require('waeup.payStudent')
label = _('Requery Interswitch PAYDirect History')
gateway_amt = GATEWAY_AMT
merchant_id = MERCHANT_ID
gateway_url = PAYDIRECT_URL
gateway_host = PAYDIRECT_HOST
https = True
def init_update(self):
if self.context.p_state == 'paid':
return _("Payment ticket can't be used again.")
if self.context.r_company and self.context.r_company != 'interswitch':
return _("Payment ticket has been used for another payment gateway.")
self.ref_number = self.merchant_id + self.context.p_id[1:]
# Create a PAYDirect Booking
if not self.context.r_company:
result_xml = create_paydirect_booking(
self.merchant_id, self.context, self.gateway_host,
self.gateway_url, True)
if result_xml.startswith('Connection error'):
return result_xml
doc=parseString(result_xml)
if not doc.getElementsByTagName('ResponseCode'):
return _('Invalid callback from Interswitch')
rc = doc.getElementsByTagName('ResponseCode')[0].firstChild.data
if rc not in ('100', '108'):
return 'Error response code from Interswitch: %s' % rc
return
def update(self):
if not paydirect_module_activated(
self.context.student.current_session, self.context):
self.flash(_('Forbidden'), type='danger')
self.redirect(self.url(self.context, '@@index'))
return
error = self.init_update()
if error:
self.flash(error, type='danger')
self.redirect(self.url(self.context, '@@index'))
return
# Already now it becomes an Interswitch payment. We set the net amount
# and add the gateway amount.
if not self.context.r_company:
self.context.net_amt = self.context.amount_auth
self.context.amount_auth += self.gateway_amt
self.context.gateway_amt = self.gateway_amt
self.context.r_company = u'interswitch'
return
@action('Requery now', style='primary')
def fetch(self):
student = self.context.student
success, msg, log = fetch_booking_details(
self.context, self.merchant_id, self.gateway_host,
self.gateway_url, self.https)
student.writeLogMessage(self, log)
if not success:
self.flash(msg, type='warning')
self.redirect(self.url(self.context, '@@index'))
return
write_payments_log(student.student_id, self.context)
flashtype, msg, log = self.context.doAfterStudentPayment()
if log is not None:
student.writeLogMessage(self, log)
self.flash(msg, type=flashtype)
self.redirect(self.url(self.context, '@@index'))
return
class PAYDirectPageApplicant(PAYDirectPageStudent):
""" Inform applicant how to proceed.
"""
grok.context(INigeriaApplicantOnlinePayment)
grok.require('waeup.payApplicant')
def update(self):
if not paydirect_module_activated(
self.context.__parent__.__parent__.year, self.context):
self.flash(_('Forbidden'), type='danger')
self.redirect(self.url(self.context, '@@index'))
return
error = self.init_update()
if error:
self.flash(error, type='danger')
self.redirect(self.url(self.context, '@@index'))
if self.context.__parent__.__parent__.expired \
and self.context.__parent__.__parent__.strict_deadline:
return _("Payment ticket can't be used. "
"Application period has expired.")
# Already now it becomes an Interswitch payment. We set the net amount
# and add the gateway amount.
if not self.context.r_company:
self.context.net_amt = self.context.amount_auth
self.context.amount_auth += self.gateway_amt
self.context.gateway_amt = self.gateway_amt
self.context.r_company = u'interswitch'
return
@action('Requery now', style='primary')
def fetch(self):
applicant = self.context.__parent__
success, msg, log = fetch_booking_details(
self.context, self.merchant_id, self.gateway_host,
self.gateway_url, self.https)
applicant.writeLogMessage(self, log)
if not success:
self.flash(msg, type='warning')
self.redirect(self.url(self.context, '@@index'))
return
write_payments_log(applicant.applicant_id, self.context)
flashtype, msg, log = self.context.doAfterApplicantPayment()
if log is not None:
applicant.writeLogMessage(self, log)
self.flash(msg, type=flashtype)
return
class StudentPaymentRefNumberSlipActionButton(ManageActionButton):
grok.order(1)
grok.context(INigeriaStudentOnlinePayment)
grok.view(PAYDirectPageStudent)
grok.require('waeup.viewStudent')
icon = 'actionicon_pdf.png'
text = _('Download reference number slip')
target = 'refnumberslip.pdf'
@property
def target_url(self):
if self.context.p_state == 'paid':
return ''
return self.view.url(self.view.context, self.target)
class ApplicantPaymentRefNumberSlipActionButton(StudentPaymentRefNumberSlipActionButton):
grok.context(INigeriaApplicantOnlinePayment)
grok.view(PAYDirectPageApplicant)
grok.require('waeup.viewApplication')
class StudentRefNumberSlip(UtilityView, grok.View):
"""Deliver a PDF slip of the context.
"""
grok.context(INigeriaStudentOnlinePayment)
grok.name('refnumberslip.pdf')
grok.require('waeup.viewStudent')
form_fields = grok.AutoFields(INigeriaOnlinePayment).select(
'amount_auth', 'p_category')
prefix = 'form'
omit_fields = (
'password', 'suspended', 'phone', 'date_of_birth',
'adm_code', 'sex', 'suspended_comment', 'current_level',
'flash_notice', 'parents_email', 'current_mode',
'entry_session', 'reg_number')
merchant_id = MERCHANT_ID
@property
def refnumber(self):
return self.merchant_id + self.context.p_id[1:]
@property
def label(self):
portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
return translate(_('Interswitch PAYDirect Payment'),
'waeup.kofa', target_language=portal_language) \
+ ' %s' % self.refnumber
@property
def note(self):
return """
Go to your bank and make your PAYDirect payment with the reference number %s.
""" % self.refnumber
def render(self):
if self.context.p_state == 'paid':
self.flash('Payment has already been made.')
self.redirect(self.url(self.context))
return
studentview = StudentBasePDFFormPage(self.context.student,
self.request, self.omit_fields)
students_utils = getUtility(IStudentsUtils)
return students_utils.renderPDF(self, 'refnumberslip.pdf',
self.context.student, studentview, note=self.note,
omit_fields=self.omit_fields)
class ApplicantRefNumberSlip(StudentRefNumberSlip):
"""Deliver a PDF slip of the context.
"""
grok.context(INigeriaApplicantOnlinePayment)
grok.require('waeup.viewApplication')
@property
def note(self):
return """
Go to your bank and make your PAYDirect payment with the reference number %s.
""" % self.refnumber
def render(self):
if self.context.p_state == 'paid':
self.flash('Payment has already been made.')
self.redirect(self.url(self.context))
return
applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
self.request)
students_utils = getUtility(IStudentsUtils)
return students_utils.renderPDF(self, 'refnumberslip.pdf',
self.context.__parent__, applicantview, note=self.note,
omit_fields=self.omit_fields)