Changeset 15759 for main/waeup.aaue/trunk/src/waeup
- Timestamp:
- 6 Nov 2019, 16:49:15 (5 years ago)
- Location:
- main/waeup.aaue/trunk/src/waeup/aaue/etranzact
- Files:
-
- 2 deleted
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.aaue/trunk/src/waeup/aaue/etranzact/browser.py
r15346 r15759 1 1 ## $Id$ 2 2 ## 3 ## Copyright (C) 201 2Uli Fouquet & Henrik Bettermann3 ## Copyright (C) 2017 Uli Fouquet & Henrik Bettermann 4 4 ## This program is free software; you can redistribute it and/or modify 5 5 ## it under the terms of the GNU General Public License as published by … … 16 16 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 17 ## 18 from datetime import datetime19 import httplib20 import urllib21 import urllib222 import re23 from xml.dom.minidom import parseString24 import grok25 from zope.component import getUtility26 from zope.catalog.interfaces import ICatalog27 from waeup.kofa.interfaces import IUniversity, CLEARED28 from waeup.kofa.payments.interfaces import IPayer29 from waeup.kofa.webservices import PaymentDataWebservice30 from waeup.kofa.browser.layout import KofaPage, UtilityView31 from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent32 from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant33 from waeup.aaue.interfaces import academic_sessions_vocab34 from kofacustom.nigeria.interswitch.browser import (35 InterswitchActionButtonStudent,36 InterswitchRequestWebserviceActionButtonStudent,37 InterswitchActionButtonApplicant,38 InterswitchRequestWebserviceActionButtonApplicant)39 from waeup.aaue.interfaces import MessageFactory as _40 from waeup.aaue.students.interfaces import ICustomStudentOnlinePayment41 from waeup.aaue.applicants.interfaces import ICustomApplicantOnlinePayment42 18 43 ERROR_PART1 = ( 44 'PayeeName=N/A~' 45 + 'Faculty=N/A~' 46 + 'Department=N/A~' 47 + 'Level=N/A~' 48 + 'ProgrammeType=N/A~' 49 + 'StudyType=N/A~' 50 + 'Session=N/A~' 51 + 'PayeeID=N/A~' 52 + 'Amount=N/A~' 53 + 'FeeStatus=') 54 ERROR_PART2 = ( 55 '~Semester=N/A~' 56 + 'PaymentType=N/A~' 57 + 'MatricNumber=N/A~' 58 + 'Email=N/A~' 59 + 'PhoneNumber=N/A') 19 from kofacustom.nigeria.etranzact.applicantsbrowser import ( 20 EtranzactPageApplicant, EtranzactReceiveResponseApplicant, 21 EtranzactRequestPaymentStatusPageApplicant) 22 from kofacustom.nigeria.etranzact.studentsbrowser import ( 23 EtranzactPageStudent, EtranzactReceiveResponseStudent, 24 EtranzactRequestPaymentStatusPageStudent) 25 from kofacustom.nigeria.etranzact.payoutletbrowser import ( 26 EtranzactEnterPinPageStudent, EtranzactEnterPinPageApplicant, 27 EtranzactQueryHistoryPageStudent, EtranzactQueryHistoryPageApplicant) 28 from kofacustom.nigeria.etranzact.payoutletwebservice import NigeriaPaymentDataWebservice 60 29 61 class CustomPaymentDataWebservice(PaymentDataWebservice): 30 # Temporarily we can use the test portal like in kofacustom.nigeria 31 32 from kofacustom.nigeria.etranzact.tests import ( 33 HOST, HTTPS, SECRET_KEY, TERMINAL_ID, GATEWAY_AMT) 34 35 #HOST = 'www.etranzact.net' 36 #HTTPS = True 37 #SECRET_KEY = 38 #TERMINAL_ID = 0570000070 39 #GATEWAY_AMT = 40 LOGO_URL = 'https://iuokada.waeup.org/static_custom/iou_logo.png' 41 42 class CustomEtranzactPageApplicant(EtranzactPageApplicant): 43 44 host = HOST 45 https = HTTPS 46 secret_key = SECRET_KEY 47 terminal_id = TERMINAL_ID 48 logo_url = LOGO_URL 49 gateway_amt = GATEWAY_AMT 50 51 def update(self): 52 # Already now it becomes an Etranzact payment. We set the net amount 53 # and add the gateway amount. 54 if not self.context.r_company: 55 self.context.net_amt = self.context.amount_auth 56 self.context.amount_auth += self.gateway_amt 57 self.context.gateway_amt = self.gateway_amt 58 self.context.r_company = u'etranzact' 59 self.amount = "%.1f" % self.context.amount_auth 60 error = self.init_update() 61 if error: 62 self.flash(error, type='danger') 63 self.redirect(self.url(self.context, '@@index')) 64 return 65 return 66 67 class CustomEtranzactReceiveResponseApplicant(EtranzactReceiveResponseApplicant): 68 69 secret_key = SECRET_KEY 70 terminal_id = TERMINAL_ID 71 72 class CustomEtranzactRequestPaymentStatusPageApplicant( 73 EtranzactRequestPaymentStatusPageApplicant): 74 75 host = HOST 76 https = HTTPS 77 secret_key = SECRET_KEY 78 terminal_id = TERMINAL_ID 79 logo_url = LOGO_URL 80 81 class CustomEtranzactPageStudent(EtranzactPageStudent): 82 83 host = HOST 84 https = HTTPS 85 secret_key = SECRET_KEY 86 terminal_id = TERMINAL_ID 87 logo_url = LOGO_URL 88 gateway_amt = GATEWAY_AMT 89 90 def update(self): 91 # Already now it becomes an Etranzact payment. We set the net amount 92 # and add the gateway amount. 93 if not self.context.r_company: 94 self.context.net_amt = self.context.amount_auth 95 self.context.amount_auth += self.gateway_amt 96 self.context.gateway_amt = self.gateway_amt 97 self.context.r_company = u'etranzact' 98 self.amount = "%.1f" % self.context.amount_auth 99 error = self.init_update() 100 if error: 101 self.flash(error, type='danger') 102 self.redirect(self.url(self.context, '@@index')) 103 return 104 return 105 106 class CustomEtranzactReceiveResponseStudent(EtranzactReceiveResponseStudent): 107 108 secret_key = SECRET_KEY 109 terminal_id = TERMINAL_ID 110 111 class CustomEtranzactRequestPaymentStatusPageStudent( 112 EtranzactRequestPaymentStatusPageStudent): 113 114 host = HOST 115 https = HTTPS 116 secret_key = SECRET_KEY 117 terminal_id = TERMINAL_ID 118 logo_url = LOGO_URL 119 120 # Payoutlet customizations 121 122 class CustomEtranzactEnterPinPageStudent(EtranzactEnterPinPageStudent): 123 """Enter confirmation PIN and submit to `EtranzactQueryHistoryPageStudent` 124 """ 125 gateway_amt = GATEWAY_AMT 126 127 class CustomEtranzactEnterPinPageApplicant(EtranzactEnterPinPageApplicant): 128 """Enter confirmation PIN and submit to `EtranzactQueryHistoryPageApplicant` 129 """ 130 gateway_amt = GATEWAY_AMT 131 132 class CustomEtranzactQueryHistoryPageStudent(EtranzactQueryHistoryPageStudent): 133 """ Query history of Etranzact payments 134 """ 135 terminal_id = TERMINAL_ID 136 host = HOST 137 https = HTTPS 138 139 class CustomEtranzactQueryHistoryPageApplicant(EtranzactQueryHistoryPageApplicant): 140 """ Query history of Etranzact payments 141 """ 142 terminal_id = TERMINAL_ID 143 host = HOST 144 https = HTTPS 145 146 class CustomPaymentDataWebservice(NigeriaPaymentDataWebservice): 62 147 """A simple webservice to publish payment and payer details on request from 63 148 accepted IP addresses without authentication. 64 65 Etranzact is asking for the PAYEE_ID which is indeed misleading.66 These are not the data of the payee but of the payer. And it's67 not the id of the payer but of the payment.68 149 """ 69 grok.name('feerequest')70 71 150 #ACCEPTED_IP = ('195.219.3.181', '195.219.3.184') 72 151 ACCEPTED_IP = None 73 152 74 def update(self, PAYEE_ID=None, PAYMENT_TYPE=None): 153 CATEGORY_MAPPING = { 154 'SCHOOLFEE': ('schoolfee',), 155 } 75 156 76 # webservice disabled on 6/3/1977 self.output = None78 return79 80 if PAYEE_ID == None:81 self.output = ERROR_PART1 + 'Missing PAYEE_ID' + ERROR_PART282 return83 real_ip = self.request.get('HTTP_X_FORWARDED_FOR', None)84 # We can forego the logging once eTranzact payments run smoothly85 # and the accepted IP addresses are used.86 if real_ip:87 self.context.logger.info('PaymentDataWebservice called: %s' % real_ip)88 if real_ip and self.ACCEPTED_IP:89 if real_ip not in self.ACCEPTED_IP:90 self.output = ERROR_PART1 + 'Wrong IP address' + ERROR_PART291 return92 category_mapping = {93 'SCHOOL-FEE-NEW': ('schoolfee',),94 'SCHOOL-FEE-RETURNING': ('schoolfee',),95 'SCHOOL-FEE-PLUS-NEW': ('schoolfee_incl',),96 'SCHOOL-FEE-PLUS-RETURNING': ('schoolfee_incl',),97 'SCHOOL-FEE-PG-NEW': ('schoolfee',),98 'SCHOOL-FEE-PG-RETURNING': ('schoolfee',),99 'SCHOOL-FEE-FIRST-INSTALMENT-PLUS': ('schoolfee_1',),100 'SCHOOL-FEE-SECOND-INSTALMENT': ('schoolfee_2',),101 'SCHOOL-FEE-BALANCE': ('schoolfee','schoolfee_incl',102 'schoolfee_1','schoolfee_2'),103 'SCHOOL-FEE-PT-NEW': ('schoolfee',),104 'SCHOOL-FEE-PT-RETURNING': ('schoolfee',),105 'SCHOOL-FEE-PT-PLUS-NEW': ('schoolfee_incl',),106 'SCHOOL-FEE-PT-PLUS-RETURNING': ('schoolfee_incl',),107 'SCHOOL-FEE-PT-PG-NEW': ('schoolfee',),108 'SCHOOL-FEE-PT-PG-RETURNING': ('schoolfee',),109 'SCHOOL-FEE-PT-FIRST-INSTALMENT-PLUS': ('schoolfee_1',),110 'SCHOOL-FEE-PT-SECOND-INSTALMENT': ('schoolfee_2',),111 'SCHOOL-FEE-PT-BALANCE': ('schoolfee','schoolfee_incl',112 'schoolfee_1','schoolfee_2'),113 'SCHOOL-FEE-FP-NEW': ('schoolfee',),114 'SCHOOL-FEE-IJMBE': ('schoolfee',),115 116 'ACCEPTANCE-FEE': ('clearance',),117 'ACCEPTANCE-FEE-PLUS': ('clearance_incl',),118 'ACCEPTANCE-FEE-PG': ('clearance',),119 'ACCEPTANCE-FEE-PT': ('clearance',),120 'ACCEPTANCE-FEE-PT-PLUS': ('clearance_incl',),121 'ACCEPTANCE-FEE-PT-PG': ('clearance',),122 'ACCEPTANCE-FEE-FP': ('clearance',),123 'ACCEPTANCE-FEE-IJMBE': ('clearance',),124 125 'APPLICATION-FEE-UTME': ('application',),126 'APPLICATION-FEE-PT': ('application',),127 'APPLICATION-FEE-FP': ('application',),128 129 'LATE-REGISTRATION': ('late_registration',),130 'LATE-REGISTRATION-PT': ('late_registration',),131 132 'AAU-STUDENT-WELFARE-ASSURANCE': ('welfare',),133 'AAU-STUDENT-WELFARE-ASSURANCE-PT': ('welfare',),134 135 'AAU-STUDENT-ID_CARD': ('id_card',),136 137 'HOSTEL-ACCOMMODATION-FEE': ('hostel_maintenance',),138 'HOSTEL-ACCOMMODATION-FEE-PT': ('hostel_maintenance',),139 140 'AAU-LAPEL-FILE-FEE': ('lapel',),141 'AAU-LAPEL-FILE-FEE-PT': ('lapel',),142 143 'MATRICULATION-GOWN-FEE': ('matric_gown',),144 'MATRICULATION-GOWN-FEE-PT': ('matric_gown',),145 146 'CONCESSIONAL-FEE': ('concessional',),147 'CONCESSIONAL-FEE-PT': ('concessional',),148 149 'STUDENTS-UNION-DUES': ('union',),150 'STUDENTS-UNION-DUES-PT': ('union',),151 152 'RESTITUTION-FEE': ('restitution',),153 }154 155 if PAYMENT_TYPE not in category_mapping.keys():156 self.output = ERROR_PART1 + 'Invalid PAYMENT_TYPE' + ERROR_PART2157 return158 159 # It seems eTranzact sends a POST request with an empty body but the URL160 # contains a query string. So it's actually a GET request pretended161 # to be a POST request. Although this does not comply with the162 # RFC 2616 HTTP guidelines we may try to fetch the id from the QUERY_STRING163 # value of the request.164 #if PAYEE_ID is None:165 # try:166 # PAYEE_ID = self.request['QUERY_STRING'].split('=')[1]167 # except:168 # self.output = '-4'169 # return170 171 cat = getUtility(ICatalog, name='payments_catalog')172 results = list(cat.searchResults(p_id=(PAYEE_ID, PAYEE_ID)))173 if len(results) != 1:174 self.output = ERROR_PART1 + 'Invalid PAYEE_ID' + ERROR_PART2175 return176 student = getattr(results[0], 'student', None)177 amount = results[0].amount_auth178 payment_type = results[0].category179 p_category = results[0].p_category180 programme_type = results[0].p_item181 if not programme_type:182 programme_type = 'N/A'183 academic_session = academic_sessions_vocab.getTerm(184 results[0].p_session).title185 status = results[0].p_state186 187 if status == 'paid':188 self.output = ERROR_PART1 + 'PAYEE_ID already used' + ERROR_PART2189 return190 if p_category not in category_mapping[PAYMENT_TYPE]:191 self.output = ERROR_PART1 + 'Wrong PAYMENT_TYPE' + ERROR_PART2192 return193 if student and PAYMENT_TYPE.endswith('-RETURNING') \194 and student.state == CLEARED:195 self.output = ERROR_PART1 + 'Not a returning student' + ERROR_PART2196 return197 if student and PAYMENT_TYPE.endswith('-NEW') \198 and student.state != CLEARED:199 self.output = ERROR_PART1 + 'Not a new student' + ERROR_PART2200 return201 if student and '-PG' in PAYMENT_TYPE and not student.is_postgrad:202 self.output = ERROR_PART1 + 'Not a postgrad student' + ERROR_PART2203 return204 if student and '-PG' not in PAYMENT_TYPE and student.is_postgrad \205 and results[0].p_item != 'Balance':206 self.output = ERROR_PART1 + 'Postgrad student' + ERROR_PART2207 return208 if student and '-PT' in PAYMENT_TYPE \209 and not student.current_mode.endswith('_pt'):210 self.output = ERROR_PART1 + 'Not a part-time student' + ERROR_PART2211 return212 if student and '-PT' not in PAYMENT_TYPE \213 and student.current_mode.endswith('_pt'):214 self.output = ERROR_PART1 + 'Part-time student' + ERROR_PART2215 return216 if student and '-FP' in PAYMENT_TYPE and student.current_mode != 'found':217 self.output = ERROR_PART1 + 'Not a foundation programme student' + ERROR_PART2218 return219 if student and '-FP' not in PAYMENT_TYPE and student.current_mode == 'found':220 self.output = ERROR_PART1 + 'Foundation programme student' + ERROR_PART2221 return222 if student and '-IJMBE' in PAYMENT_TYPE and student.current_mode != 'ijmbe':223 self.output = ERROR_PART1 + 'Not IJMBE student' + ERROR_PART2224 return225 if student and '-IJMBE' not in PAYMENT_TYPE and student.current_mode == 'ijmbe':226 self.output = ERROR_PART1 + 'IJMBE student student' + ERROR_PART2227 return228 if '-BALANCE' in PAYMENT_TYPE and results[0].p_item != 'Balance':229 self.output = ERROR_PART1 + 'Not a balance payment' + ERROR_PART2230 return231 if not '-BALANCE' in PAYMENT_TYPE and results[0].p_item == 'Balance':232 self.output = ERROR_PART1 + 'Balance payment' + ERROR_PART2233 return234 235 try:236 owner = IPayer(results[0])237 full_name = owner.display_fullname238 matric_no = owner.id239 faculty = owner.faculty240 department = owner.department241 study_type = owner.current_mode242 email = owner.email243 phone = owner.phone244 level = owner.current_level245 except (TypeError, AttributeError):246 self.output = ERROR_PART1 + 'Unknown error' + ERROR_PART2247 return248 self.output = (249 'PayeeName=%s~' +250 'Faculty=%s~' +251 'Department=%s~' +252 'Level=%s~' +253 'ProgrammeType=%s~' +254 'StudyType=%s~' +255 'Session=%s~' +256 'PayeeID=%s~' +257 'Amount=%s~' +258 'FeeStatus=%s~' +259 'Semester=N/A~' +260 'PaymentType=%s~' +261 'MatricNumber=%s~' +262 'Email=%s~' +263 'PhoneNumber=%s'264 265 ) % (full_name, faculty,266 department, level, programme_type, study_type,267 academic_session, PAYEE_ID, amount, status, payment_type,268 matric_no, email, phone)269 return270 271 272 # Requerying eTranzact payments273 274 TERMINAL_ID = '0570000070'275 QUERY_URL = 'https://www.etranzact.net/WebConnectPlus/query.jsp'276 277 # Test environment278 #QUERY_URL = 'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'279 #TERMINAL_ID = '5009892289'280 281 def query_etranzact(confirmation_number, payment):282 283 postdict = {}284 postdict['TERMINAL_ID'] = TERMINAL_ID285 #postdict['RESPONSE_URL'] = 'http://dummy'286 postdict['CONFIRMATION_NO'] = confirmation_number287 data = urllib.urlencode(postdict)288 payment.conf_number = confirmation_number289 try:290 # eTranzact only accepts HTTP 1.1 requests. Therefore291 # the urllib2 package is required here.292 f = urllib2.urlopen(url=QUERY_URL, data=data)293 success = f.read()294 success = success.replace('\r\n','')295 # eTranzact sends strange HTML tags which must be removed.296 success = re.sub("<.*?>", "", success)297 if 'CUSTOMER_ID' not in success:298 msg = _('Invalid or unsuccessful callback: ${a}',299 mapping = {'a': success})300 log = 'invalid callback for payment %s: %s' % (payment.p_id, success)301 payment.p_state = 'failed'302 return False, msg, log303 success = success.replace('%20',' ').split('&')304 # We expect at least two parameters305 if len(success) < 2:306 msg = _('Invalid callback: ${a}', mapping = {'a': success})307 log = 'invalid callback for payment %s: %s' % (payment.p_id, success)308 payment.p_state = 'failed'309 return False, msg, log310 try:311 success_dict = dict([tuple(i.split('=')) for i in success])312 except ValueError:313 msg = _('Invalid callback: ${a}', mapping = {'a': success})314 log = 'invalid callback for payment %s: %s' % (payment.p_id, success)315 payment.p_state = 'failed'316 return False, msg, log317 except IOError:318 msg = _('eTranzact IOError')319 log = 'eTranzact IOError'320 return False, msg, log321 payment.r_code = u'ET'322 payment.r_company = u'etranzact'323 payment.r_desc = u'%s' % success_dict.get('TRANS_DESCR')324 payment.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))325 payment.r_card_num = None326 payment.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')327 if payment.r_amount_approved != payment.amount_auth:328 msg = _('Wrong amount')329 log = 'wrong callback for payment %s: %s' % (payment.p_id, success)330 payment.p_state = 'failed'331 return False, msg, log332 customer_id = success_dict.get('CUSTOMER_ID')333 if payment.p_id != customer_id:334 msg = _('Wrong payment id')335 log = 'wrong callback for payment %s: %s' % (payment.p_id, success)336 payment.p_state = 'failed'337 return False, msg, log338 log = 'valid callback for payment %s: %s' % (payment.p_id, success)339 msg = _('Successful callback received')340 payment.p_state = 'paid'341 payment.payment_date = datetime.utcnow()342 return True, msg, log343 344 class EtranzactEnterPinActionButtonApplicant(APABApplicant):345 grok.context(ICustomApplicantOnlinePayment)346 grok.require('waeup.payApplicant')347 grok.order(3)348 icon = 'actionicon_call.png'349 text = _('Query eTranzact History')350 target = 'enterpin'351 352 class EtranzactEnterPinActionButtonStudent(APABStudent):353 grok.context(ICustomStudentOnlinePayment)354 grok.require('waeup.payStudent')355 grok.order(3)356 icon = 'actionicon_call.png'357 text = _('Query eTranzact History')358 target = 'enterpin'359 360 class EtranzactEnterPinPageStudent(KofaPage):361 """362 """363 grok.context(ICustomStudentOnlinePayment)364 grok.name('enterpin')365 grok.template('enterpin')366 grok.require('waeup.payStudent')367 368 buttonname = _('Submit to eTranzact')369 label = _('Requery eTranzact History')370 action = 'query_history'371 placeholder = _('Confirmation Number (PIN)')372 373 def update(self):374 super(EtranzactEnterPinPageStudent, self).update()375 if not self.context.p_category.startswith('schoolfee'):376 return377 student = self.context.student378 if student.state != CLEARED:379 return380 if student.entry_session < 2013:381 return382 for ticket in student['payments'].values():383 if ticket.p_state == 'paid' and \384 ticket.p_category.startswith('clearance'):385 return386 self.flash(_('Please pay acceptance fee first.'), type="danger")387 self.redirect(self.url(self.context, '@@index'))388 return389 390 class EtranzactEnterPinPageApplicant(EtranzactEnterPinPageStudent):391 """392 """393 grok.require('waeup.payApplicant')394 grok.context(ICustomApplicantOnlinePayment)395 396 class EtranzactQueryHistoryPageStudent(UtilityView, grok.View):397 """ Query history of eTranzact payments398 """399 grok.context(ICustomStudentOnlinePayment)400 grok.name('query_history')401 grok.require('waeup.payStudent')402 403 def update(self, confirmation_number=None):404 if self.context.p_state == 'paid':405 self.flash(_('This ticket has already been paid.'))406 return407 student = self.context.student408 success, msg, log = query_etranzact(confirmation_number,self.context)409 student.writeLogMessage(self, log)410 if not success:411 self.flash(msg)412 return413 flashtype, msg, log = self.context.doAfterStudentPayment()414 if log is not None:415 student.writeLogMessage(self, log)416 self.flash(msg, type=flashtype)417 return418 419 def render(self):420 self.redirect(self.url(self.context, '@@index'))421 return422 423 class EtranzactQueryHistoryPageApplicant(UtilityView, grok.View):424 """ Query history of eTranzact payments425 """426 grok.context(ICustomApplicantOnlinePayment)427 grok.name('query_history')428 grok.require('waeup.payApplicant')429 430 def update(self, confirmation_number=None):431 ob_class = self.__implemented__.__name__432 if self.context.p_state == 'paid':433 self.flash(_('This ticket has already been paid.'))434 return435 applicant = self.context.__parent__436 success, msg, log = query_etranzact(confirmation_number,self.context)437 applicant.writeLogMessage(self, log)438 if not success:439 self.flash(msg)440 return441 flashtype, msg, log = self.context.doAfterApplicantPayment()442 if log is not None:443 applicant.writeLogMessage(self, log)444 self.flash(msg, type=flashtype)445 return446 447 def render(self):448 self.redirect(self.url(self.context, '@@index'))449 return450 451 # Disable Interswitch viewlets. This could be avoided by defining the452 # action button viewlets of kofacustom.nigeria.interswitch.browser in the453 # context of INigeriaStudentOnlinePayment or INigeriaApplicantOnlinePayment454 # respectively. But then all interswitch.browser modules have to be extended.455 456 #class InterswitchActionButtonStudent(InterswitchActionButtonStudent):457 458 # @property459 # def target_url(self):460 # return ''461 462 #class InterswitchRequestWebserviceActionButtonStudent(463 # InterswitchRequestWebserviceActionButtonStudent):464 465 # @property466 # def target_url(self):467 # return ''468 469 #class InterswitchActionButtonApplicant(InterswitchActionButtonApplicant):470 471 # @property472 # def target_url(self):473 # return ''474 475 #class InterswitchRequestWebserviceActionButtonApplicant(476 # InterswitchRequestWebserviceActionButtonApplicant):477 478 # @property479 # def target_url(self):480 # return ''
Note: See TracChangeset for help on using the changeset viewer.