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

Last change on this file since 17639 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
Line 
1## $Id: helpers.py 16167 2020-07-15 09:00:00Z henrik $
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
24from urllib2 import urlopen
25from urlparse import parse_qs
26import httplib
27import hashlib
28import json
29from zope.event import notify
30from waeup.kofa.utils.helpers import extract_formvars
31from kofacustom.nigeria.interfaces import MessageFactory as _
32
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
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
59def query_history(host, terminal_id, transaction_id, https):
60    headers={"Content-type": "application/x-www-form-urlencoded",
61             "Accept": "text/plain"}
62    url = "/webconnect/v3/query.jsp"
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            }
70    #args['RESPONSE_URL'] = responseurl
71    h.request('POST', url, urlencode(args), headers)
72    response = h.getresponse()
73    if response.status!=200:
74        return 'Connection error (%s, %s)' % (response.status, response.reason), None
75    raw = response.read()
76    return raw, extract_formvars(raw)
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
100 # A sample query response
101
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
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):
156    if not form or not form.get('SUCCESS', None):
157        msg = _('No (valid) response from Etranzact.')
158        log = 'invalid response for payment %s' % payment.p_id
159        payment.p_state = 'failed'
160        notify(grok.ObjectModifiedEvent(payment))
161        return False, msg, log
162    success = form.get('SUCCESS', None)
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))
174        payment.p_state = 'failed'
175        notify(grok.ObjectModifiedEvent(payment))
176        return False, msg, log
177    payment.r_code = form.get('SUCCESS', None)
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))
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
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
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.