Merge pull request #190 from Gluejar/instant_charge
Instant charge: implementing PaymentManager.chargepull/1/head
commit
7c013ef93b
|
@ -96,6 +96,8 @@ class Processor:
|
|||
|
||||
def __init__( self, transaction, return_url=None, amount=None, paymentReason=""):
|
||||
self.transaction=transaction
|
||||
self.return_url = return_url
|
||||
self.amount = amount
|
||||
|
||||
def api(self):
|
||||
return "null api"
|
||||
|
@ -104,13 +106,13 @@ class Processor:
|
|||
return None
|
||||
|
||||
def amount( self ):
|
||||
return None
|
||||
return self.amount
|
||||
|
||||
def key( self ):
|
||||
return None
|
||||
|
||||
def next_url( self ):
|
||||
return self.url
|
||||
return self.return_url
|
||||
|
||||
class Preapproval(Pay):
|
||||
|
||||
|
|
|
@ -554,8 +554,8 @@ class PaymentManager( object ):
|
|||
|
||||
authorizes a set amount of money to be collected at a later date
|
||||
|
||||
return_url: url to redirect supporter to after a successful PayPal transaction
|
||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||
return_url: url to redirect supporter to after a successful transaction
|
||||
paymentReason: a memo line that will show up in the unglue.it accounting
|
||||
modification: whether this authorize call is part of a modification of an existing pledge
|
||||
|
||||
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||
|
@ -626,6 +626,73 @@ class PaymentManager( object ):
|
|||
transaction.save()
|
||||
logger.info("Authorize Error: " + p.error_string())
|
||||
return transaction, None
|
||||
|
||||
def charge(self, transaction, return_url=None, paymentReason="unglue.it Purchase"):
|
||||
'''
|
||||
charge
|
||||
|
||||
immediately attempt to collect on transaction
|
||||
|
||||
return_url: url to redirect supporter to after a successful transaction
|
||||
paymentReason: a memo line that will show up in our stripe accounting
|
||||
|
||||
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||
the redirect url will be None
|
||||
|
||||
'''
|
||||
|
||||
if transaction.host == PAYMENT_HOST_NONE:
|
||||
#TODO send user to select a payment processor -- for now, set to a system setting
|
||||
transaction.host = settings.PAYMENT_PROCESSOR
|
||||
|
||||
# we might want to not allow for a return_url to be passed in but calculated
|
||||
# here because we have immediate access to the Transaction object.
|
||||
|
||||
#if return_url is None:
|
||||
#
|
||||
# #return_path = "{0}?{1}".format(reverse('charge_complete'),
|
||||
# # urllib.urlencode({'tid':transaction.id}))
|
||||
# return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||
# urllib.urlencode({'tid':transaction.id}))
|
||||
# return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path)
|
||||
|
||||
# Question: do I need to set transaction.amount = transaction.max_amount ?
|
||||
p = transaction.get_payment_class().Pay(transaction, amount=transaction.max_amount, return_url=return_url, paymentReason=paymentReason)
|
||||
|
||||
# Create a response for this
|
||||
#envelope = p.envelope()
|
||||
#
|
||||
#if envelope:
|
||||
# r = PaymentResponse.objects.create(api=p.url,
|
||||
# correlation_id = p.correlation_id(),
|
||||
# timestamp = p.timestamp(),
|
||||
# info = p.raw_response,
|
||||
# transaction=transaction)
|
||||
|
||||
if p.success() and not p.error():
|
||||
transaction.preapproval_key = p.key()
|
||||
transaction.save()
|
||||
|
||||
# it make sense for the payment processor library to calculate next_url when
|
||||
# user is redirected there. But if no redirection is required, send user
|
||||
# straight on to the return_url
|
||||
url = p.next_url()
|
||||
|
||||
if url is None:
|
||||
url = return_url
|
||||
|
||||
logger.info("Pay Success: " + url if url is not None else '')
|
||||
|
||||
return transaction, url
|
||||
|
||||
|
||||
else:
|
||||
transaction.error = p.error_string()
|
||||
transaction.save()
|
||||
logger.info("Pay Error: " + p.error_string())
|
||||
return transaction, None
|
||||
|
||||
|
||||
|
||||
def process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None, user=None,
|
||||
return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None,
|
||||
|
|
|
@ -31,6 +31,7 @@ from regluit.payment.parameters import (
|
|||
TRANSACTION_STATUS_COMPLETE,
|
||||
TRANSACTION_STATUS_ERROR,
|
||||
PAYMENT_TYPE_AUTHORIZATION,
|
||||
PAYMENT_TYPE_INSTANT,
|
||||
TRANSACTION_STATUS_CANCELED
|
||||
)
|
||||
from regluit.payment.signals import transaction_charged, transaction_failed
|
||||
|
@ -522,7 +523,7 @@ class PledgeScenarioTest(TestCase):
|
|||
|
||||
def test_charge_good_cust(self):
|
||||
charge = self._sc.create_charge(10, customer=self._good_cust.id, description="$10 for good cust")
|
||||
self.assertEqual(type(charge.id), str)
|
||||
self.assertEqual(type(charge.id), unicode)
|
||||
|
||||
# print out all the pieces of Customer and Charge objects
|
||||
print dir(charge)
|
||||
|
@ -542,17 +543,19 @@ class PledgeScenarioTest(TestCase):
|
|||
def tearDownClass(cls):
|
||||
# clean up stuff we create in test -- right now list current objects
|
||||
|
||||
pass
|
||||
|
||||
#cls._good_cust.delete()
|
||||
|
||||
print "list of customers"
|
||||
print [(i, c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent, c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year, c.active_card.country) for(i, c) in enumerate(cls._sc.customer.all()["data"])]
|
||||
|
||||
print "list of charges"
|
||||
print [(i, c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid, c.fee, c.disputed, c.amount_refunded, c.failure_message, c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year) for (i, c) in enumerate(cls._sc.charge.all()['data'])]
|
||||
|
||||
# can retrieve events since a certain time?
|
||||
print "list of events", cls._sc.event.all()
|
||||
print [(i, e.id, e.type, e.created, e.pending_webhooks, e.data) for (i,e) in enumerate(cls._sc.event.all()['data'])]
|
||||
#print "list of customers"
|
||||
#print [(i, c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent, c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year, c.active_card.country) for(i, c) in enumerate(cls._sc.customer.all()["data"])]
|
||||
#
|
||||
#print "list of charges"
|
||||
#print [(i, c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid, c.fee, c.disputed, c.amount_refunded, c.failure_message, c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year) for (i, c) in enumerate(cls._sc.charge.all()['data'])]
|
||||
#
|
||||
## can retrieve events since a certain time?
|
||||
#print "list of events", cls._sc.event.all()
|
||||
#print [(i, e.id, e.type, e.created, e.pending_webhooks, e.data) for (i,e) in enumerate(cls._sc.event.all()['data'])]
|
||||
|
||||
class StripePaymentRequest(baseprocessor.BasePaymentRequest):
|
||||
"""so far there is no need to have a separate class here"""
|
||||
|
@ -647,19 +650,51 @@ class Processor(baseprocessor.Processor):
|
|||
'''
|
||||
|
||||
def __init__( self, transaction, return_url=None, amount=None, paymentReason=""):
|
||||
self.transaction=transaction
|
||||
self.transaction=transaction
|
||||
self.url = return_url
|
||||
|
||||
#def api(self):
|
||||
# return "null api"
|
||||
#
|
||||
##def exec_status( self ):
|
||||
# return None
|
||||
now_val = now()
|
||||
transaction.date_authorized = now_val
|
||||
|
||||
# ASSUMPTION: a user has any given moment one and only one active payment Account
|
||||
|
||||
account = transaction.user.profile.account
|
||||
|
||||
if not account:
|
||||
logger.warning("user {0} has no active payment account".format(transaction.user))
|
||||
raise StripelibError("user {0} has no active payment account".format(transaction.user))
|
||||
|
||||
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_INSTANT
|
||||
transaction.host = PAYMENT_HOST_STRIPE
|
||||
|
||||
transaction.preapproval_key = account.account_id
|
||||
|
||||
transaction.currency = 'USD'
|
||||
transaction.amount = amount
|
||||
|
||||
transaction.save()
|
||||
|
||||
# execute the transaction
|
||||
p = transaction.get_payment_class().Execute(transaction)
|
||||
|
||||
if p.success() and not p.error():
|
||||
transaction.pay_key = p.key()
|
||||
transaction.save()
|
||||
else:
|
||||
transaction.error = p.error_string()
|
||||
transaction.save()
|
||||
logger.info("execute_transaction Error: " + p.error_string())
|
||||
|
||||
def amount( self ):
|
||||
return self.transaction.amount
|
||||
|
||||
def key( self ):
|
||||
return None
|
||||
return self.transaction.pay_key
|
||||
|
||||
def next_url( self ):
|
||||
return self.url
|
||||
|
@ -740,7 +775,6 @@ class Processor(baseprocessor.Processor):
|
|||
# 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
|
||||
|
@ -874,8 +908,8 @@ class Processor(baseprocessor.Processor):
|
|||
|
||||
def suite():
|
||||
|
||||
#testcases = [PledgeScenarioTest, StripeErrorTest]
|
||||
testcases = [StripeErrorTest]
|
||||
testcases = [PledgeScenarioTest, StripeErrorTest]
|
||||
#testcases = [StripeErrorTest]
|
||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||
#suites.addTest(LibraryThingTest('test_cache'))
|
||||
#suites.addTest(SettingsTest('test_dev_me_alignment')) # give option to test this alignment
|
||||
|
|
|
@ -297,6 +297,49 @@ class TransactionTest(TestCase):
|
|||
self.assertEqual(results[0].amount, D('12.34'))
|
||||
self.assertEqual(c.left,c.target-D('12.34'))
|
||||
self.assertEqual(c.supporters_count, 1)
|
||||
|
||||
def test_paymentmanager_charge(self):
|
||||
"""
|
||||
test regluit.payment.manager.PaymentManager.charge
|
||||
|
||||
trying to simulate the conditions of having a bare transaction setup before we try to do
|
||||
an instant charge.
|
||||
|
||||
"""
|
||||
user = User.objects.create_user('pm_charge', 'support2@example.org', 'payment_test')
|
||||
# need to create an Account to associate with user
|
||||
from regluit.payment.stripelib import StripeClient, card, Processor
|
||||
|
||||
sc = StripeClient()
|
||||
|
||||
# valid card and Account
|
||||
card0 = card()
|
||||
stripe_processor = Processor()
|
||||
account = stripe_processor.make_account(user,token=card0)
|
||||
|
||||
w = Work()
|
||||
w.save()
|
||||
|
||||
c = Campaign(target=D('1000.00'),deadline=now() + timedelta(days=180),work=w)
|
||||
c.save()
|
||||
|
||||
t = Transaction(host='stripelib')
|
||||
|
||||
t.max_amount = D('12.34')
|
||||
t.type = PAYMENT_TYPE_NONE
|
||||
t.status = TRANSACTION_STATUS_NONE
|
||||
t.campaign = c
|
||||
t.approved = False
|
||||
t.user = user
|
||||
|
||||
t.save()
|
||||
|
||||
pm = PaymentManager()
|
||||
response = pm.charge(t)
|
||||
|
||||
self.assertEqual(t.status, TRANSACTION_STATUS_COMPLETE)
|
||||
self.assertEqual(t.type, EXECUTE_TYPE_CHAINED_INSTANT)
|
||||
self.assertEqual(t.amount, D('12.34'))
|
||||
|
||||
class BasicGuiTest(TestCase):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue