packages feed

stripe-servant-0.3.0.0: src/Stripe/Resources.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
module Stripe.Resources
  ( -- * Core Types
    TimeStamp(..), StripeList(..)
    -- * Customers
  , CustomerId(..), Customer(..), CustomerCreate(..), CustomerUpdate(..)
    -- * Product catalog
  , ProductId(..), PriceId(..)
  , Product(..), ProductCreate(..)
  , Price(..), PriceRecurring(..), PriceCreate(..), PriceCreateRecurring(..)
    -- * Subscriptions
  , SubscriptionId(..), SubscriptionItemId(..), Subscription(..), SubscriptionItem(..), SubscriptionCreate(..), SubscriptionCreateItem(..)
    -- * Customer Portal
  , CustomerPortalId(..), CustomerPortal(..), CustomerPortalCreate(..)
    -- * Checkout
  , CheckoutSessionId(..), CheckoutSession(..), CheckoutSessionCreate(..), CheckoutSessionCreateLineItem(..)
    -- * Events
  , EventId(..), Event(..), EventData(..)
  )
where

import Stripe.Util.Aeson

import Data.Maybe
import Data.Time
import Data.Time.Clock.POSIX
import GHC.Generics
import Servant.API
import Text.Casing (quietSnake)
import Web.FormUrlEncoded
import qualified Data.Aeson as A
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Data.Vector as V

formOptions :: Int -> FormOptions
formOptions x =
  FormOptions
  { fieldLabelModifier = quietSnake . drop x }

-- | A 'UTCTime' wrapper that has unix timestamp JSON representation
newtype TimeStamp
  = TimeStamp { unTimeStamp :: UTCTime }
  deriving (Show, Eq)

instance A.ToJSON TimeStamp where
  toJSON = A.Number . fromRational . toRational . utcTimeToPOSIXSeconds . unTimeStamp

instance A.FromJSON TimeStamp where
  parseJSON =
    A.withScientific "unix timestamp" $ \sci ->
    pure $ TimeStamp $ posixSecondsToUTCTime (fromRational $ toRational sci)

instance ToHttpApiData TimeStamp where
  toUrlPiece x =
    let unix :: Int
        unix = round . utcTimeToPOSIXSeconds . unTimeStamp $ x
    in T.pack (show unix)

-- | A 'V.Vector' wrapper with an indication is there are more items available through pagination.
data StripeList a
  = StripeList
  { slHasMore :: Bool
  , slData :: V.Vector a
  } deriving (Show, Eq, Functor)

instance Semigroup (StripeList a) where
 (<>) a b = StripeList (slHasMore a || slHasMore b) (slData a <> slData b)

instance Monoid (StripeList a) where
  mempty = StripeList False mempty

instance Applicative StripeList where
  pure = StripeList False . pure
  (<*>) go x = StripeList (slHasMore go || slHasMore x) (slData go <*> slData x)

