source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/helpers.py @ 16484

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

Implement PAYDirect Bank Branch payment.

  • Property svn:keywords set to Id
File size: 12.7 KB
Line 
1## $Id: helpers.py 16484 2021-05-19 07:47:21Z henrik $
2##
3## Copyright (C) 2012 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 interswitch module in custom packages.
19"""
20from datetime import datetime
21import httplib
22import hashlib
23import json
24from urllib import urlencode
25import grok
26from xml.dom.minidom import parseString
27from zope.event import notify
28from kofacustom.nigeria.interfaces import MessageFactory as _
29
30def SOAP_post(soap_action, xml, host, url, https):
31    """Handles making the SOAP request.
32    """
33    if https:
34        h = httplib.HTTPSConnection(host)
35    else:
36        h = httplib.HTTPConnection(host)
37    headers={
38        'Host':host,
39        'Content-Type':'text/xml; charset=utf-8',
40        'Content-Length':len(xml),
41        'SOAPAction':'"%s"' % soap_action,
42    }
43    h.request('POST', url, body=xml,headers=headers)
44    response = h.getresponse()
45    return response
46
47def write_payments_log(id, payment):
48    payment.logger.info(
49        '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
50        id, payment.p_id, payment.p_category,
51        payment.amount_auth, payment.r_code,
52        payment.provider_amt, payment.gateway_amt,
53        payment.thirdparty_amt))
54
55# CollegePAY helper functions
56
57def get_SOAP_response(product_id, transref, host, url, https):
58    xml="""\
59<?xml version="1.0" encoding="utf-8"?>
60<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
61  <soap:Body>
62    <getTransactionData xmlns="http://tempuri.org/">
63      <product_id>%s</product_id>
64      <trans_ref>%s</trans_ref>
65    </getTransactionData>
66  </soap:Body>
67</soap:Envelope>""" % (product_id, transref)
68    response=SOAP_post("http://tempuri.org/getTransactionData",xml, host, url, https)
69    if response.status!=200:
70        return 'Connection error (%s, %s)' % (response.status, response.reason)
71    result_xml = response.read()
72    doc=parseString(result_xml)
73    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
74    return response
75
76
77def get_JSON_response(product_id, transref, host, url, https, mac, amount):
78    hashargs = product_id + transref + mac
79    hashvalue = hashlib.sha512(hashargs).hexdigest()
80    headers={
81        'Content-Type':'text/xml; charset=utf-8',
82        'Hash':hashvalue,
83    }
84    if https:
85        h = httplib.HTTPSConnection(host)
86    else:
87        h = httplib.HTTPConnection(host)
88    amount = int(100 * amount)
89    args = {'productid': product_id,
90            'transactionreference': transref,
91            'amount': amount}
92    url = '%s?' % url + urlencode(args)
93    h.request("GET", url, headers=headers)
94    response = h.getresponse()
95    if response.status!=200:
96        return {'error': 'Connection error (%s, %s)' % (response.status, response.reason)}
97    jsonout = response.read()
98    parsed_json = json.loads(jsonout)
99    return parsed_json
100
101def query_interswitch_SOAP(payment, product_id, host, url, https, verify):
102    sr = get_SOAP_response(product_id, payment.p_id, host, url, https)
103    if sr.startswith('Connection error'):
104        msg = _('Connection error')
105        log = sr
106        return False, msg, log
107    wlist = sr.split(':')
108    if len(wlist) < 7:
109        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
110        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
111        return False, msg, log
112    payment.r_code = wlist[0]
113    payment.r_desc = wlist[1]
114    payment.r_amount_approved = float(wlist[2]) / 100
115    payment.r_card_num = wlist[3]
116    payment.r_pay_reference = wlist[5]
117    payment.r_company = u'interswitch'
118    if payment.r_code != '00':
119        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
120        log = 'unsuccessful callback for %s payment %s: %s' % (
121            payment.p_category, payment.p_id, sr)
122        payment.p_state = 'failed'
123        notify(grok.ObjectModifiedEvent(payment))
124        return False, msg, log
125    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
126        msg = _('Callback amount does not match.')
127        log = 'wrong callback for %s payment %s: %s' % (
128            payment.p_category, payment.p_id, sr)
129        payment.p_state = 'failed'
130        notify(grok.ObjectModifiedEvent(payment))
131        return False, msg, log
132    if wlist[4] != payment.p_id:
133        msg = _('Callback transaction id does not match.')
134        log = 'wrong callback for %s payment %s: %s' % (
135            payment.p_category, payment.p_id, sr)
136        payment.p_state = 'failed'
137        notify(grok.ObjectModifiedEvent(payment))
138        return False, msg, log
139    payment.p_state = 'paid'
140    if not verify:
141        payment.payment_date = datetime.utcnow()
142    msg = _('Successful callback received.')
143    log = 'valid callback for %s payment %s: %s' % (
144        payment.p_category, payment.p_id, sr)
145    notify(grok.ObjectModifiedEvent(payment))
146    return True, msg, log
147
148def query_interswitch(payment, product_id, host, url, https, mac, verify):
149    # If no mac mac key is given, fall back to deprecated SOAP method
150    # (Uniben, AAUA, FCEOkene).
151    if mac == None:
152        return query_interswitch_SOAP(
153            payment, product_id, host, url, https, verify)
154    jr = get_JSON_response(product_id, payment.p_id, host, url,
155                           https, mac, payment.amount_auth)
156    error = jr.get('error')
157    if error:
158        msg = log = error
159        return False, msg, log
160
161    # A typical JSON response
162
163    # old:
164
165    #  {u'SplitAccounts': [],
166    #   u'MerchantReference':u'p4210665523377',
167    #   u'PaymentReference':u'GTB|WEB|KPOLY|12-01-2015|013138',
168    #   u'TransactionDate':u'2015-01-12T13:43:39.27',
169    #   u'RetrievalReferenceNumber':u'000170548791',
170    #   u'ResponseDescription': u'Approved Successful',
171    #   u'Amount': 2940000,
172    #   u'CardNumber': u'2507',
173    #   u'ResponseCode': u'00',
174    #   u'LeadBankCbnCode': None,
175    #   u'LeadBankName': None}
176
177    # new:
178
179    # 'PaymentReference' is maybe missing
180
181    #  {u'SplitAccounts': [],
182    #  u'MerchantReference':u'p5918633006916',
183    #  u'TransactionDate':u'2020-06-11T09:17:37',
184    #  u'ResponseDescription':u'Customer Cancellation',
185    #  u'Amount': 89525000,
186    #  u'CardNumber': u'',
187    #  u'ResponseCode': u'Z6',
188    #  u'BankCode': u''}
189
190    if not 'ResponseCode' in jr.keys() \
191        or not 'ResponseDescription' in jr.keys() \
192        or not 'Amount' in jr.keys():
193        msg = _('Invalid callback: ${a}', mapping = {'a': str(jr)})
194        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
195        return False, msg, log
196    if verify and jr['ResponseCode'] == '20050':
197        msg = _('Integration method has changed.')
198        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
199        return False, msg, log
200    payment.r_code = jr['ResponseCode']
201    payment.r_desc = jr['ResponseDescription']
202    payment.r_amount_approved = jr['Amount'] / 100.0
203    payment.r_card_num = jr.get('CardNumber', u'')
204    payment.r_pay_reference = jr.get('PaymentReference', u'')
205    #payment.r_company = u'interswitch'
206    if payment.r_code != '00':
207        msg = _('Unsuccessful callback: ${a}', mapping = {'a': payment.r_desc})
208        log = 'unsuccessful callback for %s payment %s: %s' % (
209            payment.p_category, payment.p_id, payment.r_desc)
210        payment.p_state = 'failed'
211        notify(grok.ObjectModifiedEvent(payment))
212        return False, msg, log
213    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
214        msg = _('Callback amount does not match.')
215        log = 'wrong callback for %s payment %s: %s' % (
216            payment.p_category, payment.p_id, str(jr))
217        payment.p_state = 'failed'
218        notify(grok.ObjectModifiedEvent(payment))
219        return False, msg, log
220    if jr['MerchantReference'] != payment.p_id:
221        msg = _('Callback transaction id does not match.')
222        log = 'wrong callback for %s payment %s: %s' % (
223            payment.p_category, payment.p_id, str(jr))
224        payment.p_state = 'failed'
225        notify(grok.ObjectModifiedEvent(payment))
226        return False, msg, log
227    payment.p_state = 'paid'
228    if not verify:
229        payment.payment_date = datetime.utcnow()
230    msg = _('Successful callback received')
231    log = 'valid callback for %s payment %s: %s' % (
232        payment.p_category, payment.p_id, str(jr))
233    notify(grok.ObjectModifiedEvent(payment))
234    return True, msg, log
235
236# PAYDirect helper functions
237
238def get_SOAP_response_paydirect(merchant_id, p_id, host, url, https):
239    xml="""\
240<?xml version="1.0" encoding="utf-8"?>
241<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:book="http://interswitchng.com/bookonhold">
242   <soap:Header/>
243   <soap:Body>
244      <book:FetchBookingDetails>
245         <book:ReservationDetailsRequest>
246            <book:MerchantId>%s</book:MerchantId>
247            <book:Bookings>
248               <book:Booking>
249                  <book:ReferenceNumber>%s%s</book:ReferenceNumber>
250               </book:Booking>
251            </book:Bookings>
252         </book:ReservationDetailsRequest>
253      </book:FetchBookingDetails>
254   </soap:Body>
255</soap:Envelope>""" % (merchant_id, merchant_id, p_id[1:])
256
257    response=SOAP_post(
258        "http://interswitchng.com/bookonhold/FetchBookingDetails",
259        xml, host, url, https)
260    if response.status!=200:
261        error = 'Connection error (%s, %s)' % (response.status, response.reason)
262        return error
263    result_xml = response.read()
264    return result_xml
265
266def fetch_booking_details(payment, merchant_id, host, url, https):
267    result_xml = get_SOAP_response_paydirect(
268        merchant_id, payment.p_id, host, url, https)
269    if result_xml.startswith('Connection error'):
270        return False, result_xml, result_xml
271    doc=parseString(result_xml)
272    if not doc.getElementsByTagName('PaymentStatus'):
273        msg = _('Your payment %s was not found.' % payment.p_id)
274        log = 'payment %s cannot be found' % payment.p_id
275        return False, msg, log
276    p_status = doc.getElementsByTagName('PaymentStatus')[0].firstChild.data
277    payment.r_code = p_status
278    try:
279        payment.r_desc = "%s - %s - %s" % (
280            doc.getElementsByTagName('ChannelName')[0].firstChild.data,
281            doc.getElementsByTagName('TerminalId')[0].firstChild.data,
282            doc.getElementsByTagName('Location'))[0].firstChild.data
283    except AttributeError:
284        pass
285    try:
286        amount = doc.getElementsByTagName('Amount')[0].firstChild.data
287        payment.r_amount_approved = int(amount)
288    except AttributeError:
289        pass
290    try:
291        payment.r_pay_reference = doc.getElementsByTagName(
292            'ReferenceNumber').firstChild.data
293    except AttributeError:
294        pass
295    if p_status not in ('Pending', 'Completed'):
296        msg = _('Unknown status: %s' % sr[0])
297        log = 'invalid callback for payment %s: %s' % (payment.p_id, status)
298        payment.p_state = 'failed'
299        notify(grok.ObjectModifiedEvent(payment))
300        return False, msg, log
301    if p_status == 'Completed' and not payment.r_amount_approved:
302        msg = _('Amount unconfirmed')
303        log = 'unsuccessful callback for payment %s: amount unconfirmed' % payment.p_id
304        payment.p_state = 'failed'
305        notify(grok.ObjectModifiedEvent(payment))
306        return False, msg, log
307    if p_status == 'Pending':
308        msg = _('Payment pending')
309        log = 'unsuccessful callback for payment %s: pending' % payment.p_id
310        payment.p_state = 'failed'
311        notify(grok.ObjectModifiedEvent(payment))
312        return False, msg, log
313    if payment.r_amount_approved != payment.amount_auth:
314        msg = _('Callback amount does not match.')
315        log = 'unsuccessful callback for %s payment %s: callback amount %s does not match' % (
316            payment.p_category, payment.p_id, amount)
317        payment.p_state = 'failed'
318        notify(grok.ObjectModifiedEvent(payment))
319        return False, msg, log
320    payment.p_state = 'paid'
321    payment.payment_date = datetime.utcnow()
322    msg = _('Successful callback received')
323    log = 'valid callback for %s payment %s: %s' % (
324        payment.p_category, payment.p_id, p_status)
325    notify(grok.ObjectModifiedEvent(payment))
326    return True, msg, log
Note: See TracBrowser for help on using the repository browser.