I'll do a quick round up of various libraries and implementation options here. This is based on an evaluation from early 2017, but these things aren't changing so much at present.
There are 4 wrapper libraries available on Github for interacting with the Stripe API.
- clj-stripe
- elephant
- prachetasp/stripe-clojure (not evaluated here)
- racehub/stripe-clj (not evaluated here)
clj-stripe
clj-stripe is a library that's done in a slightly different style from many. It
features heavy use of multimethods, and wrapping Stripe operations in record
types. The operations are rather verbose. To do an operation you first
"create" an object representing the operation and then execute
it. It has the
benefit of not relying on ambient state. In the end I didn't use this library,
because it was too cumbersome.
I ran into some problems with old TLS versions when running on Java 7. However, hopefully, Java 7 is a distant memory by the time you read this.
elephant
Elephant is a supposedly "modern" library. In practice I found that it was
somewhat clumsy to work with. It uses global state, in the form of the
set-api-key!
function. It delegates to the Java wrapper library, which then
wraps the REST API.
There are some weird things in working with the Elephant API. When you submit calls to the API, you often use string keys in the map you are passing:
(ecr/create {"source" token "email" email})
This is a little abnormal for a Clojure API and seems to be an indication of an abstraction leak. (Actually Clojure is full of abstraction leaks, so it wouldn't be alone, but I found this one rather distracting.)
But, when you get results from the Elephant APIs, the keys are automatically "keywordized".
There are also some cases where you seem to need to do unnecessary calls in order to perform certain actions, as in this example:
(defn change-payment-source! [stripe-customer-id token]
(ecr/update (ecr/retrieve stripe-customer-id) {"source" token}))
Notice that we do two REST operations here. There's surely little point in this network traffic (not that it matters so much in this case). I don't know the REST API well enough to comment on if it's possible to do this in a single network request, but I can't see why it wouldn't be possible? However, Elephant requires a specially-tagged map that was returned from a previous Elephant call as the first argument -- it can't take a plain ID.
The representation of result maps is also slightly confusing in itself -- the maps seem to contain a fully "mapped" keywordized version of the data, as well as a full string copy of the actual response? This is a tad awkward when trying to read the data at the REPL.
Things that it wasn't possible to do with these
- Neither library has wrapper code for Stripe events, which you are pretty much guaranteed to need if you need to handle subscriptions.
- There's no way to get the total count of GET-collection operations.
- There's no way to manually trigger payment of an invoice. (
Invoice.pay()
in the Java API.) This can be useful when testing. - There's no automatic paging of results, which can be a touch tricky to implement: for instance it's easy to just block until everything is returned and process everything in a loop. But how long are you willing to block for? Or would you prefer to pass some kind of callback or async action and process results a page at a time? The possibilities are endless.
Lessons learned
Elephant is quite easy to use for basic use cases. I can't remember where I read this before, but some previous author said not to use wrappers when interacting with external REST APIs in Clojure, and I almost tend to agree. Overall, the time I spent dealing with the various quirks and imbrications of these libraries probably outweighed the time it would take to create my own wrapper. And don't forget that you have even more options: as well as the two libraries not surveyed here, you can also use the Java API through introspection, or go directly to REST. If I did it again, I think I'd go directly to REST. And so probably end up creating library number 5...