From af752f7ac446638df87c6d5e861d9590ab1e3283 Mon Sep 17 00:00:00 2001 From: django Date: Fri, 20 Dec 2013 15:42:55 -0500 Subject: [PATCH 01/15] Added duplicate window value to auth and settle --- paython/gateways/authorize_net.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/paython/gateways/authorize_net.py b/paython/gateways/authorize_net.py index 696da7b..6ecf4d6 100644 --- a/paython/gateways/authorize_net.py +++ b/paython/gateways/authorize_net.py @@ -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 @@ -171,7 +172,7 @@ def charge_setup(self): 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): """ Sends charge for authorization based on amount """ @@ -181,6 +182,8 @@ def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None, #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 invoice_num is not None: super(AuthorizeNet, self).set(self.REQUEST_FIELDS['invoice_num'], invoice_num) @@ -208,7 +211,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 """ @@ -219,6 +222,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) From 748994cd1d1a5bf7d93fd0ecff9865442d586f97 Mon Sep 17 00:00:00 2001 From: django Date: Fri, 14 Feb 2014 07:56:51 -0500 Subject: [PATCH 02/15] Initial eCheck.Net Integration --- paython/gateways/authecheckdotnet.py | 77 ++++++++++++++++++++++++++++ paython/gateways/core.py | 11 ++++ paython/lib/echeck.py | 52 +++++++++++++++++++ paython/lib/utils.py | 9 ++++ 4 files changed, 149 insertions(+) create mode 100644 paython/gateways/authecheckdotnet.py create mode 100644 paython/lib/echeck.py diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py new file mode 100644 index 0000000..51f1b45 --- /dev/null +++ b/paython/gateways/authecheckdotnet.py @@ -0,0 +1,77 @@ +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=method.test,delim=delim) + # Update Fields to bubble up to Base Class + super(AuthECheckDotNet, self).__init__(translations=self.REQUEST_FIELDS, debug=debug) + + def charge_setup(self): + super(AuthECheckDotNet,self).charge_setup() + """ Change Method to Echeck Instead of CC """ + super(AuthECheckDotNet, self).set('x_method', 'ECHECK') + + def transact(self, amount, echeck_type=None, bank_account=None, billing_info=None, shipping_info=None, invoice_num=None): + """ + Sends Bank and Check details for authorization + """ + #set up transaction + self.charge_setup() + + #setting transaction data + super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['amount'], amount) + + 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) + + diff --git a/paython/gateways/core.py b/paython/gateways/core.py index 4978973..a5735b7 100644 --- a/paython/gateways/core.py +++ b/paython/gateways/core.py @@ -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): """ diff --git a/paython/lib/echeck.py b/paython/lib/echeck.py new file mode 100644 index 0000000..63136b3 --- /dev/null +++ b/paython/lib/echeck.py @@ -0,0 +1,52 @@ +from paython.exceptions import DataValidationError + +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, strict=False): + """ + 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.strict = strict + + def is_valid(self): + """ + boolean to see if a Details is valid + """ + try: + self.validate() + except DataValidationError: + return False + else: + return True + + def __repr__(self): + """ + string repr for debugging + """ + return u', check_num: {0.check_num}'.format(self) + + + 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 diff --git a/paython/lib/utils.py b/paython/lib/utils.py index 6122ba6..a8fee42 100644 --- a/paython/lib/utils.py +++ b/paython/lib/utils.py @@ -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 + From 133029b25c7e8f62c20e1957f6fbcc3e7eb6a675 Mon Sep 17 00:00:00 2001 From: django Date: Mon, 17 Feb 2014 02:56:06 -0500 Subject: [PATCH 03/15] Add Missing imports in __init__ --- paython/gateways/__init__.py | 1 + paython/lib/__init__.py | 3 ++- paython/lib/echeck.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/paython/gateways/__init__.py b/paython/gateways/__init__.py index 002282e..fac22bc 100644 --- a/paython/gateways/__init__.py +++ b/paython/gateways/__init__.py @@ -4,3 +4,4 @@ from plugnpay import PlugnPay from stripe_com import Stripe from samurai_ff import Samurai +from authecheckdotnet import AuthECheckDotNet diff --git a/paython/lib/__init__.py b/paython/lib/__init__.py index a9dbeeb..46836aa 100644 --- a/paython/lib/__init__.py +++ b/paython/lib/__init__.py @@ -1 +1,2 @@ -from cc import CreditCard \ No newline at end of file +from cc import CreditCard +from echeck import ECheck diff --git a/paython/lib/echeck.py b/paython/lib/echeck.py index 63136b3..17b4d3e 100644 --- a/paython/lib/echeck.py +++ b/paython/lib/echeck.py @@ -4,7 +4,7 @@ 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, strict=False): + 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, strict=False): """ sets eCheck info """ @@ -21,6 +21,7 @@ def __init__(self, aba_code, acct_num, acct_type, bank_name,first_name=None, las self.acct_type = acct_type self.bank_name = bank_name self.check_num = check_num + self.recurring_billing = recurring_billing self.strict = strict From 71d17545029233a3531d0a65421e2dcf3baaf7a9 Mon Sep 17 00:00:00 2001 From: django Date: Mon, 17 Feb 2014 03:05:06 -0500 Subject: [PATCH 04/15] Fix Syntax Errors --- paython/gateways/authecheckdotnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py index 51f1b45..4d7389c 100644 --- a/paython/gateways/authecheckdotnet.py +++ b/paython/gateways/authecheckdotnet.py @@ -59,9 +59,9 @@ def transact(self, amount, echeck_type=None, bank_account=None, billing_info=Non super(AuthECheckDotNet, self).use_echeck(bank_account) #Set Conditionally Required Fields - if echeck_type == 'ARC' or echeck_type == 'BOC' + 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' + elif echeck_type == 'WEB' or echeck_type == 'TEL': super(AuthECheckDotNet, self).set(self.REQUEST_FIELDS['recurring_billing'], bank_account.recurring_billing) if billing_info: From 1b1c68459d6d58fd9391b94a3dda7acc0867a48b Mon Sep 17 00:00:00 2001 From: django Date: Mon, 17 Feb 2014 05:18:55 -0500 Subject: [PATCH 05/15] Error Fixes --- paython/gateways/authecheckdotnet.py | 4 ++-- paython/lib/api.py | 1 + paython/lib/echeck.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py index 4d7389c..cace07b 100644 --- a/paython/gateways/authecheckdotnet.py +++ b/paython/gateways/authecheckdotnet.py @@ -21,9 +21,9 @@ class AuthECheckDotNet(AuthorizeNet): 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=method.test,delim=delim) + debug=debug, test=test,delim=delim) # Update Fields to bubble up to Base Class - super(AuthECheckDotNet, self).__init__(translations=self.REQUEST_FIELDS, debug=debug) + super(AuthorizeNet, self).__init__(translations=self.REQUEST_FIELDS, debug=debug) def charge_setup(self): super(AuthECheckDotNet,self).charge_setup() diff --git a/paython/lib/api.py b/paython/lib/api.py index 75443a7..b43114b 100644 --- a/paython/lib/api.py +++ b/paython/lib/api.py @@ -169,6 +169,7 @@ def make_request(self, uri): GETs url with params - simple enough... string uri, string params """ try: + import pdb;pdb.set_trace() params = self.query_string() request = urllib.urlopen('%s%s' % (uri, params)) diff --git a/paython/lib/echeck.py b/paython/lib/echeck.py index 17b4d3e..d9a1d10 100644 --- a/paython/lib/echeck.py +++ b/paython/lib/echeck.py @@ -1,4 +1,5 @@ from paython.exceptions import DataValidationError +from paython.lib.utils import is_valid_aba class ECheck(object): """ @@ -40,7 +41,7 @@ def __repr__(self): """ string repr for debugging """ - return u', check_num: {0.check_num}'.format(self) From 73f8f714f3fa76253c85874620a577ce0dcc3516 Mon Sep 17 00:00:00 2001 From: django Date: Mon, 17 Feb 2014 05:31:35 -0500 Subject: [PATCH 06/15] Remove pdb --- paython/lib/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/paython/lib/api.py b/paython/lib/api.py index b43114b..75443a7 100644 --- a/paython/lib/api.py +++ b/paython/lib/api.py @@ -169,7 +169,6 @@ def make_request(self, uri): GETs url with params - simple enough... string uri, string params """ try: - import pdb;pdb.set_trace() params = self.query_string() request = urllib.urlopen('%s%s' % (uri, params)) From 9fd137947b022e63375367e418567de67bc58fc8 Mon Sep 17 00:00:00 2001 From: django Date: Mon, 17 Feb 2014 06:16:45 -0500 Subject: [PATCH 07/15] Display only last 4 digits of account number in logs --- paython/lib/echeck.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/paython/lib/echeck.py b/paython/lib/echeck.py index d9a1d10..89acc15 100644 --- a/paython/lib/echeck.py +++ b/paython/lib/echeck.py @@ -25,6 +25,23 @@ def __init__(self, aba_code, acct_num, acct_type, bank_name,first_name=None, las self.recurring_billing = recurring_billing self.strict = strict + + def __repr__(self): + """ + string repr for debugging + """ + return u', 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): """ @@ -37,14 +54,6 @@ def is_valid(self): else: return True - def __repr__(self): - """ - string repr for debugging - """ - return u', check_num: {0.check_num}'.format(self) - - def validate(self): """ validates All Codes using util functions From a2e6f363e58abf659f69d89aa4a9164ca34049af Mon Sep 17 00:00:00 2001 From: django Date: Wed, 19 Feb 2014 12:10:45 -0500 Subject: [PATCH 08/15] Small Refactoring --- paython/gateways/authecheckdotnet.py | 12 +++++------- paython/gateways/authorize_net.py | 1 - paython/lib/api.py | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py index cace07b..1a661db 100644 --- a/paython/gateways/authecheckdotnet.py +++ b/paython/gateways/authecheckdotnet.py @@ -24,21 +24,19 @@ def __init__(self, username='test', password='testpassword', debug=False, test=F 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 charge_setup(self): - super(AuthECheckDotNet,self).charge_setup() - """ Change Method to Echeck Instead of CC """ - super(AuthECheckDotNet, self).set('x_method', 'ECHECK') + def transact(self, amount, echeck_type=None, bank_account=None, billing_info=None, shipping_info=None, invoice_num=None): """ Sends Bank and Check details for authorization """ #set up transaction - self.charge_setup() - + 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') if not echeck_type: debug_string = "No Echeck Type Given" diff --git a/paython/gateways/authorize_net.py b/paython/gateways/authorize_net.py index 6ecf4d6..b530406 100644 --- a/paython/gateways/authorize_net.py +++ b/paython/gateways/authorize_net.py @@ -178,7 +178,6 @@ def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None, """ #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') diff --git a/paython/lib/api.py b/paython/lib/api.py index 75443a7..4795a0e 100644 --- a/paython/lib/api.py +++ b/paython/lib/api.py @@ -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') From dd0a1efe0b39e94b2a27ae9018a0fd9dd4f0e812 Mon Sep 17 00:00:00 2001 From: django Date: Thu, 20 Feb 2014 11:04:29 -0500 Subject: [PATCH 09/15] Conform to paython naming convention --- paython/gateways/authecheckdotnet.py | 50 +++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py index 1a661db..bdbd2b7 100644 --- a/paython/gateways/authecheckdotnet.py +++ b/paython/gateways/authecheckdotnet.py @@ -26,7 +26,54 @@ def __init__(self, username='test', password='testpassword', debug=False, test=F super(AuthorizeNet, self).__init__(translations=self.REQUEST_FIELDS, debug=debug) - def transact(self, amount, echeck_type=None, bank_account=None, billing_info=None, shipping_info=None, invoice_num=None): + def auth(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_ONLY') + 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) + + 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 """ @@ -37,6 +84,7 @@ def transact(self, amount, echeck_type=None, bank_account=None, billing_info=Non #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" From e3777ca7ec5582bf210e68a78cb92ab7f5535c14 Mon Sep 17 00:00:00 2001 From: django Date: Fri, 21 Feb 2014 10:25:23 -0500 Subject: [PATCH 10/15] 2 step checkout for stripe --- paython/gateways/stripe_com.py | 105 +++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/paython/gateways/stripe_com.py b/paython/gateways/stripe_com.py index 3226525..c005137 100644 --- a/paython/gateways/stripe_com.py +++ b/paython/gateways/stripe_com.py @@ -1,11 +1,13 @@ -import pdb import time +import logging try: import stripe except ImportError: raise Exception('Stripe library not found, please install requirements.txt') +logger = logging.getLogger(__name__) + class Stripe(object): """TODO needs docstring""" VERSION = 'v1' @@ -14,7 +16,7 @@ class Stripe(object): 'id':'trans_id', 'amount':'amount', 'cvv_response':'cvc_check', - 'avs_response':'address_line1_check', + 'avs_response':'address_line1_check', } debug = False test = False @@ -24,36 +26,78 @@ def __init__(self, username=None, api_key=None, debug=False): """ setting up object so we can run 2 different ways (live & debug) - we have username and api_key because other gateways use "username" + we have username and api_key because other gateways use "username" and we want to make it simple to change out gateways ;) """ self.stripe_api.api_key = username or api_key if debug: self.debug = True - debug_string = " paython.gateways.stripe.__init__() -- You're in debug mode" - print debug_string.center(80, '=') + debug_string = " paython.gateways.stripe.__init__() -- You're in debug mode" + 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): - if self.debug: # debugging is so gross - debug_string = " paython.gateways.stripe.parse() -- Sending charge " - print debug_string.center(80, '=') + debug_string = " paython.gateways.stripe.parse() -- Sending charge " + logger.debug(debug_string.center(80, '=')) - credit_card.validate() # validate the card first amount = int(float(amount)*100) # then change the amount to how stripe likes it start = time.time() # timing it @@ -94,11 +138,10 @@ def void(self, trans_id): raise NotImplementedError('Stripe does not support transaction voiding. Try credit().') def credit(self, amount, trans_id): - if self.debug: # debugging is so gross - debug_string = " paython.gateways.stripe.parse() -- Sending credit " - print debug_string.center(80, '=') + debug_string = " paython.gateways.stripe.parse() -- Sending credit " + logger.debug(debug_string.center(80, '=')) - amount = int(float(amount)*100) + amount = int(float(amount) * 100) start = time.time() # timing it try: ch = self.stripe_api.Charge.retrieve(trans_id) @@ -106,10 +149,10 @@ def credit(self, amount, trans_id): except Exception, e: response = {'failure_message':'Unable to refund: %s' % e} end = time.time() # done timing it - response_time = '%0.2f' % (end-start) + response_time = '%0.2f' % (end - start) else: end = time.time() # done timing it - response_time = '%0.2f' % (end-start) + response_time = '%0.2f' % (end - start) return self.parse(response, response_time) @@ -128,12 +171,10 @@ def parse(self, response, response_time): if hasattr(response, 'to_dict'): response = response.to_dict() - if self.debug: # debugging is so gross - debug_string = " paython.gateways.stripe.parse() -- Dict response: " - print debug_string.center(80, '=') - debug_string = "\n %s" % response - print debug_string - + debug_string = " paython.gateways.stripe.parse() -- Dict response: " + logger.debug(debug_string.center(80, '=')) + logger.debug("\n %s" % response) + new_response = {} # alright now lets stuff some info in here @@ -150,4 +191,4 @@ def parse(self, response, response_time): response[key] = '%.2f' % (float(response[key])/float(100)) new_response[self.RESPONSE_KEYS[key]] = response[key] - return new_response \ No newline at end of file + return new_response From 24ddbfd15e3f1a36f991659aaeab3c8a1fb14001 Mon Sep 17 00:00:00 2001 From: Shaumyadeep Chaudhuri Date: Tue, 25 Feb 2014 08:37:07 -0500 Subject: [PATCH 11/15] test cases --- paython/lib/echeck.py | 4 +--- tests/test_utils.py | 9 +++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/paython/lib/echeck.py b/paython/lib/echeck.py index 89acc15..f15cc43 100644 --- a/paython/lib/echeck.py +++ b/paython/lib/echeck.py @@ -5,7 +5,7 @@ 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, strict=False): + 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 """ @@ -23,8 +23,6 @@ def __init__(self, aba_code, acct_num, acct_type, bank_name,first_name=None, las self.bank_name = bank_name self.check_num = check_num self.recurring_billing = recurring_billing - - self.strict = strict def __repr__(self): """ diff --git a/tests/test_utils.py b/tests/test_utils.py index 97c9ea7..6ca94f3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ from paython.exceptions import GatewayError -from paython.lib.utils import parse_xml, is_valid_email +from paython.lib.utils import parse_xml, is_valid_email, is_valid_aba -from nose.tools import assert_equals, raises +from nose.tools import assert_equals, raises, assert_true, assert_false @raises(GatewayError) def test_parse_xml(): @@ -39,3 +39,8 @@ def test_append_to_root(): def test_valid_email(): """testing our email validation""" assert_equals(is_valid_email("lol@lol.com") is None, False) + +def test_valid_email(): + """testing ABA number validation""" + assert_true(is_valid_aba("789456124")) + assert_false(is_valid_aba("789456120")) From 4bec77da0507cd6c6f2ed0043641f26fa7f11a84 Mon Sep 17 00:00:00 2001 From: Shaumyadeep Chaudhuri Date: Tue, 25 Feb 2014 08:37:52 -0500 Subject: [PATCH 12/15] test cases --- tests/test_echeck.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/test_echeck.py diff --git a/tests/test_echeck.py b/tests/test_echeck.py new file mode 100644 index 0000000..b6a90b0 --- /dev/null +++ b/tests/test_echeck.py @@ -0,0 +1,60 @@ +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from paython.lib.echeck import ECheck +from paython.exceptions import DataValidationError + +from nose.tools import assert_equals, assert_false, assert_true, with_setup, raises + +# Initialize globals here so that pyflakes doesn't freak out about them. +TEST_ECHECKS = {} +TEST_ABAS = {} + +def setup(): + """setting up the test""" + + global TEST_CARDS + global TEST_ABAS + + TEST_ABAS = [ "123456789123", ] + +def teardown(): + """teardowning the test""" + pass + +@with_setup(setup, teardown) +@raises(DataValidationError) +def test_invalid(): + """test if a ABA Routing Number is invalid""" + echeck = ECheck( + aba_code = "011000014", # invalid ABA CODE + acct_num = "123456789123", + acct_type = "Checking", + bank_name = "bank1", + first_name = "John", + last_name = "Doe", + ) + + # safe check for validity + assert_false(echeck.is_valid()) + + # checking if the exception fires + echeck.validate() + +@with_setup(setup, teardown) +def test_valid(): + """test if a ABA routing number is valid""" + for test_echeck_aba in TEST_ABAS: + # create a credit card object + echeck = ECheck( + aba_code = "011000015", + acct_num = "123456789123", + acct_type = "Checking", + bank_name = "bank1", + first_name = "John", + last_name = "Doe", + ) + + # safe check + assert_true(echeck.is_valid()) + From 77de3799dc8348c06e0f7bd986f94c08a2bb22f6 Mon Sep 17 00:00:00 2001 From: Shaumyadeep Chaudhuri Date: Fri, 7 Mar 2014 15:06:24 -0500 Subject: [PATCH 13/15] set method to cc for authorize.net CC --- paython/gateways/authorize_net.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paython/gateways/authorize_net.py b/paython/gateways/authorize_net.py index b530406..4a71242 100644 --- a/paython/gateways/authorize_net.py +++ b/paython/gateways/authorize_net.py @@ -169,6 +169,7 @@ 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, '=')) From 6f6eefd2b7fa2bec2fd9b2d2383f34b6f69c08fd Mon Sep 17 00:00:00 2001 From: django Date: Fri, 30 Oct 2015 13:30:12 -0400 Subject: [PATCH 14/15] passing customer ip to auth.net calls --- paython/gateways/authecheckdotnet.py | 4 +++- paython/gateways/authorize_net.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/paython/gateways/authecheckdotnet.py b/paython/gateways/authecheckdotnet.py index bdbd2b7..980bff3 100644 --- a/paython/gateways/authecheckdotnet.py +++ b/paython/gateways/authecheckdotnet.py @@ -26,7 +26,7 @@ def __init__(self, username='test', password='testpassword', debug=False, test=F 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): + 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 """ @@ -38,6 +38,8 @@ def auth(self, amount, echeck_type=None, bank_account=None, billing_info=None, s 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" diff --git a/paython/gateways/authorize_net.py b/paython/gateways/authorize_net.py index 4a71242..302b9d5 100644 --- a/paython/gateways/authorize_net.py +++ b/paython/gateways/authorize_net.py @@ -173,7 +173,7 @@ def charge_setup(self): 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, duplicate_window=120): + 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 """ @@ -183,6 +183,8 @@ def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None, 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) From f56c281f3cc9157191b4769ec76d3f9ec67065eb Mon Sep 17 00:00:00 2001 From: drawingboardmedia Date: Mon, 27 Jun 2016 16:16:38 -0400 Subject: [PATCH 15/15] Changing auth.net gateway from GET to POST --- paython/gateways/authorize_net.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paython/gateways/authorize_net.py b/paython/gateways/authorize_net.py index 302b9d5..f93c81d 100644 --- a/paython/gateways/authorize_net.py +++ b/paython/gateways/authorize_net.py @@ -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 = ';' @@ -318,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)