module Web.Stripe.Charge ( Charge(..) , ChargeId(..) , chargeToken , chargeTokenById , chargeCustomer , chargeCustomerById , chargeRCard , getCharge , getCharges , partialRefund , partialRefundById , fullRefund , fullRefundById {- Re-Export -} , Amount(..) , Count(..) , Currency(..) , Description(..) , Offset(..) , UTCTime(..) , SConfig(..) , StripeT(StripeT) , runStripeT ) where import Control.Applicative ( (<$>) ) import Control.Monad ( liftM, ap ) import Control.Monad.Error ( MonadIO, throwError, strMsg ) import Network.HTTP.Types ( StdMethod(..) ) import Text.JSON ( Result(Error), JSON(..), JSValue(JSObject) , resultToEither, valFromObj ) import Web.Stripe.Card ( Card, RequestCard, rCardKV ) import Web.Stripe.Customer ( Customer(..), CustomerId(..) ) import Web.Stripe.Client ( StripeT(..), SConfig(..), SRequest(..), baseSReq , query, runStripeT ) import Web.Stripe.Token ( Token(..), TokenId(..) ) import Web.Stripe.Utils ( Amount(..), Count(..), Currency(..) , Description(..), Offset(..), UTCTime(..) , fromSeconds, jGet, mjGet, optionalArgs ) ---------------- -- Data Types -- ---------------- -- | Represents a charge in the Stripe system. data Charge = Charge { chargeId :: ChargeId , chargeCreated :: UTCTime , chargeDescription :: Maybe Description , chargeCurrency :: Currency , chargeAmount :: Amount , chargeFee :: Int , chargeLive :: Bool , chargePaid :: Bool , chargeRefunded :: Bool , chargeCard :: Card } deriving Show -- | Represents the identifier for a given 'Charge' in the Stripe system. newtype ChargeId = ChargeId { unChargeId :: String } deriving Show -- | Submit a 'Charge' to the Stripe API using an already constructed 'Token'. chargeToken :: MonadIO m => Token -> Amount -> Currency -> Maybe Description -> StripeT m Charge chargeToken = chargeTokenById . tokId -- | Submit a 'Charge' to the Stripe API using a 'TokenId'. chargeTokenById :: MonadIO m => TokenId -> Amount -> Currency -> Maybe Description -> StripeT m Charge chargeTokenById (TokenId tid) = charge [("card", tid)] -- | Submit a 'Charge' to the Stripe for a specific 'Customer' that already has -- payment details on file. chargeCustomer :: MonadIO m => Customer -> Amount -> Currency -> Maybe Description -> StripeT m Charge chargeCustomer = chargeCustomerById . custId -- | Submit a 'Charge' to the Stripe for a specific 'Customer', identified by -- its 'CustomerId', that already has payment details on file. chargeCustomerById :: MonadIO m => CustomerId -> Amount -> Currency -> Maybe Description -> StripeT m Charge chargeCustomerById (CustomerId cid) = charge [("customer", cid)] -- | Submit a 'Charge' to the Stripe API using a 'RequestCard' to describe -- payment details. chargeRCard :: MonadIO m => RequestCard -> Amount -> Currency -> Maybe Description -> StripeT m Charge chargeRCard rc = charge (rCardKV rc) -- | Internal convenience function to handle actually submitting a 'Charge' -- request to the Stripe API. charge :: MonadIO m => [(String, String)] -> Amount -> Currency -> Maybe Description -> StripeT m Charge charge adata a c mcd = snd `liftM` query (chargeRq []) { sMethod = POST, sData = fdata } where fdata = head (optionalArgs odata) : adata ++ bdata odata = [ ("description", unDescription <$> mcd) ] bdata = [ ("amount", show . unAmount $ a) , ("currency", unCurrency c) ] -- | Retrieve a 'Charge' from the Stripe API, identified by 'ChargeId'. getCharge :: MonadIO m => ChargeId -> StripeT m Charge getCharge (ChargeId cid) = snd `liftM` query (chargeRq [cid]) -- | Retrieve a list of 'Charge's from the Stripe API. The query can optionally -- be refined to a specific: -- -- * number of charges, via 'Count', -- * page of results, via 'Offset', and -- * 'Customer'. getCharges :: MonadIO m => Maybe CustomerId -> Maybe Count -> Maybe Offset -> StripeT m [Charge] getCharges mcid mc mo = do (_, rsp) <- query $ (chargeRq []) { sQString = optionalArgs oqs } either err return . resultToEither . valFromObj "data" $ rsp where oqs = [ ("count", show . unCount <$> mc) , ("offset", show . unOffset <$> mo) , ("customer", unCustomerId <$> mcid) ] err _ = throwError $ strMsg "Unable to parse charge list." -- | Requests that Stripe issue a partial refund to a specific 'Charge' for a -- particular 'Amount'. partialRefund :: MonadIO m => Charge -> Amount -> StripeT m Charge partialRefund = partialRefundById . chargeId -- | Requests that Stripe issue a partial refund to a specific 'Charge', -- identified by 'ChargeId', for a particular 'Amount'. partialRefundById :: MonadIO m => ChargeId -> Amount -> StripeT m Charge partialRefundById cid = refundChargeById cid . Just -- | Requests that Stripe issue a full refund to a specific 'Charge'. fullRefund :: MonadIO m => Charge -> StripeT m Charge fullRefund = fullRefundById . chargeId -- | Requests that Stripe issue a full refund to a specific 'Charge', -- identified by 'ChargeId'. fullRefundById :: MonadIO m => ChargeId -> StripeT m Charge fullRefundById cid = refundChargeById cid Nothing -- | Internal convenience function used to handle submitting a refund request -- to Stripe. refundChargeById :: MonadIO m => ChargeId -> Maybe Amount -> StripeT m Charge refundChargeById (ChargeId cid) ma = snd `liftM` query (chargeRq [cid, "refund"]) { sMethod = POST, sData = fd } where fd = optionalArgs [("amount", show . unAmount <$> ma)] -- | Convenience function to create a 'SRequest' specific to coupon-related -- actions. chargeRq :: [String] -> SRequest chargeRq pcs = baseSReq { sDestination = "charges":pcs } ------------------ -- JSON Parsing -- ------------------ -- | Attempts to parse JSON into a 'Charge'. instance JSON Charge where readJSON (JSObject c) = Charge `liftM` (ChargeId <$> jGet c "id") `ap` (fromSeconds <$> jGet c "created") `ap` ((Description <$>) <$> mjGet c "description") `ap` (Currency <$> jGet c "currency") `ap` (Amount <$> jGet c "amount") `ap` jGet c "fee" `ap` jGet c "livemode" `ap` jGet c "paid" `ap` jGet c "refunded" `ap` jGet c "card" readJSON _ = Error "Unable to read Stripe charge." showJSON _ = undefined