1) Now fire the transaction_charged signal in the Stripe webhook handling

2) add WebTest to requirements
3) have a check on possible Stripe events
pull/1/head
Raymond Yee 2012-11-05 10:44:24 -08:00
parent f6bf774f2c
commit 7ea5ac7ca6
3 changed files with 48 additions and 15 deletions

View File

@ -482,8 +482,6 @@ class PaymentManager( object ):
if p.success() and not p.error():
transaction.pay_key = p.key()
transaction.save()
logger.info("execute_transaction Success")
transaction_charged.send(sender=self, transaction=transaction)
return True
else:

View File

@ -12,7 +12,7 @@ from django.conf import settings
from django.http import HttpResponse
from django.core.mail import send_mail
from regluit.payment.models import Account
from regluit.payment.models import Account, Transaction
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 import baseprocessor
@ -20,6 +20,20 @@ from regluit.utils.localdatetime import now, zuluformat
import stripe
# as of 2012.11.05
STRIPE_EVENT_TYPES = ['account.updated', 'account.application.deauthorized',
'charge.succeeded', 'charge.failed', 'charge.refunded', 'charge.disputed',
'customer.created', 'customer.updated', 'customer.deleted',
'customer.subscription.created', 'customer.subscription.updated',
'customer.subscription.deleted', 'customer.subscription.trial_will_end',
'customer.discount.created', 'customer.discount.updated', 'customer.discount.deleted',
'invoice.created', 'invoice.updated', 'invoice.payment_succeeded', 'invoice.payment_failed',
'invoiceitem.created', 'invoiceitem.updated', 'invoiceitem.deleted',
'plan.created', 'plan.updated', 'plan.deleted',
'coupon.created', 'coupon.updated', 'coupon.deleted',
'transfer.created', 'transfer.updated', 'transfer.failed',
'ping']
logger = logging.getLogger(__name__)
# http://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904
@ -461,8 +475,7 @@ class StripeErrorTest(TestCase):
except stripe.CardError as e:
self.assertEqual(e.code, "missing")
self.assertEqual(e.message, "Cannot charge a customer that has no active card")
class PledgeScenarioTest(TestCase):
@classmethod
@ -628,10 +641,11 @@ class Processor(baseprocessor.Processor):
try:
# useful things to put in description: transaction.id, transaction.user.id, customer_id, transaction.amount
charge = sc.create_charge(transaction.amount, customer=customer_id,
description="t.id:{0}\temail:{1}\tcus.id:{2}\tc.id:{3}\tamount:{4}".format(transaction.id,
transaction.user.email, customer_id, transaction.campaign.id,
transaction.amount) )
description=json.dumps({"t.id":transaction.id,
"email":transaction.user.email,
"cus.id":customer_id,
"tc.id": transaction.campaign.id,
"amount": float(transaction.amount)}))
except stripe.StripeError as e:
# what to record in terms of errors?
@ -685,10 +699,10 @@ class Processor(baseprocessor.Processor):
# 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 ProcessIPN(self, request):
# retrieve the request's body and parse it as JSON in, e.g. Django
try:
logger.info("request.body: {0}".format(request.body))
event_json = json.loads(request.body)
except ValueError, e:
# not able to parse request.body -- throw a "Bad Request" error
@ -707,17 +721,21 @@ class Processor(baseprocessor.Processor):
return HttpResponse(status=400)
else:
event_type = event.get("type")
if event_type not in STRIPE_EVENT_TYPES:
logger.warning("Unrecognized Stripe event type {0} for event {1}".format(event_type, event_id))
# is this the right code to respond with?
return HttpResponse(status=400)
# https://stripe.com/docs/api?lang=python#event_types -- type to delegate things
# parse out type as resource.action
# use signals?
try:
(resource, action) = re.match("([^\.]+)\.(.*)", event_type).groups()
(resource, action) = re.match("^(.+)\.([^\.]*)$", event_type).groups()
except Exception, e:
logger.warning("Parsing of event_type into resource, action failed: {0}".format(e))
return HttpResponse(status=400)
try:
ev_object = event.get("data").get("object")
ev_object = event.data.object
except Exception, e:
logger.warning("attempt to retrieve event object failed: {0}".format(e))
return HttpResponse(status=400)
@ -731,8 +749,22 @@ class Processor(baseprocessor.Processor):
if action == 'succeeded':
from regluit.payment.signals import transaction_charged
logger.info("charge.succeeded webhook for {0}".format(ev_object.get("id")))
# figure out how 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
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_charged.send(sender=self, transaction=transaction)
except Exception, e:
logger.warning(e)
elif action == 'failed':
pass
elif action == 'refunded':
@ -740,6 +772,7 @@ class Processor(baseprocessor.Processor):
elif action == 'disputed':
pass
else:
# unexpected
pass
elif resource == 'customer':
if action == 'created':

View File

@ -4,6 +4,8 @@ MySQL-python==1.2.3
Pillow==1.7.7
Pyzotero==0.9.51
South==0.7.6
WebOb==1.2.3
WebTest==1.4.0
amqplib==1.0.2
anyjson==0.3.3
billiard==2.7.3.12