pull/94/head
eric 2020-02-18 09:58:02 -05:00
parent 63d6db7564
commit e3f5459f68
5 changed files with 145 additions and 145 deletions

View File

@ -51,8 +51,8 @@ def onix_feed_for_work(work):
return etree.tostring(feed, pretty_print=True) return etree.tostring(feed, pretty_print=True)
def header(facet=None): def header(facet=None):
header_node = etree.Element("Header") header_node = etree.Element("Header")
sender_node = etree.Element("Sender") sender_node = etree.Element("Sender")
sender_node.append(text_node("SenderName", "unglue.it")) sender_node.append(text_node("SenderName", "unglue.it"))
sender_node.append(text_node("EmailAddress", "unglueit@ebookfoundation.org")) sender_node.append(text_node("EmailAddress", "unglueit@ebookfoundation.org"))
header_node.append(sender_node) header_node.append(sender_node)

View File

@ -199,7 +199,7 @@ def load_from_books(books):
Author3First, Author3Role, AuthorBio, TableOfContents, Excerpt, DescriptionLong, Author3First, Author3Role, AuthorBio, TableOfContents, Excerpt, DescriptionLong,
DescriptionBrief, BISACCode1, BISACCode2, BISACCode3, CopyrightYear, ePublicationDate, DescriptionBrief, BISACCode1, BISACCode2, BISACCode3, CopyrightYear, ePublicationDate,
eListPrice, ListPriceCurrencyType, List Price in USD (paper ISBN), eTerritoryRights, eListPrice, ListPriceCurrencyType, List Price in USD (paper ISBN), eTerritoryRights,
SubjectListMARC, , Book-level DOI, URL, License SubjectListMARC, , Book-level DOI, URL, License
''' '''

View File

@ -302,7 +302,7 @@ def handle_wishlist_added(supporter, work, **kwargs):
from regluit.core.tasks import emit_notifications from regluit.core.tasks import emit_notifications
emit_notifications.delay() emit_notifications.delay()
wishlist_added.connect(handle_wishlist_added) wishlist_added.connect(handle_wishlist_added)
deadline_impending = Signal(providing_args=["campaign"]) deadline_impending = Signal(providing_args=["campaign"])

View File

@ -12,4 +12,4 @@ register = Library()
@stringfilter @stringfilter
def urldecode(value): def urldecode(value):
return unquote(value) return unquote(value)

View File

