diff --git a/r2/r2/lib/authorize/__init__.py b/r2/r2/lib/authorize/__init__.py
index f13e13984..0cd832e51 100644
--- a/r2/r2/lib/authorize/__init__.py
+++ b/r2/r2/lib/authorize/__init__.py
@@ -20,4 +20,5 @@
# Inc. All Rights Reserved.
###############################################################################
+from api import *
from interaction import *
diff --git a/r2/r2/lib/authorize/api.py b/r2/r2/lib/authorize/api.py
index 5165b1ef8..c0f797054 100644
--- a/r2/r2/lib/authorize/api.py
+++ b/r2/r2/lib/authorize/api.py
@@ -27,28 +27,33 @@ This file consists mostly of wrapper classes for dealing with their
API, while the actual useful functions live in interaction.py
"""
-from pylons import g
+import re
+import socket
from httplib import HTTPSConnection
from urlparse import urlparse
-import socket, re
+
from BeautifulSoup import BeautifulStoneSoup
-from r2.lib.utils import iters, Storage
-
-from r2.models import NotFound
-from r2.models.bidding import CustomerID, PayID, ShippingAddress
-
+from pylons import g
from xml.sax.saxutils import escape
+from r2.lib.export import export
+from r2.lib.utils import iters, Storage
+from r2.models.bidding import CustomerID, PayID, ShippingAddress
+
+__all__ = ["PROFILE_LIMIT"]
+
+
# list of the most common errors.
-Errors = Storage(TESTMODE = "E00009",
- TRANSACTION_FAIL = "E00027",
- DUPLICATE_RECORD = "E00039",
- RECORD_NOT_FOUND = "E00040",
- TOO_MANY_PAY_PROFILES = "E00042",
- TOO_MANY_SHIP_ADDRESSES = "E00043")
+Errors = Storage(TESTMODE="E00009",
+ TRANSACTION_FAIL="E00027",
+ DUPLICATE_RECORD="E00039",
+ RECORD_NOT_FOUND="E00040",
+ TOO_MANY_PAY_PROFILES="E00042",
+ TOO_MANY_SHIP_ADDRESSES="E00043")
PROFILE_LIMIT = 10 # max payment profiles per user allowed by authorize.net
+@export
class AuthorizeNetException(Exception):
def __init__(self, msg):
# don't let CC info show up in logs
@@ -65,6 +70,7 @@ class AuthorizeNetException(Exception):
# xml tags whose content shouldn't be escaped
_no_escape_list = ["extraOptions"]
+
class SimpleXMLObject(object):
"""
All API transactions are done with authorize.net using XML, so
@@ -81,9 +87,10 @@ class SimpleXMLObject(object):
@staticmethod
def simple_tag(name, content, **attrs):
attrs = " ".join('%s="%s"' % (k, v) for k, v in attrs.iteritems())
- if attrs: attrs = " " + attrs
+ if attrs:
+ attrs = " " + attrs
return ("<%(name)s%(attrs)s>%(content)s%(name)s>" %
- dict(name = name, content = content, attrs = attrs))
+ dict(name=name, content=content, attrs=attrs))
def toXML(self):
content = []
@@ -128,9 +135,12 @@ class SimpleXMLObject(object):
def _wrapper(self, content):
return content
+
class Auth(SimpleXMLObject):
_keys = ["name", "transactionKey"]
+
+@export
class Address(SimpleXMLObject):
_keys = ["firstName", "lastName", "company", "address",
"city", "state", "zip", "country", "phoneNumber",
@@ -144,11 +154,11 @@ class Address(SimpleXMLObject):
SimpleXMLObject.__init__(self, **kw)
+@export
class CreditCard(SimpleXMLObject):
_keys = ["cardNumber", "expirationDate", "cardCode"]
-
class Profile(SimpleXMLObject):
"""
Converts a user into a Profile object.
@@ -157,23 +167,22 @@ class Profile(SimpleXMLObject):
"email", "customerProfileId", "paymentProfiles", "shipToList",
"validationMode"]
def __init__(self, user, paymentProfiles, address,
- validationMode = None):
- SimpleXMLObject.__init__(self, merchantCustomerId = user._fullname,
- description = user.name, email = "",
- paymentProfiles = paymentProfiles,
- shipToList = address,
- validationMode = validationMode,
+ validationMode=None):
+ SimpleXMLObject.__init__(self, merchantCustomerId=user._fullname,
+ description=user.name, email="",
+ paymentProfiles=paymentProfiles,
+ shipToList=address,
+ validationMode=validationMode,
customerProfileId=CustomerID.get_id(user))
-
class PaymentProfile(SimpleXMLObject):
_keys = ["billTo", "payment", "customerPaymentProfileId", "validationMode"]
- def __init__(self, billTo, card, paymentId = None,
- validationMode = None):
- SimpleXMLObject.__init__(self, billTo = billTo,
- customerPaymentProfileId = paymentId,
- payment = SimpleXMLObject(creditCard = card),
- validationMode = validationMode)
+ def __init__(self, billTo, card, paymentId=None,
+ validationMode=None):
+ SimpleXMLObject.__init__(self, billTo=billTo,
+ customerPaymentProfileId=paymentId,
+ payment=SimpleXMLObject(creditCard=card),
+ validationMode=validationMode)
@classmethod
def fromXML(cls, res):
@@ -181,38 +190,56 @@ class PaymentProfile(SimpleXMLObject):
return cls(Address.fromXML(res.billto),
CreditCard.fromXML(res.payment), payid)
+
+@export
class Order(SimpleXMLObject):
_keys = ["invoiceNumber", "description", "purchaseOrderNumber"]
+
class Transaction(SimpleXMLObject):
_keys = ["amount", "customerProfileId", "customerPaymentProfileId",
"transId", "order"]
- def __init__(self, amount, profile_id, pay_id, trans_id = None,
- order = None):
- SimpleXMLObject.__init__(self, amount = amount,
- customerProfileId = profile_id,
- customerPaymentProfileId = pay_id,
- transId = trans_id,
- order = order)
+ def __init__(self, amount, profile_id, pay_id, trans_id=None,
+ order=None):
+ SimpleXMLObject.__init__(self, amount=amount,
+ customerProfileId=profile_id,
+ customerPaymentProfileId=pay_id,
+ transId=trans_id,
+ order=order)
def _wrapper(self, content):
return self.simple_tag(self._name(), content)
+
# authorize and charge
+@export
class ProfileTransAuthCapture(Transaction): pass
-# only authorize (no charge is made)
-class ProfileTransAuthOnly(Transaction): pass
-# charge only (requires previous auth_only)
-class ProfileTransPriorAuthCapture(Transaction): pass
-# stronger than above: charge even on decline (not sure why you would want to)
-class ProfileTransCaptureOnly(Transaction): pass
-# refund a transaction
-class ProfileTransRefund(Transaction): pass
-# void a transaction
-class ProfileTransVoid(Transaction): pass
+# only authorize (no charge is made)
+@export
+class ProfileTransAuthOnly(Transaction): pass
+
+
+# charge only (requires previous auth_only)
+@export
+class ProfileTransPriorAuthCapture(Transaction): pass
+
+
+# stronger than above: charge even on decline (not sure why you would want to)
+@export
+class ProfileTransCaptureOnly(Transaction): pass
+
+
+# refund a transaction
+@export
+class ProfileTransRefund(Transaction): pass
+
+
+# void a transaction
+@export
+class ProfileTransVoid(Transaction): pass
#-----
@@ -221,8 +248,8 @@ class AuthorizeNetRequest(SimpleXMLObject):
@property
def merchantAuthentication(self):
- return Auth(name = g.authorizenetname,
- transactionKey = g.authorizenetkey)
+ return Auth(name=g.authorizenetname,
+ transactionKey=g.authorizenetkey)
def _wrapper(self, content):
return ('' +
@@ -253,7 +280,7 @@ class AuthorizeNetRequest(SimpleXMLObject):
_autoclose_re = re.compile("<([^/]+)/>")
def _autoclose_handler(self, m):
- return "<%(m)s>%(m)s>" % dict(m = m.groups()[0])
+ return "<%(m)s>%(m)s>" % dict(m=m.groups()[0])
def handle_response(self, res):
res = self._autoclose_re.sub(self._autoclose_handler, res)
@@ -277,11 +304,11 @@ class CustomerRequest(AuthorizeNetRequest):
else:
cust_id = CustomerID.get_id(user)
self._user = user
- AuthorizeNetRequest.__init__(self, customerProfileId = cust_id, **kw)
-
+ AuthorizeNetRequest.__init__(self, customerProfileId=cust_id, **kw)
# --- real request classes below
+
class CreateCustomerProfileRequest(AuthorizeNetRequest):
"""
Create a new user object on authorize.net and return the new object ID.
@@ -291,12 +318,12 @@ class CreateCustomerProfileRequest(AuthorizeNetRequest):
"""
_keys = AuthorizeNetRequest._keys + ["profile", "validationMode"]
- def __init__(self, user, validationMode = None):
+ def __init__(self, user, validationMode=None):
# cache the user object passed in
self._user = user
AuthorizeNetRequest.__init__(self,
- profile = Profile(user, None, None),
- validationMode = validationMode)
+ profile=Profile(user, None, None),
+ validationMode=validationMode)
def process_response(self, res):
customer_id = int(res.customerprofileid.contents[0])
@@ -330,6 +357,7 @@ class CreateCustomerProfileRequest(AuthorizeNetRequest):
return cust_id
return AuthorizeNetRequest.process_error(self, res)
+
class CreateCustomerPaymentProfileRequest(CustomerRequest):
"""
Adds a payment profile to an existing user object. The profile
@@ -337,11 +365,11 @@ class CreateCustomerPaymentProfileRequest(CustomerRequest):
"""
_keys = (CustomerRequest._keys + ["paymentProfile", "validationMode"])
- def __init__(self, user, address, creditcard, validationMode = None):
+ def __init__(self, user, address, creditcard, validationMode=None):
CustomerRequest.__init__(self, user,
- paymentProfile = PaymentProfile(address,
- creditcard),
- validationMode = validationMode)
+ paymentProfile=PaymentProfile(address,
+ creditcard),
+ validationMode=validationMode)
def process_response(self, res):
pay_id = int(res.customerpaymentprofileid.contents[0])
@@ -355,7 +383,8 @@ class CreateCustomerPaymentProfileRequest(CustomerRequest):
if len(profiles) == 1:
return profiles[0].customerPaymentProfileId
return
- return CustomerRequest.process_error(self,res)
+ return CustomerRequest.process_error(self, res)
+
class CreateCustomerShippingAddressRequest(CustomerRequest):
"""
@@ -396,7 +425,7 @@ class GetCustomerPaymentProfileRequest(CustomerRequest):
def process_error(self, res):
if self.is_error_code(res, Errors.RECORD_NOT_FOUND):
PayID.delete(self._user, self.customerPaymentProfileId)
- return CustomerRequest.process_error(self,res)
+ return CustomerRequest.process_error(self, res)
class GetCustomerShippingAddressRequest(CustomerRequest):
@@ -418,7 +447,8 @@ class GetCustomerShippingAddressRequest(CustomerRequest):
def process_error(self, res):
if self.is_error_code(res, Errors.RECORD_NOT_FOUND):
ShippingAddress.delete(self._user, self.customerAddressId)
- return CustomerRequest.process_error(self,res)
+ return CustomerRequest.process_error(self, res)
+
class GetCustomerProfileIdsRequest(AuthorizeNetRequest):
"""
@@ -428,6 +458,7 @@ class GetCustomerProfileIdsRequest(AuthorizeNetRequest):
def process_response(self, res):
return [int(x.contents[0]) for x in res.ids.findAll('numericstring')]
+
class GetCustomerProfileRequest(CustomerRequest):
"""
Given a user, find their customer information.
@@ -458,7 +489,7 @@ class GetCustomerProfileRequest(CustomerRequest):
for profile in res.findAll("paymentprofiles"):
a = Address.fromXML(profile)
cc = CreditCard.fromXML(profile.payment)
- payprof = PaymentProfile(a, cc,int(a.customerPaymentProfileId))
+ payprof = PaymentProfile(a, cc, int(a.customerPaymentProfileId))
PayID.add(acct, a.customerPaymentProfileId)
profiles.append(payprof)
@@ -476,20 +507,22 @@ class DeleteCustomerProfileRequest(CustomerRequest):
def process_error(self, res):
if self.is_error_code(res, Errors.RECORD_NOT_FOUND):
CustomerID.delete(self._user)
- return CustomerRequest.process_error(self,res)
+ return CustomerRequest.process_error(self, res)
+
class DeleteCustomerPaymentProfileRequest(GetCustomerPaymentProfileRequest):
"""
Delete a customer shipping address
"""
def process_response(self, res):
- PayID.delete(self._user,self.customerPaymentProfileId)
+ PayID.delete(self._user, self.customerPaymentProfileId)
return True
def process_error(self, res):
if self.is_error_code(res, Errors.RECORD_NOT_FOUND):
- PayID.delete(self._user,self.customerPaymentProfileId)
- return GetCustomerPaymentProfileRequest.process_error(self,res)
+ PayID.delete(self._user, self.customerPaymentProfileId)
+ return GetCustomerPaymentProfileRequest.process_error(self, res)
+
class DeleteCustomerShippingAddressRequest(GetCustomerShippingAddressRequest):
"""
@@ -502,9 +535,7 @@ class DeleteCustomerShippingAddressRequest(GetCustomerShippingAddressRequest):
def process_error(self, res):
if self.is_error_code(res, Errors.RECORD_NOT_FOUND):
ShippingAddress.delete(self._user, self.customerAddressId)
- GetCustomerShippingAddressRequest.process_error(self,res)
-
-
+ GetCustomerShippingAddressRequest.process_error(self, res)
# TODO
@@ -520,12 +551,12 @@ class UpdateCustomerPaymentProfileRequest(CreateCustomerPaymentProfileRequest):
For updating the user's payment profile
"""
def __init__(self, user, paymentid, address, creditcard,
- validationMode = None):
+ validationMode=None):
CustomerRequest.__init__(self, user,
paymentProfile=PaymentProfile(address,
creditcard,
paymentid),
- validationMode = validationMode)
+ validationMode=validationMode)
def process_response(self, res):
return self.paymentProfile.customerPaymentProfileId
@@ -539,14 +570,12 @@ class UpdateCustomerShippingAddressRequest(
def __init__(self, user, address_id, address):
address.customerAddressId = address_id
CreateCustomerShippingAddressRequest.__init__(self, user,
- address = address)
+ address=address)
def process_response(self, res):
return True
-
-
class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest):
_keys = AuthorizeNetRequest._keys + ["transaction", "extraOptions"]
@@ -579,10 +608,10 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest):
"cav_response")
# list of casts for the response fields given above
- response_types = dict(response_code = int,
- response_subcode = int,
- response_reason_code = int,
- trans_id = int)
+ response_types = dict(response_code=int,
+ response_subcode=int,
+ response_reason_code=int,
+ trans_id=int)
def __init__(self, **kw):
from pylons import g
@@ -604,7 +633,7 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest):
return (False, self.package_response(res))
elif self.is_error_code(res, Errors.TESTMODE):
return (None, None)
- return AuthorizeNetRequest.process_error(self,res)
+ return AuthorizeNetRequest.process_error(self, res)
def package_response(self, res):
@@ -617,6 +646,7 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest):
pass
return s
+
class GetSettledBatchListRequest(AuthorizeNetRequest):
_keys = AuthorizeNetRequest._keys + ["includeStatistics",
"firstSettlementDate",
diff --git a/r2/r2/lib/authorize/interaction.py b/r2/r2/lib/authorize/interaction.py
index 26abc8e4a..33978ec3f 100644
--- a/r2/r2/lib/authorize/interaction.py
+++ b/r2/r2/lib/authorize/interaction.py
@@ -20,9 +20,29 @@
# Inc. All Rights Reserved.
###############################################################################
-from api import *
from pylons import g
-from r2.models.bidding import Bid
+
+from r2.lib.db.thing import NotFound
+from r2.lib.utils import Storage
+from r2.lib.export import export
+from r2.models.bidding import Bid, CustomerID, PayID
+
+from r2.lib.authorize.api import (
+ Address,
+ AuthorizeNetException,
+ CreateCustomerPaymentProfileRequest,
+ CreateCustomerProfileRequest,
+ CreateCustomerProfileTransactionRequest,
+ CreditCard,
+ GetCustomerProfileRequest,
+ Order,
+ ProfileTransAuthOnly,
+ ProfileTransPriorAuthCapture,
+ ProfileTransVoid,
+ UpdateCustomerPaymentProfileRequest,
+)
+
+__all__ = []
# useful test data:
test_card = dict(AMEX = ("370000000000002" , 1234),
@@ -32,15 +52,21 @@ test_card = dict(AMEX = ("370000000000002" , 1234),
# visa card which generates error codes based on the amount
ERRORCARD = ("4222222222222" , 123))
-test_card = Storage((k, CreditCard(cardNumber = x, expirationDate="2011-11",
- cardCode = y)) for k, (x, y) in
+test_card = Storage((k, CreditCard(cardNumber=x,
+ expirationDate="2011-11",
+ cardCode=y)) for k, (x, y) in
test_card.iteritems())
-test_address = Address(firstName = "John", lastName = "Doe",
- address = "123 Fake St.",
- city = "Anytown", state = "MN", zip = "12346")
+test_address = Address(firstName="John",
+ lastName="Doe",
+ address="123 Fake St.",
+ city="Anytown",
+ state="MN",
+ zip="12346")
-def get_account_info(user, recursed = False):
+
+@export
+def get_account_info(user, recursed=False):
# if we don't have an ID for the user, try to make one
if not CustomerID.get_id(user):
cust_id = CreateCustomerProfileRequest(user).make_request()
@@ -61,7 +87,9 @@ def get_account_info(user, recursed = False):
raise AuthorizeNetException, "error creating user"
return data
-def edit_profile(user, address, creditcard, pay_id = None):
+
+@export
+def edit_profile(user, address, creditcard, pay_id=None):
if pay_id:
return UpdateCustomerPaymentProfileRequest(
user, pay_id, address, creditcard).make_request()
@@ -70,10 +98,8 @@ def edit_profile(user, address, creditcard, pay_id = None):
user, address, creditcard).make_request()
-
-
def _make_transaction(trans_cls, amount, user, pay_id,
- order = None, trans_id = None, test = None):
+ order=None, trans_id=None, test=None):
"""
private function for handling transactions (since the data is
effectively the same regardless of trans_cls)
@@ -84,32 +110,33 @@ def _make_transaction(trans_cls, amount, user, pay_id,
# lookup customer ID
cust_id = CustomerID.get_id(user)
# create a new transaction
- trans = trans_cls(amount, cust_id, pay_id, trans_id = trans_id,
- order = order)
+ trans = trans_cls(amount, cust_id, pay_id, trans_id=trans_id,
+ order=order)
extra = {}
# the optional test field makes the transaction a test, and will
# make the response be the error code corresponding to int(test).
if isinstance(test, int):
- extra = dict(x_test_request = "TRUE",
- x_card_num = test_card.ERRORCARD.cardNumber,
- x_amount = test)
+ extra = dict(x_test_request="TRUE",
+ x_card_num=test_card.ERRORCARD.cardNumber,
+ x_amount=test)
# using the transaction, generate a transaction request and make it
- req = CreateCustomerProfileTransactionRequest(transaction = trans,
- extraOptions = extra)
+ req = CreateCustomerProfileTransactionRequest(transaction=trans,
+ extraOptions=extra)
return req.make_request()
-def auth_transaction(amount, user, payid, thing, campaign, test = None):
+@export
+def auth_transaction(amount, user, payid, thing, campaign, test=None):
# use negative pay_ids to identify freebies, coupons, or anything
# that doesn't require a CC.
if payid < 0:
trans_id = -thing._id
# update previous freebie transactions if we can
try:
- bid = Bid.one(thing_id = thing._id,
- transaction = trans_id,
- campaign = campaign)
+ bid = Bid.one(thing_id=thing._id,
+ transaction=trans_id,
+ campaign=campaign)
bid.bid = amount
bid.auth()
except NotFound:
@@ -117,10 +144,10 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None):
return bid.transaction, ""
elif int(payid) in PayID.get_ids(user):
- order = Order(invoiceNumber = "T%dC%d" % (thing._id, campaign))
+ order = Order(invoiceNumber="T%dC%d" % (thing._id, campaign))
success, res = _make_transaction(ProfileTransAuthOnly,
amount, user, payid,
- order = order, test = test)
+ order=order, test=test)
if success:
if test:
return auth_transaction(amount, user, -1, thing, campaign,
@@ -130,7 +157,7 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None):
return res.trans_id, ""
elif res is None:
# we are in test mode!
- return auth_transaction(amount, user, -1, thing, test = test)
+ return auth_transaction(amount, user, -1, thing, test=test)
# duplicate transaction, which is bad, but not horrible. Log
# the transaction id, creating a new bid if necessary.
elif res.trans_id and (res.response_code, res.response_reason_code) == (3,11):
@@ -143,24 +170,27 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None):
return res.trans_id, res.response_reason_text
-
-def void_transaction(user, trans_id, campaign, test = None):
- bid = Bid.one(transaction = trans_id, campaign = campaign)
+@export
+def void_transaction(user, trans_id, campaign, test=None):
+ bid = Bid.one(transaction=trans_id, campaign=campaign)
bid.void()
if trans_id > 0:
res = _make_transaction(ProfileTransVoid,
- None, user, None, trans_id = trans_id,
- test = test)
+ None, user, None, trans_id=trans_id,
+ test=test)
return res
+@export
def is_charged_transaction(trans_id, campaign):
if not trans_id: return False # trans_id == 0 means no bid
- bid = Bid.one(transaction = trans_id, campaign = campaign)
+ bid = Bid.one(transaction=trans_id, campaign=campaign)
return bid.is_charged()
-def charge_transaction(user, trans_id, campaign, test = None):
- bid = Bid.one(transaction = trans_id, campaign = campaign)
+
+@export
+def charge_transaction(user, trans_id, campaign, test=None):
+ bid = Bid.one(transaction=trans_id, campaign=campaign)
if not bid.is_charged():
bid.charged()
if trans_id < 0:
@@ -169,8 +199,8 @@ def charge_transaction(user, trans_id, campaign, test = None):
elif bid.account_id == user._id:
res = _make_transaction(ProfileTransPriorAuthCapture,
bid.bid, user,
- bid.pay_id, trans_id = trans_id,
- test = test)
+ bid.pay_id, trans_id=trans_id,
+ test=test)
return bool(res)
# already charged
diff --git a/r2/r2/tests/unit/lib/authorize/test_api.py b/r2/r2/tests/unit/lib/authorize/test_api.py
new file mode 100755
index 000000000..55d940561
--- /dev/null
+++ b/r2/r2/tests/unit/lib/authorize/test_api.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+import unittest
+
+class AuthorizeNetExceptionTest(unittest.TestCase):
+
+ def test_exception_message(self):
+ from r2.lib.authorize.api import AuthorizeNetException
+ card_number = "