source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/etranzact/helpers.py @ 16827

Last change on this file since 16827 was 16167, checked in by Henrik Bettermann, 4 years ago

Adjust to new Interswitch API.

  • Property svn:keywords set to Id
File size: 11.6 KB
RevLine 
[15586]1## $Id: helpers.py 16167 2020-07-15 09:00:00Z henrik $
[15585]2##
3## Copyright (C) 2017 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##
18"""General helper functions for the etranzact module in custom packages.
19"""
20import grok
21import re
22from datetime import datetime
23from urllib import urlencode
[15730]24from urllib2 import urlopen
25from urlparse import parse_qs
[15585]26import httplib
27import hashlib
28import json
29from zope.event import notify
[15596]30from waeup.kofa.utils.helpers import extract_formvars
[15585]31from kofacustom.nigeria.interfaces import MessageFactory as _
32
[15734]33ERROR_PART1 = (
34        'PayeeName=N/A~'
35        + 'Faculty=N/A~'
36        + 'Department=N/A~'
37        + 'Level=N/A~'
38        + 'ProgrammeType=N/A~'
39        + 'StudyType=N/A~'
40        + 'Session=N/A~'
41        + 'PayeeID=N/A~'
42        + 'Amount=N/A~'
43        + 'FeeStatus=')
44ERROR_PART2 = (
45        '~Semester=N/A~'
46        + 'PaymentType=N/A~'
47        + 'MatricNumber=N/A~'
48        + 'Email=N/A~'
49        + 'PhoneNumber=N/A')
50
[15585]51def write_payments_log(id, payment):
52    payment.logger.info(
53        '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
54        id, payment.p_id, payment.p_category,
55        payment.amount_auth, payment.r_code,
56        payment.provider_amt, payment.gateway_amt,
57        payment.thirdparty_amt))
58
[15596]59def query_history(host, terminal_id, transaction_id, https):
[15585]60    headers={"Content-type": "application/x-www-form-urlencoded",
61             "Accept": "text/plain"}
[15589]62    url = "/webconnect/v3/query.jsp"
[15585]63    if https:
64        h = httplib.HTTPSConnection(host)
65    else:
66        h = httplib.HTTPConnection(host)
67    args = {'TERMINAL_ID': terminal_id,
68            'TRANSACTION_ID': transaction_id,
69            }
[15589]70    #args['RESPONSE_URL'] = responseurl
[15585]71    h.request('POST', url, urlencode(args), headers)
72    response = h.getresponse()
73    if response.status!=200:
[15596]74        return 'Connection error (%s, %s)' % (response.status, response.reason), None
75    raw = response.read()
76    return raw, extract_formvars(raw)
[15589]77
78 # A sample caller response sent to the RESPONSE_URL
79
80 # http://salsa:8080/app/applicants/cbt2015/449072/p5679522929425/receive_etranzact?
81 # AMOUNT=3333.0&
82 # DESCRIPTION=&
83 # CHECKSUM=8aab3904652f8ba69ebed42d3bae80a2&
84 # EMAIL=aa%40aa.de&
85 # SUCCESS=C&
86 # MESSAGE=Cancel&
87 # LOGO_URL=https%3A%2F%2Fiuokada.waeup.org%2Fstatic_custom%2Fiou_logo.png&
88 # RESPONSE_URL=http%3A%2F%2Fsalsa%3A8080%2Fapp%2Fapplicants%2Fcbt2015%2F449072%2Fp5679522929425%2Freceive_etranzact&
89 # CURRENCY_CODE=NGN&
90 # TERMINAL_ID=0000000001&
91 # TRANSACTION_ID=p5679522929425&
92 # MERCHANT_CODE=0339990001&
93 # RESPONSE_CODE=C&
94 # FINAL_CHECKSUM=E524590DBFAB719EEE428C778FFF1650&
95 # STATUS_REASON=Cancel&
96 # TRANS_NUM=01ESA20190913062149AHGUHQ&
97 # CARD_NO=null&
98 # CARD_TYPE=null
99
[15596]100 # A sample query response
[15589]101
[15596]102 # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
103 #     "http://www.w3.org/TR/html4/loose.dtd">
104 # <script language='javascript'>
105 # var spanId = document.getElementById("message_container");
106 # spanId.textContent = 'Redirecting...';</script>
107 # <form method="GET" id="redirect_form" name="redirect_form"
108 # action="http://localhost:81/projects/Webconnect/response.php" target="_top">
109 # <input type="hidden" name = "LOGO_URL"
110 # value="http://localhost:81/projects/Webconnect/images/elogo.fw.png">
111 # <input type="hidden" name = "RESPONSE_URL"
112 # value="http://localhost:81/projects/Webconnect/response.php">
113 # <input type="hidden" name = "CURRENCY_CODE" value="NGN">
114 # <input type="hidden" name = "TERMINAL_ID" value="0000000001">
115 # <input type="hidden" name = "TRANSACTION_ID" value="etz1568638104web">
116 # <input type="hidden" name = "AMOUNT" value="100">
117 # <input type="hidden" name = "DESCRIPTION" value="Payment Description">
118 # <input type="hidden" name = "CHECKSUM"
119 # value="5be04064f4bb250f73650059a8e921cc">
120 # <input type="hidden" name = "MERCHANT_CODE" value="0339990001">
121 # <input type="hidden" name = "EMAIL" value="xyz@yahoo.com">
122 # <input type="hidden" name = "SUCCESS" value="0">
123 # <input type="hidden" name = "FINAL_CHECKSUM"
124 # value="FD67A4CCC39E2C8DEEEC78D6C64A61FE">
125 # <input type="hidden" name = "STATUS_REASON" value="Approved">
126 # <input type="hidden" name = "TRANS_NUM" value="01ESA20190916134824YA3YJ8">
127 # <input type="hidden" name = "CARD_NO" value="506066XXXXXXXXX6666">
128 # <input type="hidden" name = "CARD_TYPE" value="Verve">
129 # </form>
130 # <script language='javascript'>
131 # var fom = document.forms["redirect_form"];
132 # fom.submit();</script>
133
134 # A sample query response sent to the RESPONSE_URL after the browser has
135 # automatically executed the Javascript above (we don't use this response)
136
[15589]137 # http://salsa:8080/app/applicants/cbt2015/449072/p5686487280654/receive_etranzact?
138 # LOGO_URL=https%3A%2F%2Fiuokada.waeup.org%2Fstatic_custom%2Fiou_logo.png&
139 # RESPONSE_URL=http%3A%2F%2Fsalsa%3A8080%2Fapp%2Fapplicants%2Fcbt2015%2F449072%2Fp5686487280654%2Freceive_etranzact&
140 # CURRENCY_CODE=NGN&
141 # TERMINAL_ID=0000000001&
142 # TRANSACTION_ID=p5686487280654&
143 # AMOUNT=3333.0&
144 # DESCRIPTION=&
145 # CHECKSUM=3886118fcd91a376cc95c48c94dc499a&
146 # MERCHANT_CODE=0339990001&
147 # EMAIL=aa%40aa.de&
148 # SUCCESS=0&
149 # FINAL_CHECKSUM=EE105B703F84B1D67D0A4234622C03E8&
150 # STATUS_REASON=Approved&
151 # TRANS_NUM=01ESA20190916164636L2UTU7&
152 # CARD_NO=506066XXXXXXXXX6666&
153 # CARD_TYPE=Verve
154
155def process_response(payment, form, view, verify):
[15598]156    if not form or not form.get('SUCCESS', None):
[16083]157        msg = _('No (valid) response from Etranzact.')
[16167]158        log = 'invalid response for payment %s' % payment.p_id
[15589]159        payment.p_state = 'failed'
160        notify(grok.ObjectModifiedEvent(payment))
161        return False, msg, log
[15598]162    success = form.get('SUCCESS', None)
[15589]163    # Compute final checksum
164    transaction_id = payment.p_id
165    amount = "%.1f" % payment.amount_auth
166    responseurl = view.url(payment, 'receive_etranzact')
167    hashargs =  success + amount + view.terminal_id + transaction_id \
168        + responseurl + view.secret_key
169    final_checksum = hashlib.md5(hashargs).hexdigest().upper()
170    if form.get('FINAL_CHECKSUM', None) != final_checksum:
171        msg = _('Wrong checksum.')
172        log = 'wrong checksum for %s payment %s: %s' % (
173            payment.p_category, payment.p_id, str(form))
[15596]174        payment.p_state = 'failed'
175        notify(grok.ObjectModifiedEvent(payment))
[15589]176        return False, msg, log
[15598]177    payment.r_code = form.get('SUCCESS', None)
[15589]178    payment.r_desc = form.get('STATUS_REASON', None) # MESSAGE also available
179    payment.r_amount_approved = float(form.get('AMOUNT', None))
180    payment.r_pay_reference = form.get('TRANS_NUM', None)
181    payment.r_card_num = "%s %s" % (form.get('CARD_TYPE', None),
182                                    form.get('CARD_NO', None))
183    if payment.r_code != '0':
184        msg = _('Unsuccessful response: ${a}', mapping = {'a': payment.r_desc})
185        log = 'unsuccessful response for %s payment %s: %s' % (
186            payment.p_category, payment.p_id, payment.r_desc)
187        payment.p_state = 'failed'
188        notify(grok.ObjectModifiedEvent(payment))
189        return False, msg, log
190    if round(payment.r_amount_approved/10.0, 0) != round(
191        payment.amount_auth/10.0, 0):
192        msg = _('Response amount does not match.')
193        log = 'wrong response for %s payment %s: %s' % (
194            payment.p_category, payment.p_id, str(form))
195        payment.p_state = 'failed'
196        notify(grok.ObjectModifiedEvent(payment))
197        return False, msg, log
198    transaction_id = form.get('TRANSACTION_ID', None)
199    if transaction_id != payment.p_id:
200        msg = _('Response transaction id does not match.')
201        log = 'wrong response for %s payment %s: %s' % (
202            payment.p_category, payment.p_id, str(form))
203        payment.p_state = 'failed'
204        notify(grok.ObjectModifiedEvent(payment))
205        return False, msg, log
206    payment.p_state = 'paid'
207    if not verify:
208        payment.payment_date = datetime.utcnow()
209    msg = _('Successful response received')
210    log = 'valid response for %s payment %s: %s' % (
211        payment.p_category, payment.p_id, str(form))
212    notify(grok.ObjectModifiedEvent(payment))
[15730]213    return True, msg, log
214
215# Requerying Etranzact Payoutlet payments
216
217# Expected response:
218# RECEIPT_NO=500191030486&PAYMENT_CODE=500854291572447457669&MERCHANT_CODE=700602WDUB
219# &TRANS_AMOUNT=200000.0&TRANS_DATE=2019/10/30
220# 15:13:47&TRANS_DESCR=Test%20Test%20Test-CLEARANCE%20-001-p5723474039401
221# &CUSTOMER_ID=p5723474039401&BANK_CODE=500&BRANCH_CODE=001
222# &SERVICE_ID=p5723474039401&CUSTOMER_NAME=Test%20Test%20Test
223# &CUSTOMER_ADDRESS=ASS-ENG&TELLER_ID=etzbankteller&USERNAME=%20
224# &PASSWORD=%20&BANK_NAME=eTranzact%20Intl%20Plc
225# &BRANCH_NAME=ETRANZACT&CHANNEL_NAME=Bank&PAYMENT_METHOD_NAME=Cash
226# &PAYMENT_CURRENCY=566&TRANS_TYPE=101&TRANS_FEE=0.0
227# &TYPE_NAME=CLEARANCE&LEAD_BANK_CODE=700&LEAD_BANK_NAME=eTranzact%20Intl%20Plc
228# COL1=2018/2019&COL2=Acceptance
229# Fee&COL3=ASS&COL4=ENG&COL5=BARTENL&COL6=400&COL7=ug_ft&COL8=N/A&COL9=11/12345
230# &COL10=damms005@gmail.com&COL11=None&COL12=&COL13=
231
232def query_payoutlet(host, terminal_id, confirmation_number, payment, https):
233    headers={"Content-type": "application/x-www-form-urlencoded",
234             "Accept": "text/plain"}
235    url = "/WebConnectPlus/query.jsp"
236    if https:
237        h = httplib.HTTPSConnection(host)
238    else:
239        h = httplib.HTTPConnection(host)
240    args = {'TERMINAL_ID': terminal_id,
241            'CONFIRMATION_NO': confirmation_number,
242            }
243    h.request('POST', url, urlencode(args), headers)
244    response = h.getresponse()
245    if response.status!=200:
246        return False, 'Connection error (%s, %s)' % (response.status, response.reason), None
247    raw = response.read()
248    # Remove empty lines
249    raw = raw.replace('\r\n','')
250    success = parse_qs(raw)
251    if not success.get('CUSTOMER_ID'):
252        msg = _('Invalid or unsuccessful callback: ${a}',
253            mapping = {'a': raw})
254        log = 'invalid callback for payment %s: %s' % (payment.p_id, raw)
255        payment.p_state = 'failed'
256        return False, msg, log
257    # We expect at least two parameters
258    if len(success) < 2:
259        msg = _('Invalid callback: ${a}', mapping = {'a': raw})
260        log = 'invalid callback for payment %s: %s' % (payment.p_id, raw)
261        payment.p_state = 'failed'
262        return False, msg, log
[15788]263    customer_id = success.get('CUSTOMER_ID')[0]
264    if payment.p_id != customer_id:
265        msg = _('Wrong payment id')
266        log = 'wrong callback for payment %s: %s' % (payment.p_id, raw)
267        payment.p_state = 'failed'
268        return False, msg, log
[15730]269    payment.r_code = u'ET'
270    payment.r_desc = u'%s' % success.get('TRANS_DESCR')[0]
271    payment.r_amount_approved = float(success.get('TRANS_AMOUNT')[0])
272    payment.r_card_num = None
273    payment.r_pay_reference = u'%s' % success.get('RECEIPT_NO')[0]
274    if payment.r_amount_approved != payment.amount_auth:
275        msg = _('Wrong amount')
276        log = 'wrong callback for payment %s: %s' % (payment.p_id, raw)
277        payment.p_state = 'failed'
278        return False, msg, log
279    log = 'valid callback for payment %s: %s' % (payment.p_id, raw)
280    msg = _('Successful callback received')
281    payment.p_state = 'paid'
282    payment.payment_date = datetime.utcnow()
283    return True, msg, log
Note: See TracBrowser for help on using the repository browser.