Implemented basic transaction_failed signal and notices -- THEY STILL NEED WORK

Tests handle situation of transaction_failed too
pull/1/head
Raymond Yee 2012-11-06 11:22:25 -08:00
parent 3da9ce6734
commit 793d984ba3
4 changed files with 110 additions and 25 deletions

View File

@ -14,7 +14,7 @@ from social_auth.signals import pre_update
from social_auth.backends.facebook import FacebookBackend from social_auth.backends.facebook import FacebookBackend
from tastypie.models import create_api_key from tastypie.models import create_api_key
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created from regluit.payment.signals import transaction_charged, transaction_failed, pledge_modified, pledge_created
import registration.signals import registration.signals
import django.dispatch import django.dispatch
@ -94,6 +94,7 @@ def create_notice_types(app, created_models, verbosity, **kwargs):
notification.create_notice_type("pledge_you_have_pledged", _("Thanks For Your Pledge!"), _("Your ungluing pledge has been entered.")) notification.create_notice_type("pledge_you_have_pledged", _("Thanks For Your Pledge!"), _("Your ungluing pledge has been entered."))
notification.create_notice_type("pledge_status_change", _("Your Pledge Has Been Modified"), _("Your ungluing pledge has been modified.")) notification.create_notice_type("pledge_status_change", _("Your Pledge Has Been Modified"), _("Your ungluing pledge has been modified."))
notification.create_notice_type("pledge_charged", _("Your Pledge has been Executed"), _("You have contributed to a successful ungluing campaign.")) notification.create_notice_type("pledge_charged", _("Your Pledge has been Executed"), _("You have contributed to a successful ungluing campaign."))
notification.create_notice_type("pledge_failed", _("Unable to charge your credit card"), _("A charge to your credit card did not go through."))
notification.create_notice_type("rights_holder_created", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder.")) notification.create_notice_type("rights_holder_created", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder."))
notification.create_notice_type("rights_holder_claim_approved", _("Claim Accepted"), _("A claim you've entered has been accepted.")) notification.create_notice_type("rights_holder_claim_approved", _("Claim Accepted"), _("A claim you've entered has been accepted."))
notification.create_notice_type("wishlist_unsuccessful_amazon", _("Campaign shut down"), _("An ungluing campaign that you supported had to be shut down due to an Amazon Payments policy change.")) notification.create_notice_type("wishlist_unsuccessful_amazon", _("Campaign shut down"), _("An ungluing campaign that you supported had to be shut down due to an Amazon Payments policy change."))
@ -171,6 +172,21 @@ def handle_transaction_charged(sender,transaction=None, **kwargs):
transaction_charged.connect(handle_transaction_charged) transaction_charged.connect(handle_transaction_charged)
# dealing with failed transactions
def handle_transaction_failed(sender,transaction=None, **kwargs):
if transaction==None:
return
notification.queue([transaction.user], "pledge_failed", {
'site':Site.objects.get_current(),
'transaction':transaction
}, True)
from regluit.core.tasks import emit_notifications
emit_notifications.delay()
transaction_failed.connect(handle_transaction_failed)
def handle_pledge_modified(sender, transaction=None, up_or_down=None, **kwargs): def handle_pledge_modified(sender, transaction=None, up_or_down=None, **kwargs):
# we need to know if pledges were modified up or down because Amazon handles the # we need to know if pledges were modified up or down because Amazon handles the
# transactions in different ways, resulting in different user-visible behavior; # transactions in different ways, resulting in different user-visible behavior;

View File

@ -11,7 +11,7 @@ from django.core import mail
from regluit.core.models import Work, Campaign, RightsHolder, Claim from regluit.core.models import Work, Campaign, RightsHolder, Claim
from regluit.payment.models import Transaction from regluit.payment.models import Transaction
from regluit.payment.manager import PaymentManager from regluit.payment.manager import PaymentManager
from regluit.payment.stripelib import StripeClient, TEST_CARDS, card from regluit.payment.stripelib import StripeClient, TEST_CARDS, ERROR_TESTING, card
from decimal import Decimal as D from decimal import Decimal as D
from regluit.utils.localdatetime import now from regluit.utils.localdatetime import now
@ -210,13 +210,13 @@ class UnifiedCampaignTests(TestCase):
r = self.client.post(ipn_url, data='{"id": "evt_XXXXXXXXX"}', content_type="application/json; charset=utf-8") r = self.client.post(ipn_url, data='{"id": "evt_XXXXXXXXX"}', content_type="application/json; charset=utf-8")
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def pledge_to_work_with_cc(self, username, password, work_id, card, preapproval_amount='10', premium_id='150'):
def test_pledge1(self):
# how much of test.campaigntest.test_relaunch can be done here? # how much of test.campaigntest.test_relaunch can be done here?
self.assertTrue(self.client.login(username="RaymondYee", password="Test_Password_")) self.assertTrue(self.client.login(username=username, password=password))
# Pro Web 2.0 Mashups # Pro Web 2.0 Mashups
self.work = Work.objects.get(id=1) self.work = Work.objects.get(id=work_id)
r = self.client.get("/work/%s/" % (self.work.id)) r = self.client.get("/work/%s/" % (self.work.id))
r = self.client.get("/work/%s/" % self.work.id) r = self.client.get("/work/%s/" % self.work.id)
@ -227,8 +227,8 @@ class UnifiedCampaignTests(TestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
# submit to pledge page # submit to pledge page
r = self.client.post("/pledge/%s/" % self.work.id, data={'preapproval_amount':'10', r = self.client.post("/pledge/%s/" % self.work.id, data={'preapproval_amount': preapproval_amount,
'premium_id':'150'}, follow=True) 'premium_id':premium_id}, follow=True)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
# Should now be on the fund page # Should now be on the fund page
@ -244,9 +244,7 @@ class UnifiedCampaignTests(TestCase):
time0 = time.time() time0 = time.time()
sc = StripeClient() sc = StripeClient()
card1 = card(number=TEST_CARDS[0][0], exp_month=1, exp_year='2020', cvc='123', name='Raymond Yee', stripe_token = sc.create_token(card=card)
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) # good card
stripe_token = sc.create_token(card=card1)
r = self.client.post(pledge_fund_path, data={'stripe_token':stripe_token.id}, follow=True) r = self.client.post(pledge_fund_path, data={'stripe_token':stripe_token.id}, follow=True)
# where are we now? # where are we now?
@ -256,13 +254,33 @@ class UnifiedCampaignTests(TestCase):
# dig up the transaction and charge it # dig up the transaction and charge it
pm = PaymentManager() pm = PaymentManager()
transaction = Transaction.objects.get(id=t_id) transaction = Transaction.objects.get(id=t_id)
# catch any exception and pass it along
try:
self.assertTrue(pm.execute_transaction(transaction, ())) self.assertTrue(pm.execute_transaction(transaction, ()))
except Exception, charge_exception:
pass
else:
charge_exception = None
time1 = time.time() time1 = time.time()
# retrieve events from this period -- need to pass in ints for event creation times # retrieve events from this period -- need to pass in ints for event creation times
events = list(sc._all_objs('Event', created={'gte':int(time0), 'lte':int(time1+1.0)})) events = list(sc._all_objs('Event', created={'gte':int(time0), 'lte':int(time1+1.0)}))
return (events, charge_exception)
def good_cc_scenario(self):
# how much of test.campaigntest.test_relaunch can be done here?
card1 = card(number=TEST_CARDS[0][0], exp_month=1, exp_year='2020', cvc='123', name='Raymond Yee',
address_line1="100 Jackson St.", address_line2="", address_zip="94706", address_state="CA", address_country=None) # good card
(events, charge_exception) = self.pledge_to_work_with_cc(username="RaymondYee", password="Test_Password_", work_id=1, card=card1,
preapproval_amount='10', premium_id='150')
self.assertEqual(charge_exception, None)
# expect to have 2 events (there is a possibility that someone else could be running tests on this stripe account at the same time) # expect to have 2 events (there is a possibility that someone else could be running tests on this stripe account at the same time)
# events returned sorted in reverse chronological order. # events returned sorted in reverse chronological order.
@ -277,18 +295,51 @@ class UnifiedCampaignTests(TestCase):
r = self.client.post(ipn_url, data=json.dumps({"id": event.id}), content_type="application/json; charset=utf-8") r = self.client.post(ipn_url, data=json.dumps({"id": event.id}), content_type="application/json; charset=utf-8")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
# confirm that an email was sent out for the customer.created event def bad_cc_scenario(self):
"""Goal of this scenario: enter a CC that will cause a charge.failed event, have user repledge succesfully"""
# Test that one message has been sent.
# for initial pledging, for successful campaign, for Customer.created event
self.assertEqual(len(mail.outbox), 3)
card1 = card(number=ERROR_TESTING['BAD_ATTACHED_CARD'][0])
(events, charge_exception) = self.pledge_to_work_with_cc(username="dataunbound", password="numbers_unbound", work_id=2, card=card1,
preapproval_amount='10', premium_id='150')
# we should have an exception when the charge was attempted
self.assertTrue(charge_exception is not None)
# expect to have 2 events (there is a possibility that someone else could be running tests on this stripe account at the same time)
# events returned sorted in reverse chronological order.
self.assertEqual(len(events), 2)
self.assertEqual(events[0].type, 'charge.failed')
self.assertEqual(events[1].type, 'customer.created')
# now feed each of the events to the IPN processor.
ipn_url = reverse("HandleIPN", args=('stripelib',))
for (i, event) in enumerate(events):
r = self.client.post(ipn_url, data=json.dumps({"id": event.id}), content_type="application/json; charset=utf-8")
self.assertEqual(r.status_code, 200)
def test_good_bad_cc_scenarios(self):
self.good_cc_scenario()
self.bad_cc_scenario()
# look at emails generated through these scenarios
#print len(mail.outbox)
#for (i, m) in enumerate(mail.outbox):
# print i, m.subject
#0 [localhost:8000] Thank you for supporting Pro Web 2.0 Mashups at Unglue.it!
#1 [localhost:8000] Thanks to you, the campaign for Pro Web 2.0 Mashups has succeeded!
#2 Stripe Customer (id cus_0gf9OjXHDhBwjw; description: RaymondYee) created
#3 [localhost:8000] Thank you for supporting Moby Dick at Unglue.it!
#4 [localhost:8000] Someone new has wished for your work at Unglue.it
#5 [localhost:8000] Thanks to you, the campaign for Moby Dick has succeeded! However, your credit card charge failed.
#6 Stripe Customer (id cus_0gf93tJp036kmG; description: dataunbound) created
self.assertEqual(len(mail.outbox), 7)

View File

@ -2,6 +2,10 @@ from notification import models as notification
from django.dispatch import Signal from django.dispatch import Signal
transaction_charged = Signal(providing_args=["transaction"]) transaction_charged = Signal(providing_args=["transaction"])
transaction_failed = Signal(providing_args=["transaction"])
transaction_refunded = Signal(providing_args=["transaction"])
transaction_disputed = Signal(providing_args=["transaction"])
pledge_created = Signal(providing_args=["transaction"]) pledge_created = Signal(providing_args=["transaction"])
pledge_modified = Signal(providing_args=["transaction", "up_or_down"]) pledge_modified = Signal(providing_args=["transaction", "up_or_down"])
credit_balance_added = Signal(providing_args=["amount"]) credit_balance_added = Signal(providing_args=["amount"])

View File

@ -16,6 +16,8 @@ from regluit.payment.models import Account, Transaction
from regluit.payment.parameters import PAYMENT_HOST_STRIPE from regluit.payment.parameters import PAYMENT_HOST_STRIPE
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_ERROR, PAYMENT_TYPE_AUTHORIZATION, TRANSACTION_STATUS_CANCELED from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_ERROR, PAYMENT_TYPE_AUTHORIZATION, TRANSACTION_STATUS_CANCELED
from regluit.payment import baseprocessor from regluit.payment import baseprocessor
from regluit.payment.signals import transaction_charged, transaction_failed
from regluit.utils.localdatetime import now, zuluformat from regluit.utils.localdatetime import now, zuluformat
import stripe import stripe
@ -391,7 +393,7 @@ class StripeErrorTest(TestCase):
self.fail("Attempt to create customer did not throw expected exception.") self.fail("Attempt to create customer did not throw expected exception.")
except stripe.CardError as e: except stripe.CardError as e:
self.assertEqual(e.code, "card_declined") self.assertEqual(e.code, "card_declined")
self.assertEqual(e.message, "Your card was declined.") self.assertEqual(e.message, "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
@ -747,7 +749,6 @@ class Processor(baseprocessor.Processor):
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':
from regluit.payment.signals import transaction_charged
logger.info("charge.succeeded webhook for {0}".format(ev_object.get("id"))) logger.info("charge.succeeded webhook for {0}".format(ev_object.get("id")))
# try to parse description of object to pull related transaction if any # try to parse description of object to pull related transaction if any
# wrapping this in a try statement because it possible that we have a charge.succeeded outside context of unglue.it # wrapping this in a try statement because it possible that we have a charge.succeeded outside context of unglue.it
@ -766,7 +767,20 @@ class Processor(baseprocessor.Processor):
logger.warning(e) logger.warning(e)
elif action == 'failed': elif action == 'failed':
pass logger.info("charge.failed webhook for {0}".format(ev_object.get("id")))
try:
charge_meta = json.loads(ev_object["description"])
transaction = Transaction.objects.get(id=charge_meta["t.id"])
# now check that account associated with the transaction matches
# ev.data.object.id, t.pay_key
if ev_object.id == transaction.pay_key:
logger.info("ev_object.id == transaction.pay_key: {0}".format(ev_object.id))
else:
logger.warning("ev_object.id {0} <> transaction.pay_key {1}".format(ev_object.id, transaction.pay_key))
# now -- should fire off transaction_charged here -- if so we need to move it from ?
transaction_failed.send(sender=self, transaction=transaction)
except Exception, e:
logger.warning(e)
elif action == 'refunded': elif action == 'refunded':
pass pass
elif action == 'disputed': elif action == 'disputed':