paymentech.py
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.3.0 (2012-12-04 09:01:56) rc1
|
||||
Version 2.3.0 (2012-12-04 10:55:53) rc1
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This module provides a simple API for Paymentech(c) payments
|
||||
# The original code was taken from this web2py issue post
|
||||
# http://code.google.com/p/web2py/issues/detail?id=1170
|
||||
#
|
||||
# Copyright (C) <2012> Alan Etkin <spametki@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys, httplib, urllib, urllib2
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
# TODO: input validation, test, debugging output
|
||||
|
||||
class PaymenTech(object):
|
||||
"""
|
||||
The base class for connecting to the Paymentech service
|
||||
|
||||
Format notes
|
||||
============
|
||||
|
||||
- Credit card expiration date (exp argument) must be of mmyyyy form
|
||||
- The amount is an all integers string with two decimal places:
|
||||
For example, $2.15 must be formatted as "215"
|
||||
|
||||
Point of sale and service options (to be passed on initialization)
|
||||
==================================================================
|
||||
|
||||
user
|
||||
password
|
||||
industry
|
||||
message
|
||||
bin_code
|
||||
merchant
|
||||
terminal
|
||||
|
||||
(WARNING!: this is False by default)
|
||||
development <bool>
|
||||
|
||||
(the following arguments have default values)
|
||||
target
|
||||
host
|
||||
api_url
|
||||
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
As this module consumes webservice methods, it should be tested
|
||||
with particular user data with the paymentech development environment
|
||||
|
||||
The simplest test would be running something like the following:
|
||||
|
||||
from paymentech import PaymenTech
|
||||
# Read the basic point of sale argument list required above
|
||||
# Remember to use development = True!
|
||||
pos_data = {'user': <username>, ...}
|
||||
|
||||
# The data arguments are documented in the .charge() method help
|
||||
charge_test = {'account': <account>, ...}
|
||||
mypayment = PaymentTech(**pos_data)
|
||||
result = mypayment.charge(**charge_test)
|
||||
|
||||
print "##################################"
|
||||
print "# Charge test result #"
|
||||
print "##################################"
|
||||
print result
|
||||
|
||||
#################################################################
|
||||
# Notes for web2py implementations #
|
||||
#################################################################
|
||||
|
||||
# A recommended model for handling payments
|
||||
|
||||
# Store this constants in a private model file (i.e. 0_private.py)
|
||||
|
||||
PAYMENTECH_USER = <str>
|
||||
PAYMENTECH_PASSWORD = <str>
|
||||
PAYMENTECH_INDUSTRY = <str>
|
||||
PAYMENTECH_MESSAGE = <str>
|
||||
PAYMENTECH_BIN_CODE= <str>
|
||||
PAYMENTECH_MERCHANT = <str>
|
||||
PAYMENTECH_terminal = <str>
|
||||
DEVELOPMENT = True
|
||||
PAYMENTECH_TARGET = <str>
|
||||
PAYMENTECH_HOST = <str>
|
||||
PAYMENTECH_API_URL = <str>
|
||||
|
||||
# The following table would allow passing data with web2py and to
|
||||
# update records with the webservice authorization output by using
|
||||
# the DAL
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# # Create a PaymenTech instance
|
||||
# mypaymentech = paymentech.PaymenTech(user=PAYMENTECH_USER, ...)
|
||||
#
|
||||
# # Fetch a payment inserted within the app
|
||||
# myrow = db.paymentech[<id>]
|
||||
#
|
||||
# # Send the authorization request to the webservice
|
||||
# result = mypaymentech.charge(myrow.as_dict())
|
||||
#
|
||||
# # Update the db record with the webservice response
|
||||
# myrow.update_record(**result)
|
||||
|
||||
db.define_table("paymentech",
|
||||
Field("account"),
|
||||
Field("exp", comment="Must be of the mmyyyy form"),
|
||||
Field("currency_code"),
|
||||
Field("currency_exponent"),
|
||||
Field("card_sec_val_ind"),
|
||||
Field("card_sec_val"),
|
||||
Field("avs_zip"),
|
||||
Field("avs_address_1"),
|
||||
Field("avs_address_2"),
|
||||
Field("avs_city"),
|
||||
Field("avs_state"),
|
||||
Field("avs_phone"),
|
||||
Field("avs_country"),
|
||||
Field("profile_from_order_ind"),
|
||||
Field("profile_order_override_ind"),
|
||||
Field("order_id"),
|
||||
Field("amount",
|
||||
comment="all integers with two decimal digits, \
|
||||
without dot separation"),
|
||||
Field("header"),
|
||||
Field("status_code"),
|
||||
Field("status_message"),
|
||||
Field("resp_code"),
|
||||
Field("tx_ref_num"),
|
||||
format="%(order_id)s")
|
||||
|
||||
TODO: add model form validators (for exp date and amount)
|
||||
"""
|
||||
|
||||
charge_xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Request>
|
||||
<NewOrder>
|
||||
<OrbitalConnectionUsername>%(user)s</OrbitalConnectionUsername>
|
||||
<OrbitalConnectionPassword>%(password)s</OrbitalConnectionPassword>
|
||||
<IndustryType>%(industry)s</IndustryType>
|
||||
<MessageType>%(message)s</MessageType>
|
||||
<BIN>%(bin)s</BIN>
|
||||
<MerchantID>%(merchant)s</MerchantID>
|
||||
<TerminalID>%(terminal)s</TerminalID>
|
||||
<AccountNum>%(account)s</AccountNum>
|
||||
<Exp>%(exp)s</Exp>
|
||||
<CurrencyCode>%(currency_code)s</CurrencyCode>
|
||||
<CurrencyExponent>%(currency_exponent)s</CurrencyExponent>
|
||||
<CardSecValInd>%(card_sec_val_ind)s</CardSecValInd>
|
||||
<CardSecVal>%(card_sec_val)s</CardSecVal>
|
||||
<AVSzip>%(avs_zip)s</AVSzip>
|
||||
<AVSaddress1>%(avs_address_1)s</AVSaddress1>
|
||||
<AVSaddress2>%(avs_address_2)s</AVSaddress2>
|
||||
<AVScity>%(avs_city)s</AVScity>
|
||||
<AVSstate>%(avs_state)s</AVSstate>
|
||||
<AVSphoneNum>%(avs_phone)s</AVSphoneNum>
|
||||
<AVScountryCode>%(avs_country)s</AVScountryCode>
|
||||
<CustomerProfileFromOrderInd>%(profile_from_order_ind)s</CustomerProfileFromOrderInd>
|
||||
<CustomerProfileOrderOverrideInd>%(profile_order_override_ind)s</CustomerProfileOrderOverrideInd>
|
||||
<OrderID>%(order_id)s</OrderID>
|
||||
<Amount>%(amount)s</Amount>
|
||||
</NewOrder>
|
||||
</Request>
|
||||
"""
|
||||
|
||||
def __init__(self, development=False, user=None, password=None,
|
||||
industry=None, message=None, api_url=None,
|
||||
bin_code=None, merchant=None, host=None,
|
||||
terminal=None, target=None):
|
||||
|
||||
# PaymenTech point of sales data
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.industry = industry
|
||||
self.message = message
|
||||
self.bin_code = bin_code
|
||||
self.merchant = merchant
|
||||
self.terminal = terminal
|
||||
|
||||
# Service options
|
||||
self.development = development
|
||||
self.target = target
|
||||
self.host = host
|
||||
self.api_url = api_url
|
||||
|
||||
# dev: https://orbitalvar1.paymentech.net/authorize:443
|
||||
# prod: https://orbital1.paymentech.net/authorize
|
||||
|
||||
if self.development is False:
|
||||
if not self.target:
|
||||
# production
|
||||
self.target = "https://orbital1.paymentech.net/authorize"
|
||||
|
||||
self.host, self.api_url = \
|
||||
urllib2.splithost(urllib2.splittype(self.target)[1])
|
||||
|
||||
else:
|
||||
if not self.target:
|
||||
# development
|
||||
self.target = "https://orbitalvar1.paymentech.net/authorize"
|
||||
if not self.host:
|
||||
self.host = "orbitalvar1.paymentech.net/authorize:443"
|
||||
if not self.api_url:
|
||||
self.api_url = "/"
|
||||
|
||||
def charge(self, raw=None, **kwargs):
|
||||
"""
|
||||
Post an XML request to Paymentech
|
||||
This is an example of a call with raw xml data:
|
||||
|
||||
from paymentech import PaymenTech
|
||||
|
||||
# Note: user/password/etc data is not mandatory as it
|
||||
# is retrieved from instance attributes (set on init)
|
||||
|
||||
pt = PaymenTech(user="<myuser>",
|
||||
password="<mypassword>",
|
||||
...) # see basic user in the class help
|
||||
result = pt.charge(raw=xml_string)
|
||||
|
||||
A better way to make a charge request is to unpack a dict object
|
||||
with the operation data:
|
||||
|
||||
...
|
||||
# The complete input values are listed below in
|
||||
# "Transacion data..."
|
||||
|
||||
charge_data = dict(account=<str>, exp=<str mmyyyy>, ...)
|
||||
result = pt.charge(**charge_data)
|
||||
|
||||
|
||||
Variable xml_string contains all details about the order,
|
||||
plus we are sending username/password in there too...
|
||||
|
||||
|
||||
Transaction data (to be passed to the charge() method)
|
||||
======================================================
|
||||
|
||||
(Note that it is possible to override the class user,
|
||||
pass, etc. passing those arguments to the .charge() method,
|
||||
which are documented in the class help)
|
||||
|
||||
account
|
||||
exp <str mmyyyy>
|
||||
currency_code
|
||||
currency_exponent
|
||||
card_sec_val_ind
|
||||
card_sec_val
|
||||
avs_zip
|
||||
avs_address_1
|
||||
avs_address_2
|
||||
avs_city
|
||||
avs_state
|
||||
avs_phone
|
||||
avs_country
|
||||
profile_from_order_ind
|
||||
profile_order_override_ind
|
||||
order_id
|
||||
amount <str> (all integers with two decimal digits, without dot
|
||||
separation)
|
||||
|
||||
Request header example
|
||||
======================
|
||||
|
||||
Request: sent as POST to https://orbitalvar1.paymentech.net/authorize:443
|
||||
from 127.0.0.1
|
||||
request headers:
|
||||
Content-Type: application/PTI45
|
||||
Content-Type: application/PTI46
|
||||
Content-transfer-encoding: text
|
||||
Request-number: 1
|
||||
Document-type: Request
|
||||
Trace-number: 1234556446
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
"""
|
||||
|
||||
# default charge data
|
||||
data = dict(user=self.user, password=self.password,
|
||||
industry=self.industry, message=self.message,
|
||||
bin_code=self.bin_code, merchant=self.merchant,
|
||||
terminal=self.terminal, account="", exp="",
|
||||
currency_code="", currency_exponent="",
|
||||
card_sec_val_ind="", card_sec_val="", avs_zip="",
|
||||
avs_address_1="", avs_address_2="", avs_city="",
|
||||
avs_state="", avs_phone="", avs_country="",
|
||||
profile_from_order_ind="",
|
||||
profile_order_override_ind="", order_id="",
|
||||
amount="")
|
||||
|
||||
result = dict()
|
||||
|
||||
# Complete the charge request with the method kwargs
|
||||
for k, v in kwargs.iteritems():
|
||||
data[k] = v
|
||||
|
||||
status_code = status_message = header = resp_code = \
|
||||
tx_ref_num = order_id = None
|
||||
conn = httplib.HTTPS(self.host)
|
||||
conn.putrequest('POST', self.api_url)
|
||||
|
||||
if self.development:
|
||||
content_type = "PTI56"
|
||||
else:
|
||||
content_type = "PTI46"
|
||||
|
||||
if raw is None:
|
||||
xml_string = self.charge_xml % data
|
||||
else:
|
||||
xml_string = raw
|
||||
|
||||
conn.putheader("Content-Type",
|
||||
"application/%s") % content_type
|
||||
conn.putheader("Content-transfer-encoding", "text")
|
||||
conn.putheader("Request-number", "1")
|
||||
conn.putheader("Content-length", str(len(xml_string)))
|
||||
conn.putheader("Document-type", "Request")
|
||||
conn.putheader("Trace-number", str(data["order_id"]))
|
||||
conn.putheader("MIME-Version", "1.0")
|
||||
conn.endheaders()
|
||||
conn.send(xml_string)
|
||||
|
||||
result["status_code"], result["status_message"], \
|
||||
result["header"] = conn.getreply()
|
||||
|
||||
fp = conn.getfile()
|
||||
output = fp.read()
|
||||
fp.close()
|
||||
|
||||
dom = parseString(output)
|
||||
result["resp_code"] = \
|
||||
dom.getElementsByTagName('RespCode')[0].firstChild.data
|
||||
result["tx_ref_num"] = \
|
||||
dom.getElementsByTagName('TxRefNum')[0].firstChild.data
|
||||
result["order_id"] = \
|
||||
dom.getElementsByTagName('CustomerRefNum')[0].firstChild.data
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user