A Journey in Payment World – Part 3: what is happening here?

A Journey in Payment World

This is a series about integrating a payment system into your web application. While inspired by our own experience, and so Stripe and Ruby oriented, most of the problems and solutions are probably useful in other technical environments.

About events

This is the third installation in our “Journey in Payment World series”. The first one dealt with picking a provider and organizing yourself, the second was about the minimal setup needed to accept payments from your customers. I suggest you to take a quick look at those two, if not already done.

This third post is dedicated to something that does not impact your end user, but is very important anyway: keeping up to date with what is happening in your payment system. In other words: how Stripe communicate events, and what to do with them.

What are Stripe events

Stripe will generate events for almost anything that happens to customers, cards or transfers, for example:

  • Customer (created, updated, deleted)
  • Customer.Card (created, updated, deleted)
  • Charge (succeeded, failed, refunded)
  • Dispute (created, updated, closed as win or lost)

You can see the full list on Stripe’s site: https://stripe.com/docs/api#event_types.

Retrieving Stripe events

The simplest way to see Stripe events is just by using their provided Web interface. You’ll find there a list of the events with their date and id:

1events

A simple click shows their data in the form of a JSON:

2success

The second option is of course to retrieve those events using Stripe’s various API. In Ruby, this means a call like:

require ‘stripe’
Stripe.api_key = ‘sk_test_thisisnotarealkey123456’

event = Stripe::Event.retrieve(‘evt_37tRE2Fuvjbbvy’)

A third and better option is to use Stripe’s Webhooks, which reverse the process, having Stripe calls you when something happens. Webhooks are defined in your Stripe account.

3webhooks

As you see, you can define hooks for both the test and live environment, and can actually define several for each.

Stripe will then call the given URL for each event, with a JSON in the body of the request. This can be routed to a controller’s method where you can retrieve the event information:

class StripeEventsController

  def receive
    stripe_id = params[:id]
    stripe_type = params[:type]
    ...
  end

end

One of the difficulties is to be clear about what the JSON sent looks like and what it contains (as you’ll want to do something with them). RequestBin is a free service that can generate an URL for you that you can use as webhook receiver. It will then show you the details of any incoming request:

3requestbin

As Stripe allows you to generate events from their (test) web application, it is quite easy to generate a given event, look at it on the RequestBin and then implement the needed behaviour.

Now that we know what events are and how to access and receive them, time to do something with them.

What to do with which events

Validate

The first step when receiving an event is actually to confirm it has well be issued by Stripe. Even if your webhook URL is not public, a third party may be able to find it and start sending malicious information there. In addition of the URL not being public, it could be wise to have Stripe call your hook with a user password or a token (/stripe/event/auth/#{configtoken} or https://user:password@myserver.com).

As we are talking about billing information here, this is not something you want to risk. Stripe’s documentation advises a very simple solution.

When you get an event from the hook, just take it’s id, disregarding the whole content, and use Stripe’s API to get it back. This would confirm that it is legit (and intended for you):

class StripeEventsController

  def receive
    stripe_id = params[:id]
    event = Stripe::Event.retrieve(stripe_id)
    ...
  end

end

Store

As Stripe itself does save the event and as you can retrieve them, you may ask what would be the point to store them in your own application. Well, there is actually several good reasons to do this.

Retention: As per the Stripe doc: “Right now, we only guarantee access to events for 30 days”. Those events represent billing information, you most probably want to keep them (for any kind of dispute that may arise). In the payment world, 30 days is a short period – the real charge may not have been done yet while the credit card or customer related events will already be gone from Stripe’s log.

You probably want to save the event in an application specific model, with explicit fields for the most important informations (id, type but also the link to your intern customer or account model, for the events that are linked to one). Extracting Stripe’s customer token may also be a good idea. The rest can go in a payload field (as serialized JSON), just in case it would be needed to retrieve something more specific on a later date – at that point, what you really need is to be sure to store all the information before it is too late.

Avoid replay: Once we’ll start to act based on certain Stripe events, you want to be sure never to act twice on the same events. Storing them is a good way to avoid unwanted “replays”.

Query: Billing information are quite interesting business wise (e.g. for statistics, for audit), so having them in your database make it much easier to extract any information you may want.

class StripeEventsController

  def receive
    stripe_id = params[:id]
    stripe_event = Stripe::Event.retrieve(stripe_id)

    app_event = ApplicationEvent.new(stripe_event)

    app_event.save
  end

end

Act

Finally, we most probably want to do more than just log the events. This is of course a business decision, but a simple case is the notifications: you probably want to be informed of some specific events. Among the usual suspects, you certainly want to get a mail when a payment is refused or disputed. Notifications are a good middle ground between being purely manual (and risking to miss some important action) and a fully automated system (which is costly). Notifying your user is probably good also (but those are two separated use cases), like to tell them if you cannot charge their card.

class StripeEventsController

  def receive
    stripe_id = params[:id]
    stripe_event = Stripe::Event.retrieve(stripe_id)

    app_event = ApplicationEvent.new(stripe_event)
    app_event.save

    if app_event.type == charge.failed
      Mailer.notify_admin(“Charge failed for #{app_event.customer}”)
      Mailer.notify_customer(‘PullReview: We failed to charge your Credit Card’)
    end
  end

end

Finally, some events may trigger automated action, like creating an invoice (and possibly sending it) when a customer is charged, or checking the total of his payment to see if he’s eligible for a special gift, …

Testing

With so many moving parts, testing is clearly of utmost importance. Of course, you probably don’t want to call Stripe (even the test environment) in your unit tests, as dependency to an external service would make your tests slow cumbersome. A good way to avoid this is to get a swappable event retriever, so you can retrieve them from Stripe in production:

class StripeEventsController

  def receive
    stripe_id = params[:id]
    stripe_event = ApplicationEvent::retrieve(stripe_id)

    app_event = ApplicationEvent.new(stripe_event)
    ...
  end

end

class ApplicationEvent
  DEFAULT_RETRIEVER = ->(stripe_id) { Stripe::Event.retrieve(stripe_id) }

  def self.retriever=(retriever)
    @retriever = retriever
  end

  def self.retriever
    @retriever || DEFAULT_RETRIEVER
  end

  def self.retrieve(stripe_id)
    ApplicationEvent.retriever.call(stripe_id)
  end
end

But mock it easily in your tests:

ApplicationEvent.retriever = ->(stripe_id) { Stripe::Event.construct_from(charge_dispute_created) }

Stripe::Event.construct_from(hash) allows to easily build a Stripe::Event yourself from a simple hash. The retriever principle was inspirer by this.

Next

With this, you should get a pretty good idea of what is happening on your billing system, and already be able to act upon some of the specific events (for some business ideas and examples email on this, see this great post by imzank). Even better, should anything happen, you have all information stored internally – audit capability is probably one of the the most important use case for a billing system.

Next part will be all about subscriptions and… Cylons – They have a Plan.

Enhanced by Zemanta




2 thoughts on “A Journey in Payment World – Part 3: what is happening here?

    1. Martin Post author

      Hi John,
      Thanks to you first for providing RequestBin – has been really useful, and will certainly be again in the future. Runscope looks great – is it a replacement/improved RequestBin?

      Martin

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>