Log in |
Posting Secure Data To A Payment GatewayIt's a pretty standard problem, really. You want to submit sensitive data over an encrypted channel. Unfortunately, doing this with Zope is way harder than it needs to be.Chief among the challenges here is the fact that Python (as of 2.1.3) has no built-in support for SSL-secured sockets... or maybe, as some claim, it does but if a reasonably smart person can't make it work, what's the difference? So there's M2Crypto, an nifty but astonishingly under-documented product. Most scripts I found don't work. For posterity's sake, here's how I got it all working when I needed to get this done. First off, there are basically two ways of going about this: the right way, and the way that actually works. The right way appears to involve building M2Crypto against the Python binary that Zope is running under. Maybe I'm just not smart enough, but I just couldn't do it. I'll spare you a detailed account of my lost weekend of frustration and go straight to the method I actaully got to work. It was easy enough (as such things go) to compile M2Crypto against the Python 2.2 install I had on my server anyway. You'll need SWIG installed to make it work... but this is reasonably well documented. Rather than fuss with 2.1.3 any more, I just wrote a script to execute in 2.2 that I would call from 2.1.3. Yeah, that's a hack. But it works. :-) So here we go... This script shows how to post to *my* payment gateway, Authorize.net. Obviously, you'll want to use your own values here. HTTPSConnection seemed to work better when given an IP than an FQDN, though I didn't really test that hard enough to know why.
post_cc.py import os
import sys
from M2Crypto import httpslib, SSL
SERVER_FQDN = 'secure.authorize.net'
SERVER_IP = '206.253.210.201'
SECURE_PAGE = '/gateway/transact.dll'
form_data = sys.argv[1]
ctx = SSL.Context()
h = httpslib.HTTPSConnection(SERVER_IP, 443, ssl_context=ctx)
h.putrequest('POST', SECURE_PAGE + ' HTTP/1.1')
h.putheader('Content-type', 'application/x-www-form-urlencoded')
h.putheader('Content-length', '%d' % len(form_data))
h.putheader('Host', 'https://' + SERVER_FQDN)
h.endheaders()
h.send(form_data)
resp = h.getresponse()
content = resp.read()
print >> sys.stdout, content
I put that script in the Zope install folder, which in my case is located in /usr/share/zope/2.6.0/. The first argument we pass to this script will consist of a string representing the url-encoded data to be submitted with this form. This data will include all data required to authenticate and process the transaction. Since the requirements for these fields is obviously system-specific, I'll just put in a couple dummy values.
my_product_code.py import urllib
...
...
class my_thing(base_classes):
...
...
def post_payment(self, order_id):
# assuming order_id gives you the ability to look stuff up...
my_data = self.some_data_source['order_id']
merchant_id = 'abc1234567890'
trans_key = 'asdfasdf987654321'
my_order_data = {
'x_Tran_Key': trans_key,
'x_Version': '3.1',
'x_Delim_Data': 'TRUE',
'x_Type': 'AUTH_CAPTURE',
'x_Login': merchant_id,
'x_Merchant_Email': '[email protected]',
'x_Amount': my_data['g_total'],
'x_Card_Num': my_data['cc_num'],
...
...
}
data = urllib.urlencode(my_order_data)
py_bin = '/usr/bin/python2.2'
py_script = '/usr/local/zope/2.6.0/post_cc.py'
r, w, e = popen3('%s %s "%s"' % (py_bin, py_script, data))
errors = e.readlines()
result = r.readlines()
r.close()
e.close()
w.close()
# at this point, result has whatever text the remote system returned
# if that's all you want, return result as skip the rest
# if you're posting to a payment gateway, there's a bit more to do...
# the following is specific to Authorize.net return scheme
result_codes = result[0].split(',')
status = int(result_codes[0])
my_trans = {}
my_trans['reason'] = result_codes[3]
my_trans['auth_code'] = result_codes[4]
my_trans['avs_result'] = result_codes[5]
my_trans['trans_id'] = result_codes[6]
if status == 1:
# approved!
my_trans['result'] = 'Approved'
else:
my_trans['result'] = 'Not Approved'
return my_trans
I hope this helps. Feel free to drop me a line if you have any comments or have actually figured out how to get this working the way it's supposed to. :-)
Links: |