@ -12,8 +12,8 @@ from itertools import islice
from pytz import utc from pytz import utc
import re import re
import unittest import unittest
from unittest import TestCase from unittest import TestCase
import stripe import stripe
""" """
@ -49,7 +49,7 @@ STRIPE_EVENT_TYPES = ['account.updated', 'account.application.deauthorized', 'ba
'charge.dispute.created', 'charge.dispute.updated', 'charge.dispute.closed', 'charge.dispute.created', 'charge.dispute.updated', 'charge.dispute.closed',
'customer.created', 'customer.updated', 'customer.deleted', 'customer.created', 'customer.updated', 'customer.deleted',
'customer.card.created', 'customer.card.updated', 'customer.card.deleted', 'customer.card.created', 'customer.card.updated', 'customer.card.deleted',
'customer.source.created', 'customer.source.deleted', 'customer.source.expiring', 'customer.source.created', 'customer.source.deleted', 'customer.source.expiring',
'customer.source.updated', 'customer.source.updated',
'customer.subscription.created', 'customer.subscription.updated', 'customer.subscription.created', 'customer.subscription.updated',
'customer.subscription.deleted', 'customer.subscription.trial_will_end', 'customer.subscription.deleted', 'customer.subscription.trial_will_end',
@ -62,7 +62,7 @@ STRIPE_EVENT_TYPES = ['account.updated', 'account.application.deauthorized', 'ba
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# https://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904 # https://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904
def grouper(iterable, page_size): def grouper(iterable, page_size):
page= [] page= []
for item in iterable: for item in iterable:
@ -79,7 +79,7 @@ class StripelibError(baseprocessor.ProcessorError):
STRIPE_PK = settings.STRIPE_PK STRIPE_PK = settings.STRIPE_PK
STRIPE_SK = settings.STRIPE_SK STRIPE_SK = settings.STRIPE_SK
# set default stripe api_key to that of unglue.it # set default stripe api_key to that of unglue.it
stripe.api_key = STRIPE_SK stripe.api_key = STRIPE_SK
@ -96,8 +96,8 @@ stripe.api_version = API_VERSION
# https://stripe.com/docs/testing # https://stripe.com/docs/testing
TEST_CARDS = ( TEST_CARDS = (
('4242424242424242', 'Visa'), ('4242424242424242', 'Visa'),
('4012888888881881', 'Visa'), ('4012888888881881', 'Visa'),
('5555555555554444', 'MasterCard'), ('5555555555554444', 'MasterCard'),
('5105105105105100', 'MasterCard'), ('5105105105105100', 'MasterCard'),
('378282246310005', 'American Express'), ('378282246310005', 'American Express'),
@ -132,7 +132,7 @@ CARD_FIELDS_TO_COMPARE = ('exp_month', 'exp_year', 'name', 'address_line1', 'add
def filter_none(d): def filter_none(d):
return dict([(k,v) for (k,v) in d.items() if v is not None]) return dict([(k,v) for (k,v) in d.items() if v is not None])
# if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token. # if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token.
# https://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10 # https://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
@ -148,18 +148,18 @@ def luhn_checksum(card_number):
for d in even_digits: for d in even_digits:
checksum += sum(digits_of(d*2)) checksum += sum(digits_of(d*2))
return checksum % 10 return checksum % 10
def is_luhn_valid(card_number): def is_luhn_valid(card_number):
return luhn_checksum(card_number) == 0 return luhn_checksum(card_number) == 0
# https://stripe.com/docs/tutorials/charges # https://stripe.com/docs/tutorials/charges
def card (number=TEST_CARDS[0][0], exp_month=1, exp_year=2030, cvc=None, name=None, def card (number=TEST_CARDS[0][0], exp_month=1, exp_year=2030, cvc=None, name=None,
address_line1=None, address_line2=None, address_zip=None, address_state=None, address_country=None): address_line1=None, address_line2=None, address_zip=None, address_state=None, address_country=None):
"""Note: there is no place to enter address_city in the API""" """Note: there is no place to enter address_city in the API"""
card = { card = {
"number": number, "number": number,
"exp_month": int(exp_month), "exp_month": int(exp_month),
@ -172,7 +172,7 @@ def card (number=TEST_CARDS[0][0], exp_month=1, exp_year=2030, cvc=None, name=No
"address_state": address_state, "address_state": address_state,
"address_country": address_country "address_country": address_country
} }
return filter_none(card) return filter_none(card)
def _isListableAPIResource(x): def _isListableAPIResource(x):
@ -186,10 +186,10 @@ def _isListableAPIResource(x):
class StripeClient(object): class StripeClient(object):
def __init__(self, api_key=STRIPE_SK): def __init__(self, api_key=STRIPE_SK):
self.api_key = api_key self.api_key = api_key
# key entities: Charge, Customer, Token, Event # key entities: Charge, Customer, Token, Event
@property @property
def charge(self): def charge(self):
return stripe.Charge(api_key=self.api_key) return stripe.Charge(api_key=self.api_key)
@ -200,22 +200,22 @@ class StripeClient(object):
@property @property
def token(self): def token(self):
return stripe.Token(api_key=self.api_key) return stripe.Token(api_key=self.api_key)
@property @property
def transfer(self): def transfer(self):
return stripe.Transfer(api_key=self.api_key) return stripe.Transfer(api_key=self.api_key)
@property @property
def event(self): def event(self):
return stripe.Event(api_key=self.api_key) return stripe.Event(api_key=self.api_key)
def create_token(self, card): def create_token(self, card):
return stripe.Token(api_key=self.api_key).create(card=card) return stripe.Token(api_key=self.api_key).create(card=card)
def create_customer(self, card=None, description=None, email=None, account_balance=None, plan=None, trial_end=None): def create_customer(self, card=None, description=None, email=None, account_balance=None, plan=None, trial_end=None):
"""card is a dictionary or a token""" """card is a dictionary or a token"""
# https://stripe.com/docs/api?lang=python#create_customer # https://stripe.com/docs/api?lang=python#create_customer
customer = stripe.Customer(api_key=self.api_key).create( customer = stripe.Customer(api_key=self.api_key).create(
card=card, card=card,
description=description, description=description,
@ -224,7 +224,7 @@ class StripeClient(object):
plan=plan, plan=plan,
trial_end=trial_end trial_end=trial_end
) )
# customer.id is useful to save in db # customer.id is useful to save in db
return customer return customer
@ -234,7 +234,7 @@ class StripeClient(object):
# customer.id or card required but not both # customer.id or card required but not both
# charge the Customer instead of the card # charge the Customer instead of the card
# amount in cents # amount in cents
charge = stripe.Charge(api_key=self.api_key).create( charge = stripe.Charge(api_key=self.api_key).create(
amount=int(100*amount), # in cents amount=int(100*amount), # in cents
currency=currency, currency=currency,
@ -242,15 +242,15 @@ class StripeClient(object):
card=card, card=card,
description=description description=description
) )
return charge return charge
def refund_charge(self, charge_id): def refund_charge(self, charge_id):
# https://stripe.com/docs/api?lang=python#refund_charge # https://stripe.com/docs/api?lang=python#refund_charge
ch = stripe.Charge(api_key=self.api_key).retrieve(charge_id) ch = stripe.Charge(api_key=self.api_key).retrieve(charge_id)
ch.refund() ch.refund()
return ch return ch
def _all_objs(self, class_type, **kwargs): def _all_objs(self, class_type, **kwargs):
"""a generic iterator for all classes of type stripe.ListableAPIResource""" """a generic iterator for all classes of type stripe.ListableAPIResource"""
# type=None, created=None, count=None, offset=0 # type=None, created=None, count=None, offset=0
@ -263,11 +263,11 @@ class StripeClient(object):
if _isListableAPIResource(stripe_class): if _isListableAPIResource(stripe_class):
kwargs2 = kwargs.copy() kwargs2 = kwargs.copy()
kwargs2.setdefault('offset', 0) kwargs2.setdefault('offset', 0)
kwargs2.setdefault('count', 100) kwargs2.setdefault('count', 100)
more_items = True more_items = True
while more_items: while more_items:
items = stripe_class(api_key=self.api_key).list(**kwargs2)['data'] items = stripe_class(api_key=self.api_key).list(**kwargs2)['data']
for item in items: for item in items:
yield item yield item
@ -277,12 +277,12 @@ class StripeClient(object):
more_items = False more_items = False
else: else:
yield StopIteration yield StopIteration
def __getattribute__(self, name): def __getattribute__(self, name):
""" handle list_* calls""" """ handle list_* calls"""
mapping = {'list_charges':"Charge", mapping = {'list_charges':"Charge",
'list_coupons': "Coupon", 'list_coupons': "Coupon",
'list_customers':"Customer", 'list_customers':"Customer",
'list_events':"Event", 'list_events':"Event",
'list_invoices':"Invoice", 'list_invoices':"Invoice",
'list_invoiceitems':"InvoiceItem", 'list_invoiceitems':"InvoiceItem",
@ -293,15 +293,15 @@ class StripeClient(object):
class_value = mapping[name] class_value = mapping[name]
def list_events(**kwargs): def list_events(**kwargs):
for e in self._all_objs(class_value, **kwargs): for e in self._all_objs(class_value, **kwargs):
yield e yield e
return list_events return list_events
else: else:
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
# can't test Transfer in test mode: "There are no transfers in test mode." # can't test Transfer in test mode: "There are no transfers in test mode."
#pledge scenario #pledge scenario
@ -328,7 +328,7 @@ class StripeClient(object):
# Errors we still need to catch: # Errors we still need to catch:
# #
# * invalid_number -- can't get stripe to generate for us. What it means: # * invalid_number -- can't get stripe to generate for us. What it means:
# #
# * that the card has been cancelled (or never existed to begin with # * that the card has been cancelled (or never existed to begin with
# #
@ -352,11 +352,11 @@ class StripeClient(object):
class StripeErrorTest(TestCase): class StripeErrorTest(TestCase):
"""Make sure the exceptions returned by stripe act as expected""" """Make sure the exceptions returned by stripe act as expected"""
def test_cc_test_numbers_luhn_valid(self): def test_cc_test_numbers_luhn_valid(self):
"""Show that the test CC numbers supplied for testing as valid numbers are indeed Luhn valid""" """Show that the test CC numbers supplied for testing as valid numbers are indeed Luhn valid"""
self.assertTrue(all([is_luhn_valid(c[0]) for c in ERROR_TESTING.values()])) self.assertTrue(all([is_luhn_valid(c[0]) for c in ERROR_TESTING.values()]))
def test_good_token(self): def test_good_token(self):
""" verify normal operation """ """ verify normal operation """
sc = StripeClient() sc = StripeClient()
@ -368,7 +368,7 @@ class StripeErrorTest(TestCase):
self.assertEqual(token2.id, token1.id) self.assertEqual(token2.id, token1.id)
# make sure token id has a form tok_ # make sure token id has a form tok_
self.assertEqual(token2.id[:4], "tok_") self.assertEqual(token2.id[:4], "tok_")
# should be only test mode # should be only test mode
self.assertEqual(token2.livemode, False) self.assertEqual(token2.livemode, False)
# token hasn't been used yet # token hasn't been used yet
@ -380,7 +380,7 @@ class StripeErrorTest(TestCase):
self.assertEqual(token2.card.last4, TEST_CARDS[0][0][-4:]) self.assertEqual(token2.card.last4, TEST_CARDS[0][0][-4:])
# fingerprint # fingerprint
self.assertGreaterEqual(len(token2.card.fingerprint), 16) self.assertGreaterEqual(len(token2.card.fingerprint), 16)
# now charge the token # now charge the token
charge1 = sc.create_charge(10, 'usd', card=token2.id) charge1 = sc.create_charge(10, 'usd', card=token2.id)
self.assertEqual(charge1.amount, 1000) self.assertEqual(charge1.amount, 1000)
@ -390,8 +390,8 @@ class StripeErrorTest(TestCase):
self.assertEqual(charge1.failure_message,None) self.assertEqual(charge1.failure_message,None)
self.assertEqual(charge1.fee,59) self.assertEqual(charge1.fee,59)
self.assertEqual(charge1.refunded,False) self.assertEqual(charge1.refunded,False)
def test_error_creating_customer_with_declined_card(self): def test_error_creating_customer_with_declined_card(self):
"""Test whether we can get a charge decline error""" """Test whether we can get a charge decline error"""
sc = StripeClient() sc = StripeClient()
@ -402,7 +402,7 @@ class StripeErrorTest(TestCase):
except stripe.error.CardError as e: except stripe.error.CardError as e:
self.assertEqual(e.code, "card_declined") self.assertEqual(e.code, "card_declined")
self.assertEqual(e.args[0], "Your card was declined") self.assertEqual(e.args[0], "Your card was declined")
def test_charge_bad_cust(self): def test_charge_bad_cust(self):
# expect the card to be declined -- and for us to get CardError # expect the card to be declined -- and for us to get CardError
sc = StripeClient() sc = StripeClient()
@ -417,24 +417,24 @@ class StripeErrorTest(TestCase):
def test_bad_cc_number(self): def test_bad_cc_number(self):
"""send a bad cc and should get an error when trying to create a token""" """send a bad cc and should get an error when trying to create a token"""
BAD_CC_NUM = '4242424242424241' BAD_CC_NUM = '4242424242424241'
# reason for decline is number is not Luhn valid # reason for decline is number is not Luhn valid
self.assertFalse(is_luhn_valid(BAD_CC_NUM)) self.assertFalse(is_luhn_valid(BAD_CC_NUM))
sc = StripeClient() sc = StripeClient()
card1 = card(number=BAD_CC_NUM, exp_month=1, exp_year=2020, cvc='123', name='Don Giovanni', card1 = card(number=BAD_CC_NUM, exp_month=1, exp_year=2020, cvc='123', name='Don Giovanni',
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) # good card address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) # good card
try: try:
token1 = sc.create_token(card=card1) token1 = sc.create_token(card=card1)
self.fail("Attempt to create token with bad cc number did not throw expected exception.") self.fail("Attempt to create token with bad cc number did not throw expected exception.")
except stripe.error.CardError as e: except stripe.error.CardError as e:
self.assertEqual(e.code, "incorrect_number") self.assertEqual(e.code, "incorrect_number")
self.assertEqual(e.args[0], "Your card number is incorrect") self.assertEqual(e.args[0], "Your card number is incorrect")
def test_invalid_expiry_month(self): def test_invalid_expiry_month(self):
"""Use an invalid month e.g. 13.""" """Use an invalid month e.g. 13."""
sc = StripeClient() sc = StripeClient()
card1 = card(number=TEST_CARDS[0][0], exp_month=13, exp_year=2020, cvc='123', name='Don Giovanni', card1 = card(number=TEST_CARDS[0][0], exp_month=13, exp_year=2020, cvc='123', name='Don Giovanni',
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None)
@ -448,7 +448,7 @@ class StripeErrorTest(TestCase):
def test_invalid_expiry_year(self): def test_invalid_expiry_year(self):
"""Use a year in the past e.g. 1970.""" """Use a year in the past e.g. 1970."""
sc = StripeClient() sc = StripeClient()
card1 = card(number=TEST_CARDS[0][0], exp_month=12, exp_year=1970, cvc='123', name='Don Giovanni', card1 = card(number=TEST_CARDS[0][0], exp_month=12, exp_year=1970, cvc='123', name='Don Giovanni',
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None)
@ -459,10 +459,10 @@ class StripeErrorTest(TestCase):
except stripe.error.CardError as e: except stripe.error.CardError as e:
self.assertEqual(e.code, "invalid_expiry_year") self.assertEqual(e.code, "invalid_expiry_year")
self.assertEqual(e.args[0], "Your card's expiration year is invalid") self.assertEqual(e.args[0], "Your card's expiration year is invalid")
def test_invalid_cvc(self): def test_invalid_cvc(self):
"""Use a two digit number e.g. 99.""" """Use a two digit number e.g. 99."""
sc = StripeClient() sc = StripeClient()
card1 = card(number=TEST_CARDS[0][0], exp_month=12, exp_year=2020, cvc='99', name='Don Giovanni', card1 = card(number=TEST_CARDS[0][0], exp_month=12, exp_year=2020, cvc='99', name='Don Giovanni',
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None)
@ -473,10 +473,10 @@ class StripeErrorTest(TestCase):
except stripe.error.CardError as e: except stripe.error.CardError as e:
self.assertEqual(e.code, "invalid_cvc") self.assertEqual(e.code, "invalid_cvc")
self.assertEqual(e.args[0], "Your card's security code is invalid") self.assertEqual(e.args[0], "Your card's security code is invalid")
def test_missing_card(self): def test_missing_card(self):
"""There is no card on a customer that is being charged""" """There is no card on a customer that is being charged"""
sc = StripeClient() sc = StripeClient()
# create a Customer with no attached card # create a Customer with no attached card
cust1 = sc.create_customer(description="test cust w/ no card") cust1 = sc.create_customer(description="test cust w/ no card")
@ -485,21 +485,21 @@ class StripeErrorTest(TestCase):
except stripe.error.CardError as e: except stripe.error.CardError as e:
self.assertEqual(e.code, "missing") self.assertEqual(e.code, "missing")
self.assertEqual(e.args[0], "Cannot charge a customer that has no active card") self.assertEqual(e.args[0], "Cannot charge a customer that has no active card")
class PledgeScenarioTest(TestCase): class PledgeScenarioTest(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls._sc = StripeClient(api_key=STRIPE_SK) cls._sc = StripeClient(api_key=STRIPE_SK)
# valid card # valid card
card0 = card() card0 = card()
cls._good_cust = cls._sc.create_customer(card=card0, description="test good customer", email="raymond.yee@gmail.com") cls._good_cust = cls._sc.create_customer(card=card0, description="test good customer", email="raymond.yee@gmail.com")
# bad card # bad card
test_card_num_to_get_BAD_ATTACHED_CARD = ERROR_TESTING['BAD_ATTACHED_CARD'][0] test_card_num_to_get_BAD_ATTACHED_CARD = ERROR_TESTING['BAD_ATTACHED_CARD'][0]
card1 = card(number=test_card_num_to_get_BAD_ATTACHED_CARD) card1 = card(number=test_card_num_to_get_BAD_ATTACHED_CARD)
cls._cust_bad_card = cls._sc.create_customer(card=card1, description="test bad customer", email="rdhyee@gluejar.com") cls._cust_bad_card = cls._sc.create_customer(card=card1, description="test bad customer", email="rdhyee@gluejar.com")
def test_charge_good_cust(self): def test_charge_good_cust(self):
charge = self._sc.create_charge(10, customer=self._good_cust.id, description="$10 for good cust") 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), str)
@ -507,39 +507,39 @@ class PledgeScenarioTest(TestCase):
# print out all the pieces of Customer and Charge objects # print out all the pieces of Customer and Charge objects
print(dir(charge)) print(dir(charge))
print(dir(self._good_cust)) print(dir(self._good_cust))
def test_error_creating_customer_with_declined_card(self): def test_error_creating_customer_with_declined_card(self):
# should get a CardError upon attempt to create Customer with this card # should get a CardError upon attempt to create Customer with this card
_card = card(number=card(ERROR_TESTING['CHARGE_DECLINE'][0])) _card = card(number=card(ERROR_TESTING['CHARGE_DECLINE'][0]))
self.assertRaises(stripe.error.CardError, self._sc.create_customer, card=_card) self.assertRaises(stripe.error.CardError, self._sc.create_customer, card=_card)
def test_charge_bad_cust(self): def test_charge_bad_cust(self):
# expect the card to be declined -- and for us to get CardError # expect the card to be declined -- and for us to get CardError
self.assertRaises(stripe.error.CardError, self._sc.create_charge, 10, self.assertRaises(stripe.error.CardError, self._sc.create_charge, 10,
customer = self._cust_bad_card.id, description="$10 for bad cust") customer = self._cust_bad_card.id, description="$10 for bad cust")
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
# clean up stuff we create in test -- right now list current objects # clean up stuff we create in test -- right now list current objects
pass pass
#cls._good_cust.delete() #cls._good_cust.delete()
class StripePaymentRequest(baseprocessor.BasePaymentRequest): class StripePaymentRequest(baseprocessor.BasePaymentRequest):
"""so far there is no need to have a separate class here""" """so far there is no need to have a separate class here"""
pass pass
class Processor(baseprocessor.Processor): class Processor(baseprocessor.Processor):
def make_account(self, user=None, token=None, email=None): def make_account(self, user=None, token=None, email=None):
"""returns a payment.models.Account based on stripe token and user""" """returns a payment.models.Account based on stripe token and user"""
if token is None or len(token) == 0: if token is None or len(token) == 0:
raise StripelibError("input token is None", None) raise StripelibError("input token is None", None)
sc = StripeClient() sc = StripeClient()
# create customer and charge id and then charge the customer # create customer and charge id and then charge the customer
try: try:
if user: if user:
@ -549,7 +549,7 @@ class Processor(baseprocessor.Processor):
customer = sc.create_customer(card=token, description='anonymous user', email=email) customer = sc.create_customer(card=token, description='anonymous user', email=email)
except stripe.error.StripeError as e: except stripe.error.StripeError as e:
raise StripelibError(e.args, e) raise StripelibError(e.args, e)
account = Account(host = PAYMENT_HOST_STRIPE, account = Account(host = PAYMENT_HOST_STRIPE,
account_id = customer.id, account_id = customer.id,
card_last4 = customer.active_card.last4, card_last4 = customer.active_card.last4,
@ -562,147 +562,147 @@ class Processor(baseprocessor.Processor):
) )
if user and user.profile.account: if user and user.profile.account:
user.profile.account.deactivate() user.profile.account.deactivate()
account.save() account.save()
account.recharge_failed_transactions() account.recharge_failed_transactions()
else: else:
account.save() account.save()
return account return account
class Preapproval(StripePaymentRequest, baseprocessor.Processor.Preapproval): class Preapproval(StripePaymentRequest, baseprocessor.Processor.Preapproval):
def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): 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 # set the expiration date for the preapproval if not passed in. This is what the paypal library does
self.transaction = transaction self.transaction = transaction
now_val = now() now_val = now()
if expiry is None: if expiry is None:
expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
transaction.date_authorized = now_val transaction.date_authorized = now_val
transaction.date_expired = expiry transaction.date_expired = expiry
# let's figure out what part of transaction can be used to store info # let's figure out what part of transaction can be used to store info
# try placing charge id in transaction.pay_key # try placing charge id in transaction.pay_key
# need to set amount # need to set amount
# how does transaction.max_amount get set? -- coming from /pledge/xxx/ -> manager.process_transaction # 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 # 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 # ASSUMPTION: a user has any given moment one and only one active payment Account
account = transaction.user.profile.account account = transaction.user.profile.account
if not account: if not account:
logger.warning("user {0} has no active payment account".format(transaction.user)) logger.warning("user {0} has no active payment account".format(transaction.user))
raise StripelibError("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)) logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id))
# settings to apply to transaction for TRANSACTION_STATUS_ACTIVE # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE
# should approved be set to False and wait for a webhook? # should approved be set to False and wait for a webhook?
transaction.approved = True transaction.approved = True
transaction.type = PAYMENT_TYPE_AUTHORIZATION transaction.type = PAYMENT_TYPE_AUTHORIZATION
transaction.host = PAYMENT_HOST_STRIPE transaction.host = PAYMENT_HOST_STRIPE
transaction.status = TRANSACTION_STATUS_ACTIVE transaction.status = TRANSACTION_STATUS_ACTIVE
transaction.preapproval_key = account.account_id transaction.preapproval_key = account.account_id
transaction.currency = 'USD' transaction.currency = 'USD'
transaction.amount = amount transaction.amount = amount
transaction.save() transaction.save()
def key(self): def key(self):
return self.transaction.preapproval_key return self.transaction.preapproval_key
def next_url(self): def next_url(self):
"""return None because no redirection to stripe is required""" """return None because no redirection to stripe is required"""
return None return None
class Pay(StripePaymentRequest, baseprocessor.Processor.Pay): class Pay(StripePaymentRequest, baseprocessor.Processor.Pay):
''' '''
The pay function generates a redirect URL to approve the transaction The pay function generates a redirect URL to approve the transaction
If the transaction has a null user (is_anonymous), then a token musr be supplied If the transaction has a null user (is_anonymous), then a token musr be supplied
''' '''
def __init__( self, transaction, return_url=None, amount=None, paymentReason="", token=None): def __init__( self, transaction, return_url=None, amount=None, paymentReason="", token=None):
self.transaction=transaction self.transaction=transaction
self.url = return_url self.url = return_url
now_val = now() now_val = now()
transaction.date_authorized = now_val transaction.date_authorized = now_val
# ASSUMPTION: a user has any given moment one and only one active payment Account # ASSUMPTION: a user has any given moment one and only one active payment Account
if token: if token:
# user is anonymous # user is anonymous
account = transaction.get_payment_class().make_account(token = token, email = transaction.receipt) account = transaction.get_payment_class().make_account(token = token, email = transaction.receipt)
else: else:
account = transaction.user.profile.account account = transaction.user.profile.account
if not account: if not account:
logger.warning("user {0} has no active payment account".format(transaction.user)) logger.warning("user {0} has no active payment account".format(transaction.user))
raise StripelibError("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)) logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id))
# settings to apply to transaction for TRANSACTION_STATUS_ACTIVE # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE
# should approved be set to False and wait for a webhook? # should approved be set to False and wait for a webhook?
transaction.approved = True transaction.approved = True
transaction.type = PAYMENT_TYPE_INSTANT transaction.type = PAYMENT_TYPE_INSTANT
transaction.host = PAYMENT_HOST_STRIPE transaction.host = PAYMENT_HOST_STRIPE
transaction.preapproval_key = account.account_id transaction.preapproval_key = account.account_id
transaction.currency = 'USD' transaction.currency = 'USD'
transaction.amount = amount transaction.amount = amount
transaction.save() transaction.save()
# execute the transaction # execute the transaction
p = transaction.get_payment_class().Execute(transaction) p = transaction.get_payment_class().Execute(transaction)
if p.success() and not p.error(): if p.success() and not p.error():
transaction.pay_key = p.key() transaction.pay_key = p.key()
transaction.save() transaction.save()
else: else:
self.errorMessage = p.errorMessage #pass error message up self.errorMessage = p.errorMessage #pass error message up
logger.info("execute_transaction Error: {}".format(p.error_string())) logger.info("execute_transaction Error: {}".format(p.error_string()))
def amount( self ): def amount( self ):
return self.transaction.amount return self.transaction.amount
def key( self ): def key( self ):
return self.transaction.pay_key return self.transaction.pay_key
def next_url( self ): def next_url( self ):
return self.url return self.url
class Execute(StripePaymentRequest): class Execute(StripePaymentRequest):
''' '''
The Execute function attempts to charge the credit card of stripe Customer associated with user connected to transaction. The Execute function attempts to charge the credit card of stripe Customer associated with user connected to transaction.
''' '''
def __init__(self, transaction=None): def __init__(self, transaction=None):
self.transaction = transaction self.transaction = transaction
# make sure transaction hasn't already been executed # make sure transaction hasn't already been executed
if transaction.status == TRANSACTION_STATUS_COMPLETE: if transaction.status == TRANSACTION_STATUS_COMPLETE:
return return
# make sure we are dealing with a stripe transaction # make sure we are dealing with a stripe transaction
if transaction.host != PAYMENT_HOST_STRIPE: if transaction.host != PAYMENT_HOST_STRIPE:
raise StripelibError("transaction.host {0} is not the expected {1}".format(transaction.host, PAYMENT_HOST_STRIPE)) raise StripelibError("transaction.host {0} is not the expected {1}".format(transaction.host, PAYMENT_HOST_STRIPE))
sc = StripeClient() sc = StripeClient()
# look first for transaction.user.profile.account.account_id # look first for transaction.user.profile.account.account_id
try: try:
customer_id = transaction.user.profile.account.account_id customer_id = transaction.user.profile.account.account_id
except: except:
customer_id = transaction.preapproval_key customer_id = transaction.preapproval_key
if customer_id is not None: if customer_id is not None:
try: try:
# useful things to put in description: transaction.id, transaction.user.id, customer_id, transaction.amount # useful things to put in description: transaction.id, transaction.user.id, customer_id, transaction.amount
charge = sc.create_charge(transaction.amount, customer=customer_id, charge = sc.create_charge(transaction.amount, customer=customer_id,
@ -718,8 +718,8 @@ class Processor(baseprocessor.Processor):
r = PaymentResponse.objects.create(api="stripelib.Execute", correlation_id=None, r = PaymentResponse.objects.create(api="stripelib.Execute", correlation_id=None,
timestamp=now(), info=e.args[0], timestamp=now(), info=e.args[0],
status=TRANSACTION_STATUS_ERROR, transaction=transaction) status=TRANSACTION_STATUS_ERROR, transaction=transaction)
transaction.status = TRANSACTION_STATUS_ERROR transaction.status = TRANSACTION_STATUS_ERROR
self.errorMessage = e.args # manager puts this on transaction self.errorMessage = e.args # manager puts this on transaction
transaction.save() transaction.save()
@ -730,57 +730,57 @@ class Processor(baseprocessor.Processor):
# otherwise, report exception to us # otherwise, report exception to us
else: else:
logger.exception("transaction id {0}, exception: {1}".format(transaction.id, e.args)) logger.exception("transaction id {0}, exception: {1}".format(transaction.id, e.args))
else: else:
self.charge = charge self.charge = charge
transaction.status = TRANSACTION_STATUS_COMPLETE transaction.status = TRANSACTION_STATUS_COMPLETE
transaction.pay_key = charge.id transaction.pay_key = charge.id
transaction.date_payment = now() transaction.date_payment = now()
transaction.save() transaction.save()
# fire signal for sucessful transaction # fire signal for sucessful transaction
transaction_charged.send(sender=self, transaction=transaction) transaction_charged.send(sender=self, transaction=transaction)
else: else:
# nothing to charge # nothing to charge
raise StripelibError("No customer id available to charge for transaction {0}".format(transaction.id), None) raise StripelibError("No customer id available to charge for transaction {0}".format(transaction.id), None)
def api(self): def api(self):
return "Base Pay" return "Base Pay"
def key(self): def key(self):
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key # 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 return self.transaction.pay_key
class PreapprovalDetails(StripePaymentRequest): class PreapprovalDetails(StripePaymentRequest):
''' '''
Get details about an authorized token Get details about an authorized token
This api must set 4 different class variables to work with the code in manager.py This api must set 4 different class variables to work with the code in manager.py
status - one of the global transaction status codes status - one of the global transaction status codes
approved - boolean value approved - boolean value
currency - not used in this API, but we can get some more info via other APIs - TODO 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 amount - not used in this API, but we can get some more info via other APIs - TODO
''' '''
def __init__(self, transaction): def __init__(self, transaction):
self.transaction = transaction self.transaction = transaction
self.status = self.transaction.status self.status = self.transaction.status
if self.status == TRANSACTION_STATUS_CANCELED: if self.status == TRANSACTION_STATUS_CANCELED:
self.approved = False self.approved = False
else: else:
self.approved = True self.approved = True
# Set the other fields that are expected. We don't have values for these now, so just copy the transaction # 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.currency = transaction.currency
self.amount = transaction.amount self.amount = transaction.amount
def ProcessIPN(self, request): def ProcessIPN(self, request):
# retrieve the request's body and parse it as JSON in, e.g. Django # retrieve the request's body and parse it as JSON in, e.g. Django
try: try:
@ -793,7 +793,7 @@ class Processor(baseprocessor.Processor):
# now parse out pieces of the webhook # now parse out pieces of the webhook
event_id = event_json.get("id") event_id = event_json.get("id")
# use Stripe to ask for details -- ignore what we're sent for security # use Stripe to ask for details -- ignore what we're sent for security
sc = StripeClient() sc = StripeClient()
try: try:
event = sc.event.retrieve(event_id) event = sc.event.retrieve(event_id)
@ -814,20 +814,20 @@ class Processor(baseprocessor.Processor):
except Exception as e: except Exception as e:
logger.warning("Parsing of event_type into resource, action failed: {0}".format(e)) logger.warning("Parsing of event_type into resource, action failed: {0}".format(e))
return HttpResponse(status=400) return HttpResponse(status=400)
try: try:
ev_object = event.data.object ev_object = event.data.object
except Exception as e: except Exception as e:
logger.warning("attempt to retrieve event object failed: {0}".format(e)) logger.warning("attempt to retrieve event object failed: {0}".format(e))
return HttpResponse(status=400) return HttpResponse(status=400)
if event_type == 'account.updated': if event_type == 'account.updated':
# should we alert ourselves? # should we alert ourselves?
# how about account.application.deauthorized ? # how about account.application.deauthorized ?
pass pass
elif resource == 'charge': elif resource == 'charge':
# we need to handle: succeeded, failed, refunded, disputed # we need to handle: succeeded, failed, refunded, disputed
if action == 'succeeded': if action == 'succeeded':
# TO DO: delete this logic since we don't do anything but look up transaction. # TO DO: delete this logic since we don't do anything but look up transaction.
logger.info("charge.succeeded webhook for {0}".format(ev_object.get("id"))) logger.info("charge.succeeded webhook for {0}".format(ev_object.get("id")))
@ -843,8 +843,8 @@ class Processor(baseprocessor.Processor):
else: else:
logger.warning("ev_object.id {0} != transaction.pay_key {1}".format(ev_object.id, transaction.pay_key)) logger.warning("ev_object.id {0} != transaction.pay_key {1}".format(ev_object.id, transaction.pay_key))
except Exception as e: except Exception as e:
logger.warning(e) logger.warning(e)
elif action == 'failed': elif action == 'failed':
# TO DO: delete this logic since we don't do anything but look up transaction. # TO DO: delete this logic since we don't do anything but look up transaction.
logger.info("charge.failed webhook for {0}".format(ev_object.get("id"))) logger.info("charge.failed webhook for {0}".format(ev_object.get("id")))
@ -863,7 +863,7 @@ class Processor(baseprocessor.Processor):
elif action == 'refunded': elif action == 'refunded':
pass pass
elif action == 'disputed': elif action == 'disputed':
pass pass
else: else:
# unexpected # unexpected
pass pass
@ -884,12 +884,12 @@ class Processor(baseprocessor.Processor):
pass pass
else: # other events else: # other events
pass pass
return HttpResponse("event_id: {0} event_type: {1}".format(event_id, event_type)) return HttpResponse("event_id: {0} event_type: {1}".format(event_id, event_type))
def suite(): def suite():
testcases = [PledgeScenarioTest, StripeErrorTest] testcases = [PledgeScenarioTest, StripeErrorTest]
#testcases = [StripeErrorTest] #testcases = [StripeErrorTest]
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases]) suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
@ -908,4 +908,4 @@ if __name__ == '__main__':
#unittest.main() #unittest.main()
suites = suite() suites = suite()
#suites = unittest.defaultTestLoader.loadTestsFromModule(__import__('__main__')) #suites = unittest.defaultTestLoader.loadTestsFromModule(__import__('__main__'))
unittest.TextTestRunner().run(suites) unittest.TextTestRunner().run(suites)