How to listen to Stripe webhooks and use them in your dev environment

Reading Time: 4 minutes

A long time ago, in November 16, 2010 to be exact, my partner Jonathan Tapia explained in his post how to easily receive payments with Stripe.

This time, rather than setting up Stripe to receive payments, I would like to show you:

  • A simple Javascript object to encapsulate the Stripe logic and how to use $.proxy() to handle callbacks given an specific context, which in our case is the instance of our object.
  • How to register Stripe Webhooks using stripe_event; and
  • How to use these webhooks on our local environment using services like Ultrahook
Considerations:
  • I will take Tapia's post as a base and from there I will continue with the configuration
  • I'm considering you already have a Rails application as we will have Stripe logic on the backend.

If you're like me and prefer to dive into the code, the repo is: Stripe Whooks

Also, you could play with it in Heroku: Heroku Stripe Whooks; is on test mode at Stripe, so you could use any of the test cards listed here

Javascript

Let's take a simple payment form as an example:

views/purchases/new.html.haml
...
= form_tag purchases_path, id: 'payment-form' do
  = hidden_field_tag :amount, 100000
  = hidden_field_tag :description, 'Panda Product'

  %label Contact Email
  = email_field_tag :email, ‘’, placeholder: ‘your@email.com'

  %label Credit Card Number:
  = text_field_tag 'number', '', data: { stripe: 'number' }

  %label Name on Credit Card:
  = text_field_tag 'billing_address[name]', '', data: { stripe: 'name' }

  %label Security Code
  = text_field_tag 'cvc', '', data: { stripe: 'cvc' }

  %label Expiration Date:
  = select_month Date.today,
    { add_month_numbers: true, use_short_month: true },
    data: { stripe: 'exp-month'}
  = select_year Date.today,
    { end_year: 2050 },
    data: { stripe: 'exp-year' }

  = submit_tag ‘Purchase'
  ...

Following Stripe's documentation, it invites us to declare the functions directly into the DOM but, in this case, I decided to create an Object to handle everything from there so, at the end of our new view, I create a new instance:

views/purchases/new.html.haml:47
...
- content_for :scripts do
  :javascript
    $(document).ready(function() {
      new stripeTest.Payment({
        stripeKey: "#{STRIPE_PUBLIC_KEY}",
        form: $('#payment-form') 
      });
    });

As you may notice, I'm using a namespace called stripeTest, because I have functionality to handle the Shipping Address too. You could take a look at it at ShippingActivator.

What's important here is that all the logic is inside the Payment class; this helps me, apart from other things, on doing easier jasmine testing.

Note: If you ask, I just copied the class to this example repo without modifying it, as I implemented this in a private repo here at the company, and it's working like a charm.

$.proxy()

My payment class looks like this:

assets/javascripts/stripe_test/payment.js.coffee
...
stripeTest.Payment = (->
  Payment = (options) ->
    Stripe.setPublishableKey options.stripeKey
    @$form = options.form
    @$container = @$form.parents('section')
    @$form.submit => @retrieveStripeToken()
    @

  Payment::retrieveStripeToken = ->
    @disableSubmit true
    Stripe.card.createToken @$form, $.proxy(@responseHandler, @)
    false

  Payment::responseHandler = (status, response) ->
    if response.error then @renderError(response.error) else @submitForm(response.id)

  Payment::renderError = (error) ->
    $paymentError = $('<div />', class: 'payment-errors' ).html error.message
    @$form.prepend $paymentError
    @fadeRemoveError $paymentError
    @disableSubmit false

  Payment::fadeRemoveError = ($error) ->
    ...

  Payment::renderSuccess = (charge) ->
    ...

  Payment::submitForm = (tokenId) ->
    input = $('<input />', { value: tokenId, name: 'stripeToken', type: 'hidden' })
    @$form.append input
    @sendPayment @$form.attr('action'), @$form.serialize()

  Payment::sendPayment = (url, data) ->
    ...

  Payment::disableSubmit = (disabled) ->
    ...

  Payment
)()

I left out a lot of code to reduce the length of the post, however, you can see the full code in the Payment file.

In the code provided by Stripe, we bind the submit and trigger the actions directly, here, I'm binding it in the initializer of the class (line: 8), then, I'm retrieving the token (as dictated by Stripe code) and sending it to a callback function:

$.proxy(@responseHandler, @)

I'm using the $.proxy() because I want to trigger that function inside the context of our Payment instance, if I remove the $.proxy, it will look for a globally declared function (like in the Stripe code).

Stripe Event

As you may know, Stripe offers us the functionality of automatically sending a payment ticket to the registered customer email, you only need to upload a logo, select some colors and voilà. But, what happens if you aren't registering customers on Stripe? What if you're just making charges to the card? or what happens if you want to have your own email template? or log the email sent action?.

Well, this could be done by using Stripe Webhooks and 'hooking' on to them it's pretty easy with stripe_event.

OK, let's walk through a simple implementation for sending the tickets to our beloved customers:

  • First, let's add it to our Gemfile and bundle:
    gem 'stripe_event'
    
  • And configure Stripe inside an initializer:
    config/initializers/stripe.rb
    Stripe.api_key = ENV['STRIPE_API_KEY’]
    STRIPE_PUBLIC_KEY = ENV['STRIPE_PUBLIC_KEY’] 
    StripeEvent.subscribe 'charge.succeeded' do |event|
      ReceiptMailer.send_receipt(event.data.object).deliver
    end
    

    Nothing out of the ordinary, we set up the Stripe.api_key and STRIPE_PUBLIC_KEY (which is the one we use on our stripeTest.Payment class), and we subscribe to charge.succeeded. More at Stripe events.

    So, as soon as Stripe responds with a succeed charge, we'll send an email with the ticket.

  • Besides this, we have to mount the engine of StripeEvent like this:
    config/routes.rb
    StripeTest::Application.routes.draw do
      resources: purchases, only: [:new, :create]
      mount StripeEvent::Engine => '/stripe-WebHooks'
      root to: 'purchases#new'
    end
    
  • And last but not least, you'll need to go to your Stripe Account and setup your webhooks:

    image alt

And that's all, we're listening for any succeed charge. I'm not showing here the PurchasesController or the PurchasesService as you can go and take a look at them and your implementation will vary depending on your requirements.

Ah, I almost forgot, what happens if you want to receive the hooks on your local environment? Well, there are many options but one that is easy to setup is Ultrahook

Ultrahook

I could guide you through the installation and so, but it's pretty straightforward, you only need to create an account and follow the steps Ultrahook shows.

After you have everything working, you would only need to run:

$ ultrahook stripe http://localhost:3000/stripe-webhooks

This will authenticate you and start forwarding any POST request to Ultrahook to your local environment:

http://stripe.mumoc.ultrahook.com -> http://localhost:3000/stripe-webhooks

In my case, Ultrahook will be listening to stripe.mumoc.ultrahook.com for any requests and it will be redirecting them to localhost:3000/stripe-webhooks, because I mounted the StripeEvent::Engine at 'stripe-webhooks'.

All that is left is to register a webhook on Stripe pointing to the Ultrahook URL and that's it.

Conclusion

As Tapia said in his post, Stripe is a simple and fast way to receive payments and, having a way to control the webhooks on our app, we can easily make interesting implementations.

Thanks for reading!

0 Shares:
You May Also Like