#!/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 by Adnan Smajlovic # # Copyright (C) <2012> Alan Etkin # License: BSD # 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 (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': , ...} # The data arguments are documented in the .charge() method help charge_test = {'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 = PAYMENTECH_PASSWORD = PAYMENTECH_INDUSTRY = PAYMENTECH_MESSAGE = PAYMENTECH_BIN_CODE= PAYMENTECH_MERCHANT = PAYMENTECH_terminal = DEVELOPMENT = True PAYMENTECH_TARGET = PAYMENTECH_HOST = PAYMENTECH_API_URL = # 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[] # # # 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 = """ %(user)s %(password)s %(industry)s %(message)s %(bin)s %(merchant)s %(terminal)s %(account)s %(exp)s %(currency_code)s %(currency_exponent)s %(card_sec_val_ind)s %(card_sec_val)s %(avs_zip)s %(avs_address_1)s %(avs_address_2)s %(avs_city)s %(avs_state)s %(avs_phone)s %(avs_country)s %(profile_from_order_ind)s %(profile_order_override_ind)s %(order_id)s %(amount)s """ 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="", password="", ...) # 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=, exp=, ...) 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 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 (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 """ # 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