{-# LANGUAGE OverloadedStrings #-}
-------------------------------------------
-- |
-- Module      : Web.Stripe.Charge
-- Copyright   : (c) David Johnson, 2014
-- Maintainer  : djohnson.m@gmail.com
-- Stability   : experimental
-- Portability : POSIX
--
-- < https:/\/\stripe.com/docs/api#charges >
--
-- @
-- import Web.Stripe         
-- import Web.Stripe.Customer 
-- import Web.Stripe.Charge
--
-- main :: IO ()
-- main = do
--   let config = SecretKey "secret_key"
--       credit = CardNumber "4242424242424242"
--       em  = ExpMonth 12
--       ey  = ExpYear 2015
--       cvc = CVC "123"
--   result <- stripe config $ do
--         Customer { customerId = cid } <- createCustomerByCard cn em ey cvc
--         charge <- chargeCustomer cid USD 100 Nothing
--         return charge
--   case result of
--     Right charge      -> print charge
--     Left  stripeError -> print stripeError
-- @
module Web.Stripe.Charge
    ( -- * API
      ---- * Create Charges
      chargeCustomer
    , chargeCardByToken
    , chargeCustomerByCardId
    , chargeCard
    , chargeBase
      ---- * Get Charge(s)
    , getCharge
    , getChargeExpandable
    , getCharges
    , getChargesExpandable
    , getCustomerCharges
    , getCustomerChargesExpandable
      ---- * Update Charge
    , updateCharge
      ---- * Capture Charge
    , captureCharge
      -- * Types
    , Charge       (..)
    , TokenId      (..)
    , ChargeId     (..)
    , CustomerId   (..)
    , Customer     (..)
    , Currency     (..)
    , CardNumber   (..)
    , CVC          (..)
    , ExpMonth     (..)
    , ExpYear      (..)
    , StripeList   (..)
    , Email        (..)
    , Description
    , StatementDescription
    , Amount
    , Capture
    ) where

import           Web.Stripe.Client.Internal (Method (GET, POST), Stripe,
                                             StripeRequest (..), callAPI,
                                             getParams, toMetaData, toText, toExpandable,
                                             toTextLower, (</>))
import           Web.Stripe.Types           (Amount, CVC (..), Capture, 
                                             CardNumber (..), Charge (..),
                                             ChargeId (..), Currency (..),
                                             CustomerId (..), Description,
                                             EndingBefore, ExpMonth (..),
                                             ExpYear (..), Limit, MetaData,
                                             Email (..), StartingAfter, Customer(..),
                                             StatementDescription(..), ExpandParams,
                                             StripeList (..), TokenId (..), CardId(..))
import           Web.Stripe.Types.Util      (getCardId, getChargeId, getCustomerId)

------------------------------------------------------------------------------
-- | Charge `Customer``s by `CustomerId`, will charge the default `Card` if exists
chargeCustomer
    :: CustomerId   -- ^ The `CustomerId` of the `Customer` to be charged
    -> Currency     -- ^ Required, 3-letter ISO Code
    -> Amount       -- ^ Required, Integer value of 100 represents $1
    -> Maybe Description -- ^ Optional, default is null
    -> Stripe Charge
chargeCustomer customerid currency amount description =
    chargeBase amount currency description (Just customerid)
    Nothing Nothing Nothing True
    Nothing Nothing Nothing Nothing []

------------------------------------------------------------------------------
-- | Charge `Customer`s by `CustomerId`
chargeCustomerByCardId
    :: CustomerId   -- ^ The `CustomerId` of the `Customer` to be charged
    -> CardId       -- ^ `CardId` of `Customer` to charge
    -> Currency     -- ^ Required, 3-letter ISO Code
    -> Amount       -- ^ Required, Integer value of 100 represents $1
    -> Maybe Description -- ^ Optional, default is null
    -> Stripe Charge
chargeCustomerByCardId
    customerid
    cardid
    currency
    amount
    description =
      chargeBase amount currency description
      (Just customerid) (Just $ TokenId $ getCardId cardid)  Nothing
      Nothing True Nothing Nothing
      Nothing Nothing []

------------------------------------------------------------------------------
-- | Charge a card by a `TokenId`
chargeCardByToken
    :: TokenId    -- ^ The `TokenId` representative of a `Card`
    -> Currency   -- ^ Required, 3-letter ISO Code
    -> Amount     -- ^ Required, Integer value of 100 represents $1
    -> Maybe Description -- ^ Optional, default is null
    -> Stripe Charge
chargeCardByToken tokenId currency amount description =
    chargeBase amount currency description Nothing (Just tokenId)
    Nothing Nothing True Nothing Nothing Nothing Nothing []

------------------------------------------------------------------------------
-- | Charge a card by `CardNumber`
chargeCard
    :: CardNumber        -- ^ Required, Credit Card Number
    -> ExpMonth          -- ^ Required, Expiration Month (i.e. 09)
    -> ExpYear           -- ^ Required, Expiration Year (i.e. 2018)
    -> CVC               -- ^ Required, CVC Number (i.e. 000)
    -> Currency          -- ^ Required, 3-letter ISO Code
    -> Amount            -- ^ Required, Integer value of 100 represents $1
    -> Maybe Description -- ^ Optional, default is null
    -> Stripe Charge
chargeCard cardNumber expMonth expYear cvc currency amount description =
    chargeBase amount currency description
    Nothing Nothing Nothing Nothing True
    (Just cardNumber) (Just expMonth)
    (Just expYear) (Just cvc) []

------------------------------------------------------------------------------
-- | Base method for creating a `Charge`
chargeBase
    :: Amount             -- ^ Required, Integer value of 100 represents $1
    -> Currency           -- ^ Required, 3-letter ISO Code
    -> Maybe Description  -- ^ Optional, default is null
    -> Maybe CustomerId   -- ^ Optional, either `CustomerId` or `TokenId` has to be specified
    -> Maybe TokenId      -- ^ Optional, either `CustomerId` or `TokenId` has to be specified
    -> Maybe StatementDescription -- ^ Optional, Arbitrary string to include on CC statements
    -> Maybe Email        -- ^ Optional, Arbitrary string to include on CC statements
    -> Capture            -- ^ Optional, default is True
    -> Maybe CardNumber   -- ^ Optional, Credit Card Number
    -> Maybe ExpMonth     -- ^ `Card` Expiration Month
    -> Maybe ExpYear      -- ^ `Card` Expiration Year
    -> Maybe CVC          -- ^ `Card` `CVC`
    -> MetaData           -- ^ `Card` `MetaData`
    -> Stripe Charge
chargeBase
    amount
    currency
    description
    customerid
    tokenId
    statementDescription
    receiptEmail
    capture
    cardNumber
    expMonth
    expYear
    cvc'
    metadata    = callAPI request
  where request = StripeRequest POST url params
        url     = "charges"
        params  = toMetaData metadata ++ getParams [
                     ("amount", toText `fmap` Just amount)
                   , ("customer", (\(CustomerId cid) -> cid) `fmap` customerid)
                   , ("currency", toTextLower `fmap` Just currency)
                   , ("card", (\(TokenId tokenid) -> tokenid) `fmap` tokenId)
                   , ("description", description)
                   , ("statement_description", (\(StatementDescription x) -> x) `fmap` statementDescription)
                   , ("receipt_email", (\(Email email) -> email) `fmap` receiptEmail)
                   , ("capture", (\x -> if x then "true" else "false") `fmap` Just capture)
                   , ("card[number]", (\(CardNumber c) -> c) `fmap` cardNumber)
                   , ("card[exp_month]", (\(ExpMonth m) ->  toText m) `fmap` expMonth)
                   , ("card[exp_year]", (\(ExpYear y) -> toText y) `fmap` expYear)
                   , ("card[cvc]", (\(CVC c) -> c) `fmap` cvc')
                  ]

------------------------------------------------------------------------------
-- | Retrieve a `Charge` by `ChargeId`
getCharge
    :: ChargeId -- ^ The `Charge` to retrive
    -> Stripe Charge
getCharge chargeid = getChargeExpandable chargeid []

------------------------------------------------------------------------------
-- | Retrieve a `Charge` by `ChargeId` with `ExpandParams`
getChargeExpandable
    :: ChargeId     -- ^ The `Charge` retrive
    -> ExpandParams -- ^ The `ExpandParams` to retrive
    -> Stripe Charge
getChargeExpandable
    chargeid
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = "charges" </> getChargeId chargeid
        params  = toExpandable expandParams

------------------------------------------------------------------------------
-- | Retrieve all `Charge`s
getCharges
    :: Limit                    -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter ChargeId   -- ^ Paginate starting after the following CustomerID
    -> EndingBefore ChargeId    -- ^ Paginate ending before the following CustomerID
    -> Stripe (StripeList Charge)
getCharges
    limit
    startingAfter
    endingBefore =
      getChargesExpandable limit startingAfter endingBefore []

------------------------------------------------------------------------------
-- | Retrieve all `Charge`s
getChargesExpandable
    :: Limit                    -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter ChargeId   -- ^ Paginate starting after the following `CustomerId`
    -> EndingBefore ChargeId    -- ^ Paginate ending before the following `CustomerId`
    -> ExpandParams             -- ^ Get Charges with `ExpandParams`
    -> Stripe (StripeList Charge)
getChargesExpandable
    limit
    startingAfter
    endingBefore
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = "charges"
        params  = getParams [
            ("limit", toText `fmap` limit )
          , ("starting_after", (\(ChargeId x) -> x) `fmap` startingAfter)
          , ("ending_before", (\(ChargeId x) -> x) `fmap` endingBefore)
          ] ++ toExpandable expandParams


------------------------------------------------------------------------------
-- | Retrieve all `Charge`s for a specified `Customer`
getCustomerCharges
    :: CustomerId
    -> Limit                    -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter ChargeId   -- ^ Paginate starting after the following `CustomerId`
    -> EndingBefore ChargeId    -- ^ Paginate ending before the following `CustomerId`
    -> Stripe (StripeList Charge)
getCustomerCharges
    customerid
    limit
    startingAfter
    endingBefore =
      getCustomerChargesExpandable customerid limit
        startingAfter endingBefore []

------------------------------------------------------------------------------
-- | Retrieve all `Charge`s for a specified `Customer` with `ExpandParams`
getCustomerChargesExpandable
    :: CustomerId
    -> Limit                    -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter ChargeId   -- ^ Paginate starting after the following `CustomerId`
    -> EndingBefore ChargeId    -- ^ Paginate ending before the following `CustomerId`
    -> ExpandParams             -- ^ Get `Customer` `Charge`s with `ExpandParams`
    -> Stripe (StripeList Charge)
getCustomerChargesExpandable
    customerid
    limit
    startingAfter
    endingBefore
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = "charges"
        params  = getParams [
            ("customer", Just $ getCustomerId customerid )
          , ("limit", toText `fmap` limit )
          , ("starting_after", (\(ChargeId x) -> x) `fmap` startingAfter)
          , ("ending_before", (\(ChargeId x) -> x) `fmap` endingBefore)
          ] ++ toExpandable expandParams

------------------------------------------------------------------------------
-- | A `Charge` to be updated
updateCharge
    :: ChargeId    -- ^ The `Charge` to update
    -> Description -- ^ The `Charge` `Description` to update
    -> MetaData    -- ^ The `Charge` `MetaData` to update
    -> Stripe Charge
updateCharge
    chargeid
    description
    metadata    = callAPI request
  where request = StripeRequest POST url params
        url     = "charges" </> getChargeId chargeid
        params  = toMetaData metadata ++ getParams [
                   ("description", Just description)
                  ]

------------------------------------------------------------------------------
-- | a `Charge` to be captured
captureCharge
    :: ChargeId     -- ^ The `ChargeId` of the `Charge` to capture
    -> Maybe Amount -- ^ If Nothing the entire charge will be captured, otherwise the remaining will be refunded
    -> Maybe Email  -- ^ `Email` to send `Charge` receipt
    -> Stripe Charge
captureCharge
    chargeid
    amount
    receiptEmail = callAPI request
  where request  = StripeRequest POST url params
        url      = "charges" </> getChargeId chargeid </> "capture"
        params   = getParams [
                     ("amount", toText `fmap` amount)
                   , ("receipt_email", (\(Email email) -> email) `fmap` receiptEmail)
                   ]