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:
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-wreq
Stripe.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:
- Run
get
,post
, ordelete
to get aWreqResponse
. - Use
wreqResponse
to convert the Wreq response to aResponse
. - Use
responseValue
to obtain the response payload as an AesonData.Aeson.Value
(or anError
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
Data.Aeson.Object x) = Just x
aesonObject (= Nothing
aesonObject _
aesonAttr :: String -> Data.Aeson.Value -> Maybe Data.Aeson.Value
= aesonObject >=> Data.HashMap.Strict.lookup (Data.Text.pack x)
aesonAttr x
aesonText :: Data.Aeson.Value -> Maybe Text
Data.Aeson.String x) = Just x
aesonText (= Nothing
aesonText _
aesonBool :: Value -> Maybe Bool
Aeson.Bool x) = Just x
aesonBool (= Nothing aesonBool _
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-signature
Stripe.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-scotty
Stripe.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
.