From 73910ae8a0d8c9c06e250308ff9cd1b3d7fc5d07 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 2 Oct 2012 23:49:19 -0400 Subject: [PATCH] To get credit module working again, had to move some methods into an object that inherits from a base class So now all payment processor specific methods are on a Processor object, --- payment/baseprocessor.py | 243 +++++++++++++++++---------------- payment/credit.py | 50 +++---- payment/manager.py | 41 +++--- payment/models.py | 4 +- payment/stripelib.py | 280 +++++++++++++++++++-------------------- 5 files changed, 303 insertions(+), 315 deletions(-) diff --git a/payment/baseprocessor.py b/payment/baseprocessor.py index ff48ac9c..b166a8c5 100644 --- a/payment/baseprocessor.py +++ b/payment/baseprocessor.py @@ -8,18 +8,6 @@ import datetime import time -def requires_explicit_preapprovals(): - """a function that returns for the given payment processor""" - return False - -def make_account(user, token): - """template function for return a payment.Account corresponding to the payment system""" - return None - -def ProcessIPN(request): - return HttpResponseForbidden() - - class BasePaymentRequest: ''' Handles common information incident to payment processing @@ -71,120 +59,131 @@ class BasePaymentRequest: def timestamp(self): return str(datetime.datetime.now()) +class Processor: + """a function that returns for the given payment processor""" + requires_explicit_preapprovals=False -class Pay( BasePaymentRequest ): - - ''' - The pay function generates a redirect URL to approve the transaction - ''' + def make_account(self, user, token): + """template function for return a payment.Account corresponding to the payment system""" + return None - def __init__( self, transaction, return_url=None, amount=None, paymentReason=""): - self.transaction=transaction - - def api(self): - return "null api" - - def exec_status( self ): - return None - - def amount( self ): - return None - - def key( self ): - return None - - def next_url( self ): - return self.url - -class Preapproval(Pay): - - def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): - - # set the expiration date for the preapproval if not passed in. This is what the paypal library does - now_val = now() - if expiry is None: - expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) - transaction.date_authorized = now_val - transaction.date_expired = expiry - transaction.save() - - # Call into our parent class - Pay.__init__(self, transaction, return_url=return_url, amount=amount, paymentReason=paymentReason) - - -class Execute(BasePaymentRequest): - - ''' - The Execute function sends an existing token(generated via the URL from the pay operation), and collects - the money. - ''' - - def __init__(self, transaction=None): - self.transaction = transaction - - def api(self): - return "Base Pay" - - def key(self): - # IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key - return self.transaction.pay_key - - - -class Finish(BasePaymentRequest): - ''' - The Finish function handles the secondary receiver in a chained payment. - ''' - def __init__(self, transaction): + def ProcessIPN(self, request): + return HttpResponseForbidden() - print "Finish" - - -class PaymentDetails(BasePaymentRequest): - ''' - Get details about executed PAY operation - - This api must set the following class variables to work with the code in manager.py - - status - one of the global transaction status codes - transactions -- Not supported for amazon, used by paypal - - ''' - def __init__(self, transaction=None): - self.transaction = transaction + + class Pay( BasePaymentRequest ): + + ''' + The pay function generates a redirect URL to approve the transaction + ''' + + def __init__( self, transaction, return_url=None, amount=None, paymentReason=""): + self.transaction=transaction - - -class CancelPreapproval(BasePaymentRequest): - ''' - Cancels an exisiting token. - ''' + def api(self): + return "null api" + + def exec_status( self ): + return None + + def amount( self ): + return None + + def key( self ): + return None - def __init__(self, transaction): - self.transaction = transaction - - -class RefundPayment(BasePaymentRequest): + def next_url( self ): + return self.url + + class Preapproval(Pay): + + def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): + + # set the expiration date for the preapproval if not passed in. This is what the paypal library does + now_val = now() + if expiry is None: + expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) + transaction.date_authorized = now_val + transaction.date_expired = expiry + transaction.save() + + # Call into our parent class + Pay.__init__(self, transaction, return_url=return_url, amount=amount, paymentReason=paymentReason) + + + class Execute(BasePaymentRequest): + + ''' + The Execute function sends an existing token(generated via the URL from the pay operation), and collects + the money. + ''' + + def __init__(self, transaction=None): + self.transaction = transaction + + def api(self): + return "Base Pay" + + def key(self): + # IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key + return self.transaction.pay_key + + - def __init__(self, transaction): - self.transaction = transaction + class Finish(BasePaymentRequest): + ''' + The Finish function handles the secondary receiver in a chained payment. + ''' + def __init__(self, transaction): - - - - -class PreapprovalDetails(BasePaymentRequest): - ''' - Get details about an authorized token - - This api must set 4 different class variables to work with the code in manager.py - - status - one of the global transaction status codes - approved - boolean value - currency - not used in this API, but we can get some more info via other APIs - TODO - amount - not used in this API, but we can get some more info via other APIs - TODO - - ''' - def __init__(self, transaction=None): - self.transaction = transaction - \ No newline at end of file + print "Finish" + + + class PaymentDetails(BasePaymentRequest): + ''' + Get details about executed PAY operation + + This api must set the following class variables to work with the code in manager.py + + status - one of the global transaction status codes + transactions -- Not supported for amazon, used by paypal + + ''' + def __init__(self, transaction=None): + self.transaction = transaction + + + + class CancelPreapproval(BasePaymentRequest): + ''' + Cancels an exisiting token. + ''' + + def __init__(self, transaction): + self.transaction = transaction + + + class RefundPayment(BasePaymentRequest): + + def __init__(self, transaction): + self.transaction = transaction + + + + + + class PreapprovalDetails(BasePaymentRequest): + ''' + Get details about an authorized token + + This api must set 4 different class variables to work with the code in manager.py + + status - one of the global transaction status codes + approved - boolean value + currency - not used in this API, but we can get some more info via other APIs - TODO + amount - not used in this API, but we can get some more info via other APIs - TODO + + ''' + def __init__(self, transaction=None): + self.transaction = transaction + \ No newline at end of file diff --git a/payment/credit.py b/payment/credit.py index 4976089d..c6cf52ec 100644 --- a/payment/credit.py +++ b/payment/credit.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User from django.conf import settings from regluit.payment.parameters import * +from regluit.payment import baseprocessor from regluit.payment.baseprocessor import BasePaymentRequest @@ -28,28 +29,29 @@ def credit_transaction(t,user,amount): user.credit.add_to_pledged(pledge_amount) t.set_credit_approved(pledge_amount) -class CancelPreapproval(BasePaymentRequest): - ''' - Cancels an exisiting token. - ''' +class Processor(baseprocessor.Processor): + class CancelPreapproval(BasePaymentRequest): + ''' + Cancels an exisiting token. + ''' + + def __init__(self, transaction): + self.transaction = transaction + if transaction.user.credit.add_to_pledged(-transaction.amount): + #success + transaction.status=TRANSACTION_STATUS_CANCELED + transaction.save() + else: + self.errorMessage="couldn't cancel the transaction" + self.status = 'Credit Cancel Failure' - def __init__(self, transaction): - self.transaction = transaction - if transaction.user.credit.add_to_pledged(-transaction.amount): - #success - transaction.status=TRANSACTION_STATUS_CANCELED - transaction.save() - else: - self.errorMessage="couldn't cancel the transaction" - self.status = 'Credit Cancel Failure' - -class PreapprovalDetails(BasePaymentRequest): - status = None - approved = None - currency = None - amount = None - def __init__(self, transaction): - self.status = transaction.status - self.approved = transaction.approved - self.currency = transaction.currency - self.amount = transaction.amount + class PreapprovalDetails(BasePaymentRequest): + status = None + approved = None + currency = None + amount = None + def __init__(self, transaction): + self.status = transaction.status + self.approved = transaction.approved + self.currency = transaction.currency + self.amount = transaction.amount diff --git a/payment/manager.py b/payment/manager.py index 5ee9e718..d2f1189b 100644 --- a/payment/manager.py +++ b/payment/manager.py @@ -6,8 +6,6 @@ from regluit.payment.parameters import * from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created from regluit.payment import credit -from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment - import uuid import traceback from regluit.utils.localdatetime import now @@ -39,14 +37,13 @@ class PaymentManager( object ): # Forward to our payment processor mod = __import__("regluit.payment." + module, fromlist=[str(module)]) - method = getattr(mod, "ProcessIPN") + method = getattr(mod.Processor, "ProcessIPN") return method(request) def update_preapproval(self, transaction): """Update a transaction to hold the data from a PreapprovalDetails on that transaction""" t = transaction - method = getattr(transaction.get_payment_class(), "PreapprovalDetails") - p = method(t) + p = transaction.get_payment_class().PreapprovalDetails(t) preapproval_status = {'id':t.id, 'key':t.preapproval_key} @@ -98,8 +95,7 @@ class PaymentManager( object ): t = transaction payment_status = {'id':t.id} - method = getattr(transaction.get_payment_class(), "PaymentDetails") - p = method(t) + p = transaction.get_payment_class().PaymentDetails(t) if p.error() or not p.success(): logger.info("Error retrieving payment details for transaction %d" % t.id) @@ -413,8 +409,7 @@ class PaymentManager( object ): transaction.date_executed = now() transaction.save() - method = getattr(transaction.get_payment_class(), "Finish") - p = method(transaction) + p = transaction.get_payment_class().Finish(transaction) # Create a response for this envelope = p.envelope() @@ -468,8 +463,7 @@ class PaymentManager( object ): transaction.date_payment = now() transaction.save() - method = getattr(transaction.get_payment_class(), "Execute") - p = method(transaction) + p = transaction.get_payment_class().Execute(transaction) # Create a response for this envelope = p.envelope() @@ -509,12 +503,11 @@ class PaymentManager( object ): ''' # does this transaction explicity require preapprovals? - requires_explicit_preapprovals = getattr(transaction.get_payment_class(), "requires_explicit_preapprovals") + requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals - if requires_explicit_preapprovals(): + if requires_explicit_preapprovals: - method = getattr(transaction.get_payment_class(), "CancelPreapproval") - p = method(transaction) + p = transaction.get_payment_class().CancelPreapproval(transaction) # Create a response for this envelope = p.envelope() @@ -575,8 +568,7 @@ class PaymentManager( object ): urllib.urlencode({'tid':transaction.id})) return_url = urlparse.urljoin(settings.BASE_URL, return_path) - method = getattr(transaction.get_payment_class(), "Preapproval") - p = method(transaction, transaction.amount, expiry, return_url=return_url, paymentReason=paymentReason) + p = transaction.get_payment_class().Preapproval(transaction, transaction.amount, expiry, return_url=return_url, paymentReason=paymentReason) # Create a response for this envelope = p.envelope() @@ -744,7 +736,7 @@ class PaymentManager( object ): # does this transaction explicity require preapprovals? - requires_explicit_preapprovals = getattr(transaction.get_payment_class(), "requires_explicit_preapprovals") + requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals if transaction.type != PAYMENT_TYPE_AUTHORIZATION: logger.info("Error, attempt to modify an invalid transaction type") @@ -785,7 +777,7 @@ class PaymentManager( object ): credit.CancelPreapproval(transaction) return t, reverse('fund_pledge', args=[t.id]) - elif requires_explicit_preapprovals() and (amount > transaction.max_amount or expiry != transaction.date_expired): + elif requires_explicit_preapprovals and (amount > transaction.max_amount or expiry != transaction.date_expired): # set the expiry date based on the campaign deadline expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) @@ -828,7 +820,7 @@ class PaymentManager( object ): # corresponding notification to the user? that would go here. return False, None - elif (requires_explicit_preapprovals() and amount <= transaction.max_amount) or (not requires_explicit_preapprovals()): + elif (requires_explicit_preapprovals and amount <= transaction.max_amount) or (not requires_explicit_preapprovals): # Update transaction but leave the preapproval alone transaction.amount = amount transaction.set_pledge_extra(pledge_extra) @@ -861,8 +853,7 @@ class PaymentManager( object ): logger.info("Refund Transaction failed, invalid transaction status") return False - method = getattr(transaction.get_payment_class(), "RefundPayment") - p = method(transaction) + p = transaction.get_payment_class().RefundPayment(transaction) # Create a response for this envelope = p.envelope() @@ -920,8 +911,7 @@ class PaymentManager( object ): t.date_payment=now() t.execution=EXECUTE_TYPE_CHAINED_INSTANT t.type=PAYMENT_TYPE_INSTANT - method = getattr(t.get_payment_class(), "Pay") - p = method(t,return_url=return_url, nevermind_url=nevermind_url) + p = t.get_payment_class().Pay(t,return_url=return_url, nevermind_url=nevermind_url) # Create a response for this envelope = p.envelope() @@ -954,8 +944,7 @@ class PaymentManager( object ): """delegate to a specific payment module the task of creating a payment account""" mod = __import__("regluit.payment." + host, fromlist=[host]) - method = getattr(mod, "make_account") - return method(user, token) + return mod.Processor().make_account(user, token) diff --git a/payment/models.py b/payment/models.py index 8526c6ce..e8310e37 100644 --- a/payment/models.py +++ b/payment/models.py @@ -109,13 +109,13 @@ class Transaction(models.Model): def get_payment_class(self): ''' - Returns the specific payment module that implements this transaction + Returns the specific payment processor that implements this transaction ''' if self.host == PAYMENT_HOST_NONE: return None else: mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)]) - return mod + return mod.Processor() def set_credit_approved(self, amount): self.amount=amount diff --git a/payment/stripelib.py b/payment/stripelib.py index 1f882627..87050e7a 100644 --- a/payment/stripelib.py +++ b/payment/stripelib.py @@ -265,155 +265,153 @@ class StripePaymentRequest(baseprocessor.BasePaymentRequest): """so far there is no need to have a separate class here""" pass -def requires_explicit_preapprovals(): - """a function that returns for the given payment processor""" - return False - -def make_account(user, token): - """returns a payment.models.Account based on stripe token and user""" +class Processor(baseprocessor.Processor): - sc = StripeClient() - - # create customer and charge id and then charge the customer - customer = sc.create_customer(card=token, description=user.username, - email=user.email) - - account = Account(host = PAYMENT_HOST_STRIPE, - account_id = customer.id, - card_last4 = customer.active_card.last4, - card_type = customer.active_card.type, - card_exp_month = customer.active_card.exp_month, - card_exp_year = customer.active_card.exp_year, - card_fingerprint = customer.active_card.fingerprint, - card_country = customer.active_card.country, - user = user - ) - - account.save() - - return account - -class Pay(StripePaymentRequest, baseprocessor.Pay): - pass - -class Preapproval(StripePaymentRequest, baseprocessor.Preapproval): - - def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): - - # set the expiration date for the preapproval if not passed in. This is what the paypal library does - - self.transaction = transaction - - now_val = now() - if expiry is None: - expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) - transaction.date_authorized = now_val - transaction.date_expired = expiry - - sc = StripeClient() - - # let's figure out what part of transaction can be used to store info - # try placing charge id in transaction.pay_key - # need to set amount - # how does transaction.max_amount get set? -- coming from /pledge/xxx/ -> manager.process_transaction - # max_amount is set -- but I don't think we need it for stripe - - # ASSUMPTION: a user has any given moment one and only one active payment Account - - if transaction.user.account_set.filter(date_deactivated__isnull=True).count() > 1: - logger.warning("user {0} has more than one active payment account".format(transaction.user)) - elif transaction.user.account_set.filter(date_deactivated__isnull=True).count() == 0: - logger.warning("user {0} has no active payment account".format(transaction.user)) - raise StripeError("user {0} has no active payment account".format(transaction.user)) - - account = transaction.user.account_set.filter(date_deactivated__isnull=True)[0] - logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id)) - - # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE - # should approved be set to False and wait for a webhook? - transaction.approved = True - transaction.type = PAYMENT_TYPE_AUTHORIZATION - transaction.host = PAYMENT_HOST_STRIPE - transaction.status = TRANSACTION_STATUS_ACTIVE - - transaction.preapproval_key = account.account_id - - transaction.currency = 'USD' - transaction.amount = amount - - transaction.save() - - def key(self): - return self.transaction.preapproval_key - - def next_url(self): - """return None because no redirection to stripe is required""" - return None - - -class Execute(StripePaymentRequest): - - ''' - The Execute function sends an existing token(generated via the URL from the pay operation), and collects - the money. - ''' - - def __init__(self, transaction=None): - self.transaction = transaction - - # execute transaction - assert transaction.host == PAYMENT_HOST_STRIPE + def make_account(self, user, token): + """returns a payment.models.Account based on stripe token and user""" sc = StripeClient() - # look at transaction.preapproval_key - # is it a customer or a token? - - # BUGBUG: replace description with somethin more useful - if transaction.preapproval_key.startswith('cus_'): - charge = sc.create_charge(transaction.amount, customer=transaction.preapproval_key, description="${0} for test / retain cc".format(transaction.amount)) - elif transaction.preapproval_key.startswith('tok_'): - charge = sc.create_charge(transaction.amount, card=transaction.preapproval_key, description="${0} for test / cc not retained".format(transaction.amount)) - - transaction.status = TRANSACTION_STATUS_COMPLETE - transaction.pay_key = charge.id - transaction.date_payment = now() - transaction.save() + # create customer and charge id and then charge the customer + customer = sc.create_customer(card=token, description=user.username, + email=user.email) - self.charge = charge - - def api(self): - return "Base Pay" + account = Account(host = PAYMENT_HOST_STRIPE, + account_id = customer.id, + card_last4 = customer.active_card.last4, + card_type = customer.active_card.type, + card_exp_month = customer.active_card.exp_month, + card_exp_year = customer.active_card.exp_year, + card_fingerprint = customer.active_card.fingerprint, + card_country = customer.active_card.country, + user = user + ) - def key(self): - # IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key - return self.transaction.pay_key + account.save() + + return account + class Pay(StripePaymentRequest, baseprocessor.Processor.Pay): + pass + + class Preapproval(StripePaymentRequest, baseprocessor.Processor.Preapproval): + + def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): + + # set the expiration date for the preapproval if not passed in. This is what the paypal library does + + self.transaction = transaction + + now_val = now() + if expiry is None: + expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) + transaction.date_authorized = now_val + transaction.date_expired = expiry + + sc = StripeClient() + + # let's figure out what part of transaction can be used to store info + # try placing charge id in transaction.pay_key + # need to set amount + # how does transaction.max_amount get set? -- coming from /pledge/xxx/ -> manager.process_transaction + # max_amount is set -- but I don't think we need it for stripe -class PreapprovalDetails(StripePaymentRequest): - ''' - Get details about an authorized token - - This api must set 4 different class variables to work with the code in manager.py - - status - one of the global transaction status codes - approved - boolean value - currency - not used in this API, but we can get some more info via other APIs - TODO - amount - not used in this API, but we can get some more info via other APIs - TODO - - ''' - def __init__(self, transaction): - - self.transaction = transaction - self.status = self.transaction.status - if self.status == TRANSACTION_STATUS_CANCELED: - self.approved = False - else: - self.approved = True - - # Set the other fields that are expected. We don't have values for these now, so just copy the transaction - self.currency = transaction.currency - self.amount = transaction.amount + # ASSUMPTION: a user has any given moment one and only one active payment Account + + if transaction.user.account_set.filter(date_deactivated__isnull=True).count() > 1: + logger.warning("user {0} has more than one active payment account".format(transaction.user)) + elif transaction.user.account_set.filter(date_deactivated__isnull=True).count() == 0: + logger.warning("user {0} has no active payment account".format(transaction.user)) + raise StripeError("user {0} has no active payment account".format(transaction.user)) + + account = transaction.user.account_set.filter(date_deactivated__isnull=True)[0] + logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id)) + + # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE + # should approved be set to False and wait for a webhook? + transaction.approved = True + transaction.type = PAYMENT_TYPE_AUTHORIZATION + transaction.host = PAYMENT_HOST_STRIPE + transaction.status = TRANSACTION_STATUS_ACTIVE + + transaction.preapproval_key = account.account_id + + transaction.currency = 'USD' + transaction.amount = amount + + transaction.save() + + def key(self): + return self.transaction.preapproval_key + + def next_url(self): + """return None because no redirection to stripe is required""" + return None + + + class Execute(StripePaymentRequest): + + ''' + The Execute function sends an existing token(generated via the URL from the pay operation), and collects + the money. + ''' + + def __init__(self, transaction=None): + self.transaction = transaction + + # execute transaction + assert transaction.host == PAYMENT_HOST_STRIPE + + sc = StripeClient() + + # look at transaction.preapproval_key + # is it a customer or a token? + + # BUGBUG: replace description with somethin more useful + if transaction.preapproval_key.startswith('cus_'): + charge = sc.create_charge(transaction.amount, customer=transaction.preapproval_key, description="${0} for test / retain cc".format(transaction.amount)) + elif transaction.preapproval_key.startswith('tok_'): + charge = sc.create_charge(transaction.amount, card=transaction.preapproval_key, description="${0} for test / cc not retained".format(transaction.amount)) + + transaction.status = TRANSACTION_STATUS_COMPLETE + transaction.pay_key = charge.id + transaction.date_payment = now() + transaction.save() + + self.charge = charge + + def api(self): + return "Base Pay" + + def key(self): + # IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key + return self.transaction.pay_key + + + class PreapprovalDetails(StripePaymentRequest): + ''' + Get details about an authorized token + + This api must set 4 different class variables to work with the code in manager.py + + status - one of the global transaction status codes + approved - boolean value + currency - not used in this API, but we can get some more info via other APIs - TODO + amount - not used in this API, but we can get some more info via other APIs - TODO + + ''' + def __init__(self, transaction): + + self.transaction = transaction + self.status = self.transaction.status + if self.status == TRANSACTION_STATUS_CANCELED: + self.approved = False + else: + self.approved = True + + # Set the other fields that are expected. We don't have values for these now, so just copy the transaction + self.currency = transaction.currency + self.amount = transaction.amount def suite():