newtype CustomerId
  = CustomerId { unCustomerId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data Customer
  = Customer
  { cId :: CustomerId
  , cLivemode :: Bool
  , cCreated :: TimeStamp
  , cName :: Maybe T.Text
  , cEmail :: Maybe T.Text
  } deriving (Show, Eq)

data CustomerCreate
  = CustomerCreate
  { ccName :: Maybe T.Text
  , ccEmail :: Maybe T.Text
  } deriving (Show, Eq, Generic)

data CustomerUpdate
  = CustomerUpdate
  { cuName :: Maybe T.Text
  , cuEmail :: Maybe T.Text
  } deriving (Show, Eq, Generic)

newtype EventId
  = EventId { unEventId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data Event
  = Event
  { eId :: EventId
  , eCreated :: TimeStamp
  , eLivemode :: Bool
  , eType :: T.Text
  , eApiVersion :: T.Text
  , eData :: EventData
  } deriving (Show, Eq)

data EventData
  = EventData
  { edObject :: A.Value
  } deriving (Show, Eq)

newtype PriceId
  = PriceId { unPriceId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data Price
  = Price
  { pId :: PriceId
  , pActive :: Bool
  , pCurrency :: T.Text
  , pNickname :: Maybe T.Text
  , pType :: T.Text -- TODO: make enum
  , pRecurring :: Maybe PriceRecurring
  , pUnitAmount :: Maybe Int
  , pProduct :: ProductId
  , pLookupKey :: Maybe T.Text
  } deriving (Show, Eq)

data PriceRecurring
  = PriceRecurring
  { prInterval :: T.Text -- TODO: make enum
  , prIntervalCount :: Int
  } deriving (Show, Eq)

data PriceCreate
  = PriceCreate
  { pcCurrency :: T.Text
  , pcUnitAmount :: Maybe Int
  , pcProduct :: ProductId
  , pcLookupKey :: Maybe T.Text
  , pcTransferLookupKey :: Bool
  , pcRecurring :: Maybe PriceCreateRecurring
  } deriving (Show, Eq, Generic)

data PriceCreateRecurring
  = PriceCreateRecurring
  { prcInterval :: T.Text -- TODO: make enum
  , prcIntervalCount :: Maybe Int
  } deriving (Show, Eq)

newtype ProductId
  = ProductId { unProductId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data Product
  = Product
  { prId :: ProductId
  , prActive :: Bool
  , prName :: T.Text
  , prDescription :: Maybe T.Text
  } deriving (Show, Eq)

data ProductCreate
  = ProductCreate
  { prcName :: T.Text
  , prcDescription :: Maybe T.Text
  } deriving (Show, Eq, Generic)

newtype SubscriptionId
  = SubscriptionId { unSubscriptionId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data Subscription
  = Subscription
  { sId :: SubscriptionId
  , sCancelAtPeriodEnd :: Bool
  , sCurrentPeriodEnd :: TimeStamp
  , sCurrentPeriodStart :: TimeStamp
  , sCustomer :: CustomerId
  , sItems :: StripeList SubscriptionItem
  , sStatus :: T.Text -- TODO: make enum
  } deriving (Show, Eq)

newtype SubscriptionItemId
  = SubscriptionItemId { unSubscriptionItemId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data SubscriptionItem
  = SubscriptionItem
  { siId :: SubscriptionItemId
  , siPrice :: Price
  , siQuantity :: Maybe Int
  , siSubscription :: SubscriptionId
  } deriving (Show, Eq)

data SubscriptionCreateItem
  = SubscriptionCreateItem
  { sciPrice :: PriceId
  , sciQuantity :: Maybe Int
  } deriving (Show, Eq, Generic)

data SubscriptionCreate
  = SubscriptionCreate
  { scCustomer :: CustomerId
  , scItems :: [SubscriptionCreateItem]
  , scCancelAtPeriodEnd :: Maybe Bool
  , scTrialEnd :: Maybe TimeStamp
  } deriving (Show, Eq, Generic)

newtype CheckoutSessionId
  = CheckoutSessionId { unCheckoutSessionId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data CheckoutSession
  = CheckoutSession
  { csId :: CheckoutSessionId
  , csLivemode :: Bool
  , csClientReferenceId :: Maybe T.Text
  , csCancelUrl :: T.Text
  , csSuccessUrl :: T.Text
  , csPaymentMethodTypes :: V.Vector T.Text  -- TODO: make enum
  , csSubscription :: Maybe SubscriptionId
  , csAllowPromotionCodes :: Maybe Bool
  } deriving (Show, Eq)

data CheckoutSessionCreate
  = CheckoutSessionCreate
  { cscCancelUrl :: T.Text
  , cscMode :: T.Text  -- TODO: make enum
  , cscPaymentMethodTypes :: [T.Text]  -- TODO: make enum
  , cscSuccessUrl :: T.Text
  , cscClientReferenceId :: Maybe T.Text
  , cscCustomer :: Maybe CustomerId
  , cscAllowPromotionCodes :: Maybe Bool
  , cscLineItems :: [CheckoutSessionCreateLineItem]
  } deriving (Show, Eq, Generic)

data CheckoutSessionCreateLineItem
  = CheckoutSessionCreateLineItem
  { cscliPrice :: PriceId
  , cscliQuantity :: Integer
  } deriving (Show, Eq, Generic)

newtype CustomerPortalId
  = CustomerPortalId { unCustomerPortalId :: T.Text }
  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)

data CustomerPortal
  = CustomerPortal
  { cpId :: CustomerPortalId
  , cpLivemode :: Bool
  , cpCreated :: TimeStamp
  , cpCustomer :: CustomerId
  , cpReturnUrl :: Maybe T.Text
  , cpUrl :: T.Text
  } deriving (Show, Eq)

data CustomerPortalCreate
  = CustomerPortalCreate
  { cpcCustomer :: CustomerId
  , cpcReturnUrl :: Maybe T.Text
  } deriving (Show, Eq, Generic)

$(deriveJSON (jsonOpts 2) ''StripeList)
$(deriveJSON (jsonOpts 1) ''Customer)
$(deriveJSON (jsonOpts 1) ''Event)
$(deriveJSON (jsonOpts 2) ''EventData)
$(deriveJSON (jsonOpts 2) ''CheckoutSession)
$(deriveJSON (jsonOpts 1) ''Price)
$(deriveJSON (jsonOpts 2) ''PriceRecurring)
$(deriveJSON (jsonOpts 2) ''Product)
$(deriveJSON (jsonOpts 1) ''Subscription)
$(deriveJSON (jsonOpts 2) ''SubscriptionItem)
$(deriveJSON (jsonOpts 2) ''CustomerPortal)

instance ToForm CustomerCreate where
  toForm = genericToForm (formOptions 2)

instance ToForm CustomerUpdate where
  toForm = genericToForm (formOptions 2)

instance ToForm CustomerPortalCreate where
  toForm = genericToForm (formOptions 3)

instance ToForm ProductCreate where
  toForm = genericToForm (formOptions 3)

instance ToForm PriceCreate where
  toForm pc =
    let recurringPiece =
          case pcRecurring pc of
            Nothing -> []
            Just x ->
              [ ("recurring[interval]", [prcInterval x])
              , ("recurring[interval_count]", maybeToList $ fmap toUrlPiece $ prcIntervalCount x)
              ]
    in Form $ HM.fromList $
       [ ("currency", [pcCurrency pc])
       , ("product", [toUrlPiece $ pcProduct pc])
       , ("unit_amount", maybeToList $ fmap toUrlPiece $ pcUnitAmount pc)
       , ("lookup_key", maybeToList $ pcLookupKey pc)
       , ("transfer_lookup_key", [toUrlPiece $ pcTransferLookupKey pc])
       ] <> recurringPiece

instance ToForm SubscriptionCreate where
  toForm sc =
    let convertItem (idx, itm) =
          [ ("items[" <> toUrlPiece idx <> "][price]", [toUrlPiece $ sciPrice itm])
          , ("items[" <> toUrlPiece idx <> "][quantity]", maybeToList $ toUrlPiece <$> sciQuantity itm)
          ]
        lineItems =
          concatMap convertItem (zip ([0..] :: [Int]) (scItems sc))
    in Form $ HM.fromList $
       [ ("customer", [toUrlPiece $ scCustomer sc])
       , ("cancel_at_period_end", maybeToList $ toUrlPiece <$> scCancelAtPeriodEnd sc)
       , ("trial_end", maybeToList $ toUrlPiece <$> scTrialEnd sc)
       ] <> lineItems


instance ToForm CheckoutSessionCreate where
  toForm csc =
    let convertItem (idx, itm) =
          [ ("line_items[" <> toUrlPiece idx <> "][price]", [toUrlPiece $ cscliPrice itm])
          , ("line_items[" <> toUrlPiece idx <> "][quantity]", [toUrlPiece $ cscliQuantity itm])
          ]
        lineItems =
          concatMap convertItem (zip ([0..] :: [Int]) (cscLineItems csc))
        convertPmt (idx, pm) =
          ( "payment_method_types[" <> toUrlPiece idx <> "]"
          , [pm]
          )
        pmt =
          map convertPmt (zip ([0..] :: [Int]) (cscPaymentMethodTypes csc))
    in Form $ HM.fromList $
       [ ("cancel_url", [cscCancelUrl csc])
       , ("success_url", [cscSuccessUrl csc])
       , ("mode", [cscMode csc])
       , ("client_reference_id", maybeToList $ cscClientReferenceId csc)
       , ("customer", maybeToList $ fmap toUrlPiece $ cscCustomer csc)
       , ("allow_promotion_codes", maybeToList $ toUrlPiece <$> cscAllowPromotionCodes csc)
       ] <> lineItems <> pmt