Skip to content
1 change: 1 addition & 0 deletions paython/gateways/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from plugnpay import PlugnPay
from stripe_com import Stripe
from samurai_ff import Samurai
from authecheckdotnet import AuthECheckDotNet
125 changes: 125 additions & 0 deletions paython/gateways/authecheckdotnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import time
from paython.exceptions import GatewayError
from paython.gateways.authorize_net import AuthorizeNet
import requests
import copy

class AuthECheckDotNet(AuthorizeNet):
REQUEST_FIELDS=copy.deepcopy(AuthorizeNet.REQUEST_FIELDS)
REQUEST_FIELDS.update({
'aba_code':'x_bank_aba_code',
'acct_num':'x_bank_acct_num',
'acct_type':'x_bank_acct_type',
'bank_name':'x_bank_name',
'acct_name':'x_bank_acct_name',
'echeck_type':'x_echeck_type',
'check_num':'x_bank_check_number',
'recurring_billing':'x_recurring_billing',
})


def __init__(self, username='test', password='testpassword', debug=False, test=False, delim=None):
#Set Required Values
super(AuthECheckDotNet, self).__init__(username=username, password=password,
debug=debug, test=test,delim=delim)
# Update Fields to bubble up to Base Class
super(AuthorizeNet, self).__init__(translations=self.REQUEST_FIELDS, debug=debug)


def auth(self, amount, echeck_type=None, bank_account=None, billing_info=None, shipping_info=None, invoice_num=None, duplicate_window=120, customer_ip=None):
"""
Sends Bank and Check details for authorization
"""
#set up transaction
super(AuthECheckDotNet,self).charge_setup()
""" Change Method to Echeck Instead of CC """
super(AuthECheckDotNet, self).set('x_method', 'ECHECK')
#setting transaction data
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['amount'], amount)
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['trans_type'], 'AUTH_ONLY')
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['duplicate_window'], duplicate_window)
if customer_ip:
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['ip'], customer_ip)

if not echeck_type:
debug_string = "No Echeck Type Given"
logger.debug(debug_string)
raise MissingDataError('You did not pass an ECheck Type')
else:
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['echeck_type'], echeck_type)

if invoice_num is not None:
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['invoice_num'], invoice_num)

# validating or building up request
if not bank_account:
debug_string = "No Account object present. You passed in %s " % (bank_account)
logger.debug(debug_string)
raise MissingDataError('You did not pass an account object into the arc method')
else:
super(AuthECheckDotNet, self).use_echeck(bank_account)

#Set Conditionally Required Fields
if echeck_type == 'ARC' or echeck_type == 'BOC':
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['check_num'], bank_account.check_num)
elif echeck_type == 'WEB' or echeck_type == 'TEL':
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['recurring_billing'], bank_account.recurring_billing)

if billing_info:
super(AuthECheckDotNet, self).set_billing_info(**billing_info)

if shipping_info:
super(AuthECheckDotNet, self).set_shipping_info(**shipping_info)

# send transaction to gateway!
response, response_time = super(AuthECheckDotNet,self).request()
return super(AuthECheckDotNet,self).parse(response, response_time)

def capture(self, amount, echeck_type=None, bank_account=None, billing_info=None, shipping_info=None, invoice_num=None, duplicate_window=120):
"""
Sends Bank and Check details for authorization
"""
#set up transaction
super(AuthECheckDotNet,self).charge_setup()
""" Change Method to Echeck Instead of CC """
super(AuthECheckDotNet, self).set('x_method', 'ECHECK')
#setting transaction data
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['amount'], amount)
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['trans_type'], 'AUTH_CAPTURE')
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['duplicate_window'], duplicate_window)

if not echeck_type:
debug_string = "No Echeck Type Given"
logger.debug(debug_string)
raise MissingDataError('You did not pass an ECheck Type')
else:
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['echeck_type'], echeck_type)

if invoice_num is not None:
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['invoice_num'], invoice_num)

# validating or building up request
if not bank_account:
debug_string = "No Account object present. You passed in %s " % (bank_account)
logger.debug(debug_string)
raise MissingDataError('You did not pass an account object into the arc method')
else:
super(AuthECheckDotNet, self).use_echeck(bank_account)

#Set Conditionally Required Fields
if echeck_type == 'ARC' or echeck_type == 'BOC':
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['check_num'], bank_account.check_num)
elif echeck_type == 'WEB' or echeck_type == 'TEL':
super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['recurring_billing'], bank_account.recurring_billing)

if billing_info:
super(AuthECheckDotNet, self).set_billing_info(**billing_info)

if shipping_info:
super(AuthECheckDotNet, self).set_shipping_info(**shipping_info)

