dmjio / stripe

:moneybag: Stripe API
http://hackage.haskell.org/package/stripe-haskell
177 stars 74 forks source link

Add support for stripe pagination #89

Open nmattia opened 6 years ago

nmattia commented 6 years ago

It would be nice if the library supported pagination natively, so that the user doesn't have to do it themself. The wreq library, for instance, has some basic support for this.

I'm not sure what exactly this should look like, and if the library wants to provide such higher-level helpers at all. Just in case, here's a naive conduit implementation:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}

{-|

   Copyright: 2017 (C) AlphaSheets, Inc
   Description: Conduit support for 'Web.Stripe'

-}

module Web.Stripe.Conduit where

import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import Data.Aeson
import Data.Conduit
import Safe (lastMay)
import Web.Stripe (stripe, (-&-))

import qualified Data.Conduit.List as CL
import qualified Web.Stripe as Stripe
import qualified Web.Stripe.Customer as Stripe

-- | Create a conduit request to `Stripe`'s API
stripeConduit
  :: (MonadIO m, FromJSON (Stripe.StripeReturn a)
    , Stripe.StripeReturn a ~ Stripe.StripeList b
    , Stripe.StripeHasParam a (Stripe.StartingAfter id)
    , MonadThrow m)
  => Stripe.StripeConfig
  -> Stripe.StripeRequest a
  -> (b -> id)
  -- ^ A mapping between the type and its ID field used in pagination
  -- TODO: the user should not have to set this themself
  -> Source m b
stripeConduit config request toId = do
    res <- liftIO $ stripe config request
    case res of
      Left e -> throwM e
      Right slist -> do
        -- Yield all objs already present
        let objs = Stripe.list slist
        CL.sourceList objs

        -- Paginate
        when (Stripe.hasMore slist) $ do
          lastId <- case toId <$> lastMay objs of
            Just lastId -> pure lastId
            Nothing -> throwM Stripe.StripeError
              { Stripe.errorType = Stripe.APIError
              , Stripe.errorMsg = "Stripe returned an empty list"
              , Stripe.errorCode  = Nothing
              , Stripe.errorParam = Nothing
              , Stripe.errorHTTP  = Nothing
              }

          stripeConduit
            config
            (request -&- Stripe.StartingAfter lastId)
            toId