Stripe Haskell packages

This week we made some billing changes: There’s now an annual billing option in addition to monthly, and we now offer region-based discounts. We also took some time to publish a lot of the code that we use to work the Stripe API.

You can find the code in the typeclasses/stripe repository on GitHub. It contains four packages:

  • stripe-concepts is a minimal package that defines a common set of types for working with the Stripe API.
  • stripe-signature is for parsing and verifying the Stripe-Signature HTTP header that Stripe includes when it sends an event to your webhook.
  • stripe-scotty provides support for writing a Stripe webhook server using the scotty web server library.
  • stripe-wreq helps you make requests to the Stripe API in conjunction with the wreq web client library.

This post is about the design of these libraries and lessons learned from writing them.

Scope

When writing a wrapper for a third-party REST API, there’s often a temptation to try to encapsulate the underlying API entirely – to make a Haskell library that can be understood and used on its own without consulting the Stripe API reference and without dealing with HTTP or JSON. We think that would have been a mistake here.

The size of Stripe’s API is considerable, and it has a terrific amount of documentation. No matter how much a library author writes about Stripe, it won’t be as much or as good as what Stripe has already written about it. Given that you’re going to start by reading their docs anyway, what good is trying to cover it up?

We’re not interested in writing something that’s going to completely control your experience using their billing platform; we just want to share the bits of code that were hardest to write and that we think the most people will find useful.

Concepts

This is an interesting aspect of Haskell package design that doesn’t come up as much in some other languages: There’s a surprising amount of value in creating packages that contain little more than some simple type or class definitions.http-types and pandoc-types are notable examples of this sort of package. They set the stage for larger packages that use them.

Rather than suffix the name with -types, we like to use the word ‘concepts’ to describe a package or module like this. The stripe-conceptsStripe.Concepts package defines Stripe-related concepts like customers, subscriptions, etc. that the other packages do more complicated things with.

One important Stripe concept is that there’s a test environment, and so every action that occurs is happening either in live mode (involving real money) or in test mode (having no real effect). So in stripe-concepts we defined this type:

data Mode = LiveMode | TestMode

Each of the other packages could have defined its own separate Mode type, but then we’d end up having to write functions that convert between them. Instead we have this little concepts package that sits at the core of the dependency hierarchy. Its own dependency set is minimal, so any package can have a dependency on stripe-concepts without incurring too much extra weight.

Wreq

We called the main package stripe-wreqStripe.Wreq because it’s based on the wreq HTTP client library and makes no attempt to abstract over that.

A typical use of this module is:

  1. Run get, post, or delete to get a WreqResponse.
  2. Use wreqResponse to convert the Wreq response to a Response.
  3. Use responseValue to obtain the response payload as an Aeson Data.Aeson.Value (or an Error if the request was not successful).

So for example, creating a subscriptionStripe API doc for creating a subscription might look something like this:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Aeson
import           Data.Coerce
import           Data.Text       (Text)
import qualified Stripe.Concepts as Stripe
import qualified Stripe.Wreq     as Stripe
import           Stripe.Wreq     (FormParam (:=))

createSubscription
    :: Stripe.ApiSecretKey -> Stripe.CustomerId -> Stripe.PlanId
    -> IO (Either Stripe.Error Data.Aeson.Value)

createSubscription secretKey customerId planId =
  do
    session <- Stripe.newAPISession

    response <- fmap Stripe.wreqResponse
        (Stripe.post session secretKey request)

    Stripe.responseValue response

  where
    request =
        Post
        { postPath = ["subscriptions"]
        , postParams =
            [ "customer" := (coerce customerId :: Text)
            , "items[0][plan]" := (coerce planId :: Text)
            ]
        }

The result we get (if the action succeeded) is an Aeson value, from which we can then read whatever attributes we care about.

Aeson

We’re not interested in deserializing the entire response into some large Haskell record type, because often there are dozens of fields but we only care about perhaps one or two of them. And we find Aeson’s decoding functions and FromJSON class overly complicated.

Instead, we usually inspect Aeson values by using the Kleisli composition operator (>=>)Documentation for >=> in Control.Monad to compose functions of type a -> Maybe b. In particular, these functions come in handy often:

aesonObject :: Data.Aeson.Value -> Maybe Data.Aeson.Object
aesonObject (Data.Aeson.Object x) = Just x
aesonObject _ = Nothing

aesonAttr :: String -> Data.Aeson.Value -> Maybe Data.Aeson.Value
aesonAttr x = aesonObject >=> Data.HashMap.Strict.lookup (Data.Text.pack x)

aesonText :: Data.Aeson.Value -> Maybe Text
aesonText (Data.Aeson.String x) = Just x
aesonText _ = Nothing

aesonBool :: Value -> Maybe Bool
aesonBool (Aeson.Bool x) = Just x
aesonBool _ = Nothing

Continuing the last example, suppose we just created a new subscription and val is the response that Stripe gave us. Let’s say we’re interested in knowing what product ID the subscription plan belongs to. Look at the API docs to see where this information is present in the response. We can obtain that value as

fmap Stripe.ProductId ((aesonAttr "plan" >=> aesonAttr "product" >=> aesonText) val)
    :: Maybe ProductId

Signature verification

When your server receives a webhook event, you need some way to verify the authenticity of the request to ensure that you are not acting upon forged events originating from some source other than Stripe. This is where the Stripe-Signature header comes in.Stripe documentation for signature verification

This is a fairly small amount of code, but we felt it deserved its own library because this is the sort of careful task that one really doesn’t want to have to implement more than once.

Our stripe-signatureStripe.Signature library’s role is to parse the header containing the signature, and to assemble the bytes in the right format to form the input to the cryptographic scheme. The heavy lifting is then performed by cryptonite, Haskell’s favorite crypto library.

Scotty

The stripe-scottyStripe.Scotty library adds one more layer of convenience if your server is built on scotty. All you have to do is apply the requireSig function, giving it your webhook secret as an argument, and you’ll get a Scotty action that rejects invalid webhook requests. Run this as the first thing in your webhook-handling ActionM.