# send transaction to gateway!
response, response_time = super(AuthECheckDotNet,self).request()
return super(AuthECheckDotNet,self).parse(response, response_time)


18 changes: 12 additions & 6 deletions paython/gateways/authorize_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import logging

from paython.exceptions import MissingDataError
from paython.lib.api import GetGateway
from paython.lib.api import PostGateway

logger = logging.getLogger(__name__)

class AuthorizeNet(GetGateway):
class AuthorizeNet(PostGateway):
"""TODO needs docstring"""
VERSION = '3.1'
DELIMITER = ';'
Expand Down Expand Up @@ -60,6 +60,7 @@ class AuthorizeNet(GetGateway):
'alt_trans_id': None,
'split_tender_id':'x_split_tender_id',
'is_partial':'x_allow_partial_auth',
'duplicate_window': 'x_duplicate_window'
}

# Response Code: 1 = Approved, 2 = Declined, 3 = Error, 4 = Held for Review
Expand Down Expand Up @@ -168,19 +169,23 @@ def charge_setup(self):
super(AuthorizeNet, self).set('x_delim_data', 'TRUE')
super(AuthorizeNet, self).set('x_delim_char', self.DELIMITER)
super(AuthorizeNet, self).set('x_version', self.VERSION)
super(AuthorizeNet, self).set('x_method', 'CC')
debug_string = " paython.gateways.authorize_net.charge_setup() Just set up for a charge "
logger.debug(debug_string.center(80, '='))

def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None, is_partial=False, split_id=None, invoice_num=None):
def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None, is_partial=False, split_id=None, invoice_num=None, duplicate_window=120, customer_ip=None):
"""
Sends charge for authorization based on amount
"""
#set up transaction
self.charge_setup() # considering turning this into a decorator?

#setting transaction data
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['amount'], amount)
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['trans_type'], 'AUTH_ONLY')
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['duplicate_window'], duplicate_window)
if customer_ip:
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['ip'], customer_ip)

if invoice_num is not None:
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['invoice_num'], invoice_num)

Expand Down Expand Up @@ -208,7 +213,7 @@ def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None,
response, response_time = self.request()
return self.parse(response, response_time)

def settle(self, amount, trans_id, split_id=None):
def settle(self, amount, trans_id, split_id=None, duplicate_window=120):
"""
Sends prior authorization to be settled based on amount & trans_id PRIOR_AUTH_CAPTURE
"""
Expand All @@ -219,6 +224,7 @@ def settle(self, amount, trans_id, split_id=None):
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['trans_type'], 'PRIOR_AUTH_CAPTURE')
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['amount'], amount)
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['trans_id'], trans_id)
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['duplicate_window'], duplicate_window)

if split_id: # settles the entire split
super(AuthorizeNet, self).set(self.REQUEST_FIELDS['split_tender_id'], split_id)
Expand Down Expand Up @@ -312,7 +318,7 @@ def request(self):

debug_string = " paython.gateways.authorize_net.request() -- Attempting request to: "
logger.debug(debug_string.center(80, '='))
debug_string = "%s with params: %s" % (url, super(AuthorizeNet, self).query_string())
debug_string = "%s with params: %s" % (url, super(AuthorizeNet, self).params())
logger.debug(debug_string)
logger.debug('as dict: %s' % self.REQUEST_DICT)

Expand Down
11 changes: 11 additions & 0 deletions paython/gateways/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ def use_credit_card(self, credit_card):
self.set(self.REQUEST_FIELDS[key], value)
except KeyError:
pass # it is okay to fail (on exp_month & exp_year)

def use_echeck(self, bank_account):
"""
Set up echceck info use (if necessary for transaction)
"""
for key, value in bank_account.__dict__.items():
if not key.startswith('_'):
try:
self.set(self.REQUEST_FIELDS[key], value)
except KeyError:
pass

