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,
pull/1/head
eric 2012-10-02 23:49:19 -04:00
parent 7a1cf969ca
commit 73910ae8a0
5 changed files with 303 additions and 315 deletions

View File

@ -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
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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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():