source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/webcheckoutbrowser.py @ 17215

Last change on this file since 17215 was 17215, checked in by Henrik Bettermann, 2 years ago

Implement Interswitch WebCheckout?. Tests will follow.

  • Property svn:executable set to *
File size: 13.1 KB
Line 
1## $Id: webcheckoutbrowser.py 16587 2021-08-31 06:28:35Z henrik $
2##
3## Copyright (C) 2021 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19from datetime import datetime, timedelta
20from zope.i18n import translate
21from zope.component import getUtility
22from zope.security import checkPermission
23from xml.dom.minidom import parseString
24from waeup.kofa.interfaces import IKofaUtils
25from waeup.kofa.utils.helpers import to_timezone
26from waeup.kofa.browser.layout import UtilityView, KofaPage, KofaFormPage, action
27from waeup.kofa.browser.viewlets import ManageActionButton
28from waeup.kofa.payments.interfaces import IPayer
29from waeup.kofa.students.interfaces import IStudentsUtils
30from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
31from waeup.kofa.students.browser import StudentBasePDFFormPage
32from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
33from waeup.kofa.applicants.browser import ApplicantBaseDisplayFormPage
34from kofacustom.nigeria.interswitch.helpers import (
35    write_payments_log, confirm_transaction)
36from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
37from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
38from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
39from kofacustom.nigeria.interswitch.tests import (
40    MERCHANT_ID)
41from kofacustom.nigeria.interfaces import MessageFactory as _
42
43GATEWAY_AMT = 0.0
44
45grok.templatedir('browser_templates')
46
47def webcheckout_module_activated(session, payment):
48    if payment.r_company and payment.r_company != 'interswitch':
49        return False
50    try:
51        return getattr(grok.getSite()['configuration'][str(session)],
52            'interswitch_webcheckout_enabled', False)
53    except KeyError:
54        session = datetime.now().year
55        try:
56            return getattr(grok.getSite()['configuration'][str(session)],
57                'interswitch_webcheckout_enabled', False)
58        except KeyError:
59            return False
60
61# Buttons
62
63class WebcheckoutActionButtonStudent(ManageActionButton):
64    grok.context(INigeriaOnlinePayment)
65    grok.view(OPDPStudent)
66    grok.require('waeup.payStudent')
67    grok.order(10)
68    icon = 'actionicon_pay.png'
69    text = _('Pay via Interswitch WebCheckout')
70    target = 'webcheckout'
71
72    @property
73    def target_url(self):
74        if not webcheckout_module_activated(
75            self.context.student.current_session, self.context):
76            return ''
77        if self.context.p_state == 'paid':
78            return ''
79        return self.view.url(self.view.context, self.target)
80
81class WebcheckoutActionButtonApplicant(WebcheckoutActionButtonStudent):
82    grok.view(OPDPApplicant)
83    grok.require('waeup.payApplicant')
84
85    @property
86    def target_url(self):
87        if not webcheckout_module_activated(
88            self.context.__parent__.__parent__.year, self.context):
89            return ''
90        if self.context.p_state != 'unpaid':
91            return ''
92        return self.view.url(self.view.context, self.target)
93
94class WebCheckoutConfirmTransactionActionButtonStudent(ManageActionButton):
95    grok.order(11)
96    grok.context(INigeriaOnlinePayment)
97    grok.view(OPDPStudent)
98    grok.require('waeup.payStudent')
99    icon = 'actionicon_call.png'
100    text = _('Confirm WebCheckout Transaction')
101    target = 'confirm_transaction'
102
103    @property
104    def target_url(self):
105        if not webcheckout_module_activated(
106            self.context.student.current_session, self.context):
107            return ''
108        if self.context.amount_auth == 0:
109            return ''
110        if self.context.p_state in ('paid', 'waived', 'scholarship'):
111            return ''
112        return self.view.url(self.view.context, self.target)
113
114class WebCheckoutConfirmTransactionActionButtonApplicant(
115    WebCheckoutConfirmTransactionActionButtonStudent):
116    grok.view(OPDPApplicant)
117    grok.require('waeup.payApplicant')
118
119    @property
120    def target_url(self):
121        if not webcheckout_module_activated(
122            self.context.__parent__.__parent__.year, self.context):
123            return ''
124        if self.context.amount_auth == 0:
125            return ''
126        if self.context.p_state in ('paid', 'waived', 'scholarship'):
127            return ''
128        return self.view.url(self.view.context, self.target)
129
130# Forwarding pages
131
132class WebCheckoutPageStudent(KofaPage):
133    """ View which sends a POST request to the Interswitch
134    WebCheckout payment gateway.
135    """
136    grok.context(INigeriaOnlinePayment)
137    grok.name('webcheckout')
138    grok.template('student_goto_webcheckout')
139    grok.require('waeup.payStudent')
140    label = _('Submit data to Interswitch')
141    submit_button = _('Submit')
142
143    action = None
144    currency = None
145    pay_item_id = None
146    merchant_code = None
147    gateway_amt = GATEWAY_AMT
148
149    def init_update(self):
150        if self.context.p_state == 'paid':
151            return _("Payment ticket can't be re-sent to WebCheckout.")
152        now = datetime.utcnow()
153        if self.context.creation_date.tzinfo is not None:
154            # That's bad. Please store timezone-naive datetimes only!
155            now = self.context.creation_date.tzinfo.localize(now)
156        time_delta = now - self.context.creation_date
157        if time_delta.days > 7:
158            return _("This payment ticket is too old. Please create a new ticket.")
159        if self.context.r_company and self.context.r_company != 'interswitch':
160            return _("Payment ticket has been used for another payment gateway.")
161        student = self.context.student
162        kofa_utils = getUtility(IKofaUtils)
163        student_utils = getUtility(IStudentsUtils)
164        if student_utils.samePaymentMade(student, self.context.p_category,
165            self.context.p_item, self.context.p_session):
166            return _("This type of payment has already been made.")
167        xmldict = {}
168        self.category = self.context.category
169        tz = kofa_utils.tzinfo
170        self.local_date_time = to_timezone(
171            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
172        self.site_redirect_url = self.url(self.context, 'confirm_transaction')
173        self.student = student
174        return
175
176    def update(self):
177        if not webcheckout_module_activated(
178            self.context.student.current_session, self.context):
179            self.flash(_('Forbidden'), type='danger')
180            self.redirect(self.url(self.context, '@@index'))
181            return
182        error = self.init_update()
183        if error:
184            self.flash(error, type='danger')
185            self.redirect(self.url(self.context, '@@index'))
186        # Already now it becomes an Interswitch payment. We set the net amount
187        # and add the gateway amount.
188        if not self.context.r_company:
189            self.context.net_amt = self.context.amount_auth
190            self.context.amount_auth += self.gateway_amt
191            self.context.gateway_amt = self.gateway_amt
192            self.context.r_company = u'interswitch'
193        self.amount_auth = int(100 * self.context.amount_auth)
194        return
195
196class WebCheckoutPageApplicant(KofaPage):
197    """ View which sends a POST request to the Interswitch
198    WebCheckout payment gateway.
199    """
200    grok.context(INigeriaApplicantOnlinePayment)
201    grok.require('waeup.payApplicant')
202    grok.template('applicant_goto_webcheckout')
203    grok.name('webcheckout')
204    label = _('Submit data to Interswitch')
205    submit_button = _('Submit')
206
207    action = 'https://newwebpay.interswitchng.com/collections/w/pay'
208    currency = '566'
209    pay_item_id = 'Default_Payable_MX76823'
210    merchant_code = 'MX76823'
211    gateway_amt = GATEWAY_AMT
212
213    def init_update(self):
214        if self.context.p_state != 'unpaid':
215            return _("Payment ticket can't be re-sent to WebCheckout.")
216        if self.context.__parent__.__parent__.expired \
217            and self.context.__parent__.__parent__.strict_deadline:
218            return _("Payment ticket can't be sent to WebCheckout. "
219                     "Application period has expired.")
220        if self.context.r_company and self.context.r_company != 'interswitch':
221            return _("Payment ticket has been used for another payment gateway.")
222        tz = getUtility(IKofaUtils).tzinfo
223        time_delta = datetime.utcnow() - self.context.creation_date
224        if time_delta.days > 7:
225            return _("This payment ticket is too old. Please create a new ticket.")
226        self.applicant = self.context.__parent__
227        self.category = self.context.category
228        tz = getUtility(IKofaUtils).tzinfo
229        self.local_date_time = to_timezone(
230            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
231        self.site_redirect_url = self.url(self.context, 'confirm_transaction')
232        return
233
234    def update(self):
235        if not webcheckout_module_activated(
236            self.context.__parent__.__parent__.year, self.context):
237            self.flash(_('Forbidden'), type='danger')
238            self.redirect(self.url(self.context, '@@index'))
239            return
240        error = self.init_update()
241        if error:
242            self.flash(error, type='danger')
243            self.redirect(self.url(self.context, '@@index'))
244        # Already now it becomes an Interswitch payment. We set the net amount
245        # and add the gateway amount.
246        if not self.context.r_company:
247            self.context.net_amt = self.context.amount_auth
248            self.context.amount_auth += self.gateway_amt
249            self.context.gateway_amt = self.gateway_amt
250            self.context.r_company = u'interswitch'
251        self.amount_auth = int(100 * self.context.amount_auth)
252        return
253
254# Webservice request views
255
256class WebCheckoutConfirmTransactionPageApplicant(UtilityView, grok.View):
257    """ Request webservice view for the WebCheckout gateway
258    """
259    grok.context(INigeriaApplicantOnlinePayment)
260    grok.name('confirm_transaction')
261    grok.require('waeup.payApplicant')
262
263    merchant_code = 'MX76823'
264    gateway_host = 'webpay.interswitchng.com'
265    gateway_url = '/collections/api/v1/gettransaction.json'
266    https = True
267
268    def update(self):
269        if not webcheckout_module_activated(
270            self.context.__parent__.__parent__.year, self.context):
271            return
272        if self.context.p_state == 'paid':
273            self.flash(_('This ticket has already been paid.'), type='danger')
274            return
275        applicant = self.context.__parent__
276        success, msg, log = confirm_transaction(
277            self.context,
278            self.merchant_code,
279            self.gateway_host,
280            self.gateway_url,
281            self.https)
282        applicant.writeLogMessage(self, log)
283        if not success:
284            self.flash(msg, type='danger')
285            return
286        write_payments_log(applicant.applicant_id, self.context)
287        flashtype, msg, log = self.context.doAfterApplicantPayment()
288        if log is not None:
289            applicant.writeLogMessage(self, log)
290        self.flash(msg, type=flashtype)
291        return
292
293    def render(self):
294        self.redirect(self.url(self.context.__parent__, 'edit'))
295        return
296
297class WebCheckoutConfirmTransactionRequestPageStudent(UtilityView, grok.View):
298    """ Request webservice view for the WebCheckout gateway
299    """
300    grok.context(INigeriaStudentOnlinePayment)
301    grok.name('confirm_transaction')
302    grok.require('waeup.payStudent')
303
304    merchant_code = 'MX76823'
305    gateway_host = 'webpay.interswitchng.com'
306    gateway_url = '/collections/api/v1/gettransaction.json'
307    https = True
308
309    def update(self):
310        if not webcheckout_module_activated(
311            self.context.student.current_session, self.context):
312            return
313        if self.context.p_state in ('paid', 'waived', 'scholarship'):
314            self.flash(_('This ticket has already been paid.'), type='danger')
315            return
316        student = self.context.student
317        success, msg, log = confirm_transaction(
318            self.context,
319            self.merchant_code,
320            self.gateway_host,
321            self.gateway_url,
322            self.https)
323        student.writeLogMessage(self, log)
324        if not success:
325            self.flash(msg, type='danger')
326            return
327        write_payments_log(student.student_id, self.context)
328        flashtype, msg, log = self.context.doAfterStudentPayment()
329        if log is not None:
330            student.writeLogMessage(self, log)
331        self.flash(msg, type=flashtype)
332        return
333
334    def render(self):
335        self.redirect(self.url(self.context, '@@index'))
336        return
337
Note: See TracBrowser for help on using the repository browser.