packages feed

stripe-servant-0.3.1.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 qualified Data.Aeson as A
import qualified Data.HashMap.Strict as HM
import Data.Maybe
import qualified Data.Text as T
import Data.Time
import Data.Time.Clock.POSIX
import qualified Data.Vector as V
import GHC.Generics
import Servant.API
import Stripe.Util.Aeson
import Text.Casing (quietSnake)
import Web.FormUrlEncoded

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 2) ''EventData)
$(deriveJSON (jsonOpts 1) ''Event)
$(deriveJSON (jsonOpts 2) ''CheckoutSession)
$(deriveJSON (jsonOpts 2) ''PriceRecurring)
$(deriveJSON (jsonOpts 1) ''Price)
$(deriveJSON (jsonOpts 2) ''Product)
$(deriveJSON (jsonOpts 2) ''SubscriptionItem)
$(deriveJSON (jsonOpts 1) ''Subscription)
$(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