jaymedavis/stripe.net — pull request #15 by jslaybaugh (2012-02-25 21:48:55+00:00)
Title: Events

Shortly after I did that other work on the webhooks, Stripe decided to completely overhaul webhooks and replace that with their new event model: https://stripe.com/docs/api#events

It really is a better model and this branch has the changes I put in place so that I could get it working for me. Quick summary of what is here:

* Removed all old stuff related to webhooks
* Added new entities for events
* Added new `StripeEventService` class for retrieving single/multiple events via API (more on this below)
* Added new `StripeEventUtility` static class for doing the work when their events fire to your server's webhook url.

First, a quick summary of how events work now. *Every* time something happens in Stripe, an event is fired and it hits your server's webhook url that you put in your account settings. This could be a new customer created, a new invoice created, a payment failing, anything. There is a complete list in their docs.

So, the `StripeEventUtility` is a class that just parses the JSON they send you on each one of those events. Here is an example of it in use in my code (this is the most elegant way I could find to deal with it, but perhaps you'll find something better):

// GET: /Stripe/
[HttpPost]
public void Index()
{
var json = new StreamReader(Request.InputStream).ReadToEnd();
var ev = StripeEventUtility.ParseEvent(json);

if (ev.Type.MatchesTrimmed("invoice.created"))
{
// for us, this is to calculate usage. It will happen immediately, but we exit out in that case
StripeInvoice invoice = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
if (invoice.Paid.HasValue && invoice.Paid.Value) return; // no need to do anything on the first invoice
CalculateAndRecordUsage(invoice);
}
else if (ev.Type.MatchesTrimmed("invoice.payment_succeeded"))
{
// for us, this happens when a customer's monthly invoice succeeds as expected
StripeInvoice invoice = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
SendPaymentNotification(invoice, true);
}
else if (ev.Type.MatchesTrimmed("invoice.payment_failed"))
{
// for us, this happens when a customer's monthly invoice fails to charge successfully
StripeInvoice invoice = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
SendPaymentNotification(invoice, false);
}
else if (ev.Type.MatchesTrimmed("customer.subscription.created"))
{
StripeSubscription sub = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
// for us, this happens when a customer is new or reactivates
//if (sub.Status.MatchesTrimmed("active") || sub.Status.MatchesTrimmed("trialing"))
//{
// SendWelcomeNotification(sub);
//}
}
else if (ev.Type.MatchesTrimmed("customer.subscription.updated"))
{
StripeSubscription sub = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
// for us, this happens when a customer is delinquent
if (sub.Status.MatchesTrimmed("unpaid"))
{
SendSuspendedNotification(sub);
}
}
else if (ev.Type.MatchesTrimmed("customer.subscription.deleted"))
{
// for us, this happens when a customer cancels their account
StripeSubscription sub = StripeEventUtility.ParseEventDataItem(ev.Data.Content);
SendCancelationNotification(sub);
}

}

As you can see, it just takes what they send me, parses it so that it can then find out what type of event it is. From there we conditionally map it to the appropriate type for each object and then do whatever it is we need to do. If we don't want to handle an event, we just don't do anything. All Stripe cares about is a 200 response. If you send a 500 response, it will retry hourly for up to 3 days.

That is the part that I'm currently using and it seems to be working fairly well. I also created the `StripeEventService` in the same fashion as all the other services you'd done previously. The only problem is that this one allows more options on the `List` method and quite frankly, I couldn't come up with a good way to implement their options. If you look at https://stripe.com/docs/api#list_events, you'll see that the `created` parameter can either be a date (typical stripe integer format) or a hash of dates where you can specify things like less than, greater than, etc. I couldn't come up with a quick/intelligent way of taking a .net datetime (or an object of datetimes) and converting it to a integer and appending it with your current `ApplyAllParameters` method. I started by creating a `StripeEventSearchOptions` but just ended up throwing `NotImplemented` and moving on since I actually didn't need the code for anything in my app.

I also didn't write any tests for the service since it wasnt working yet (and the utility methods really don't need a test as they are just mapping JSON).

Anyway, I thought I'd submit the pull request for this now just so the work I'd done was there for someone else someday -- but its still a work in progress.

I saw your tweet this week that you are now at AgileZen. Congratulations, it'll be awesome to work with and learn from people like Nate and the rest of his team. I don't know if you'll still have a need to work with Stripe on a regular basis or not, but we're certainly hooked into them pretty deep now, so I'm sure I'll keep pushing stuff as I stumble across it.

Oh, and one last thing. I kept all this in a separate branch from my live-test stuff that you didn't want to include last time. I need it for what we're doing still, so I'm just going to keep running off my local code, but this way, I can add stuff like this as it comes along but still keep it separate from the stuff you didn't want to implement -- but I'm still hopeful that someday I'll convince you to include the live-test stuff in there :-)

-- Jorin