def set_billing_info(self, address=None, address2=None, city=None, state=None, zipcode=None, country=None, phone=None, email=None, ip=None, first_name=None, last_name=None):
"""
Expand Down
64 changes: 54 additions & 10 deletions paython/gateways/stripe_com.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,62 @@ def __init__(self, username=None, api_key=None, debug=False):
logger.debug(debug_string.center(80, '='))

def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None):
"""
Not implemented because stripe does not support authorizations:
https://answers.stripe.com/questions/can-i-authorize-transactions-first-then-charge-the-customer-after-service-is-comp
"""
raise NotImplementedError('Stripe does not support auth or settlement. Try capture().')
debug_string = " paython.gateways.stripe.parse() -- Sending charge for Authorization"
logger.debug(debug_string.center(80, '='))

amount = int(float(amount)*100) # then change the amount to how stripe likes it

start = time.time() # timing it
try:
response = self.stripe_api.Charge.create(
amount=amount,
currency="usd",
card={
"name":credit_card.full_name,
"number": credit_card.number,
"exp_month": credit_card.exp_month,
"exp_year": credit_card.exp_year,
"cvc": credit_card.verification_value if credit_card.verification_value else None,
"address_line1":billing_info.get('address'),
"address_line2":billing_info.get('address2'),
"address_zip":billing_info.get('zipcode'),
"address_state":billing_info.get('state'),
},
capture=False,
)
except stripe.InvalidRequestError, e:
response = {'failure_message':'Invalid Request: %s' % e}
end = time.time() # done timing it
response_time = '%0.2f' % (end-start)
except stripe.CardError, e:
response = {'failure_message':'Card Error: %s' % e}
end = time.time() # done timing it
response_time = '%0.2f' % (end-start)
else:
end = time.time() # done timing it
response_time = '%0.2f' % (end-start)

return self.parse(response, response_time)

def settle(self, amount, trans_id):
"""
Not implemented because stripe does not support auth/settle:
https://answers.stripe.com/questions/can-i-authorize-transactions-first-then-charge-the-customer-after-service-is-comp
"""
raise NotImplementedError('Stripe does not support auth or settlement. Try capture().')
debug_string = " paython.gateways.stripe.parse() -- Sending charge For Capture with Prior Authorization"
logger.debug(debug_string.center(80, '='))

amount = int(float(amount)*100) # then change the amount to how stripe likes it

start = time.time() # timing it
try:
charge = self.stripe_api.Charge.retrieve(trans_id)
response = charge.capture()
except stripe.InvalidRequestError, e:
response = {'failure_message':'Invalid Request: %s' % e}
end = time.time() # done timing it
response_time = '%0.2f' % (end-start)
else:
end = time.time() # done timing it
response_time = '%0.2f' % (end-start)

return self.parse(response, response_time)

def capture(self, amount, credit_card=None, billing_info=None, shipping_info=None):
debug_string = " paython.gateways.stripe.parse() -- Sending charge "
Expand Down
3 changes: 2 additions & 1 deletion paython/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from cc import CreditCard
from cc import CreditCard
from echeck import ECheck
1 change: 0 additions & 1 deletion paython/lib/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ def make_request(self, uri):
try:
params = self.query_string()
request = urllib.urlopen('%s%s' % (uri, params))

return request.read()
except:
raise GatewayError('Error making request to gateway')
Expand Down
61 changes: 61 additions & 0 deletions paython/lib/echeck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from paython.exceptions import DataValidationError
from paython.lib.utils import is_valid_aba

class ECheck(object):
"""
generic ECheck object
"""
def __init__(self, aba_code, acct_num, acct_type, bank_name,first_name=None, last_name=None, acct_name=None, check_num=None,recurring_billing=None):
"""
sets eCheck info
"""
if acct_name:
self.acct_name = acct_name
else:
self.first_name = first_name
self.last_name = last_name
self.acct_name = "{0.first_name} {0.last_name}".format(self)

#everything else
self.aba_code = aba_code
self.acct_num = acct_num
self.acct_type = acct_type
self.bank_name = bank_name
self.check_num = check_num
self.recurring_billing = recurring_billing

def __repr__(self):
"""
string repr for debugging
"""
return u'<eCheck -- {0.acct_name}, aba_code: {0.aba_code}, acct_num: {0.safe_num}, acct_type: {0.acct_type} \
bank_name: {0.bank_name}>, check_num: {0.check_num}'.format(self)


@property
def safe_num(self):
"""
outputs the account number with *'s, only exposing last four digits of account number
"""
account_length = len(self.acct_num)
stars = '*' * (account_length - 4)
return '{0}{1}'.format(stars, self.acct_num[-4:])

def is_valid(self):
"""
boolean to see if a Details is valid
"""
try:
self.validate()
except DataValidationError:
return False
else:
return True

def validate(self):
"""
validates All Codes using util functions
"""
if not is_valid_aba(self.aba_code):
raise DataValidationError('The ABA Code doe not pass validation')
return True
9 changes: 9 additions & 0 deletions paython/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,14 @@ def is_valid_email(email):
pat = '^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$'
return re.search(pat, email, re.IGNORECASE)

def is_valid_aba(aba):
try:
num = map(int, aba)
except ValueError:
return False
else:
return not sum([3*x for x in num[::3]]+[7*x for x in num[1::3]]+[x for x in num[2::3]]) % 10

def transform_keys():
raise NotImplemented

Loading