diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog for `paynow-zw`
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to the
+[Haskell Package Versioning Policy](https://pvp.haskell.org/).
+
+## Unreleased
+
+## 0.1.0.0 - YYYY-MM-DD
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2022
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+# Paynow Zimbabwe Haskell SDK
+
+Haskell SDK for Paynow Zimbabwe's API
+
+# Prerequisites
+
+This library has a set of prerequisites that must be met for it to work
+
+```Haskell
+{-# LANGUAGE OverloadedStrings #-}
+import Network.Payments.Paynow as Paynow
+
+	# Do stuff
+```
+
+---
+
+# Usage example
+
+```Haskell
+defaultConfig :: PaynowConfig
+defaultConfig = defaultProdConfig "1201"
+                    "3e9fed89-60e1-4ce5-ab6e-6b1eb2d4f977"
+                    "http://localhost:3000/result"
+                    "http://localhost:3000/return"
+
+....
+client <- newPaynowClient config
+payment <- newExpressCheckout (Ecocash "0779800700") "45" 37.50 "email" Nothing
+result <- processTx payment
+case result of
+   Left e => //handle err
+   Right r => //handle result r :: PaynowTxResult
+```
+
+Create a new payment passing in the reference for that payment (e.g invoice id, or anything that you can use to identify the transaction and the user's email address
+
+---
+
+> Express Transactions
+
+```Haskell
+testVMCDetails :: VMC
+testVMCDetails = VMC (Card "4111111111111111" "588" "2023" "Trevor Sibanda") (BillingAddress "Address Line 1" "Line 2" "City" "Zimbabwe" Nothing)
+
+
+payment <- newExpressCheckout client (OneMoney "0783102754") testRef testAmount testEmail Nothing
+
+payment <- newExpressCheckout client (Ecocash "0715900800") testRef testAmount testEmail Nothing
+
+payment <- newExpressCheckout client (VisaMastercard testVMCDetails) testRef testAmount testEmail Nothing
+
+```
+
+# Checking transaction status
+
+The SDK exposes a handy method that you can use to check the status of a transaction. Once you have instantiated the Paynow class.
+
+```Haskell
+tx <- newPollTransaction "https://www.paynow.co.zw/Interface/CheckPayment/?guid=dece867e-5a40-4961-bf0e-5d691c2a97f8"
+processtx tx >>= print
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/paynow-zw.cabal b/paynow-zw.cabal
new file mode 100644
--- /dev/null
+++ b/paynow-zw.cabal
@@ -0,0 +1,61 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.4.
+--
+-- see: https://github.com/sol/hpack
+
+name:           paynow-zw
+version:        0.1.0.0
+synopsis:       A Haskell wrapper for the Paynow payment gateway
+description:    Please see the README on GitHub at <https://github.com/trevorsibanda/paynow-zw#readme>
+category:       payments
+homepage:       https://github.com/trevorsibanda/paynow-zw#readme
+bug-reports:    https://github.com/trevorsibanda/paynow-zw/issues
+author:         Trevor Sibanda
+maintainer:     sibandatrevor@gmail.com
+copyright:      2022 Trevor Sibanda
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/trevorsibanda/paynow-zw
+
+library
+  exposed-modules:
+      Network.Payments.Paynow
+  other-modules:
+      Paths_paynow_zw
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
+  build-depends:
+      HTTP >=4000.4.1 && <4000.5
+    , base >=4.7 && <5
+    , bytestring >=0.10.12 && <0.11
+    , containers >=0.6.5 && <0.7
+    , cryptohash >=0.11.9 && <0.12
+    , http-conduit >=2.3.8 && <2.4
+    , http-types >=0.12.3 && <0.13
+    , text >=1.2.4 && <1.3
+    , unliftio >=0.2.22 && <0.3
+  default-language: Haskell2010
+
+test-suite paynow-zw-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_paynow_zw
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , hspec
+    , paynow-zw
+    , unliftio
+  default-language: Haskell2010
diff --git a/src/Network/Payments/Paynow.hs b/src/Network/Payments/Paynow.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Payments/Paynow.hs
@@ -0,0 +1,333 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+module Network.Payments.Paynow
+  ( 
+    newPaynowClient,
+    defaultProdConfig,
+    PaynowConfig (..),
+    PaynowClient (..),
+    PaynowTransaction (..),
+    Checkout (..),
+    PaynowTXResult (..),
+    PaymentMethod (..),
+    TxData (..),
+    PaynowError(..),
+    PaynowTXPayload (..),
+    Status(..),
+    BillingAddress (..),
+    Card (..),
+    PhoneNumber,
+    Email,
+    Amount,
+    Ref,
+    PaynowRef,
+    PaynowIntegrationID,
+    PaynowIntegrationKey,
+    PaynowResultURL,
+    PaynowReturnURL,
+    Hash,
+    RedirectURL,
+    PollURL,
+    VMC (..),
+    serverResponseToResult,
+    sha1Hex,
+  )
+where
+
+import UnliftIO.Exception
+import UnliftIO
+import qualified Crypto.Hash as C
+import qualified Data.ByteString.Lazy.Char8 as BS
+import qualified Data.Map as M
+import Data.List (sort)
+import Data.Maybe (catMaybes, fromMaybe, isJust)
+import Data.Text hiding (zip, filter, foldr)
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import Network.HTTP.Base (urlDecode, urlEncodeVars)
+import Network.HTTP.Types.Header (hContentType)
+import Network.HTTP.Simple
+
+prodPaynowEndpoint :: Text
+prodPaynowEndpoint = "https://www.paynow.co.zw/interface/"
+
+
+-- | Additional data to be sent with the transaction
+data TxData = TxData
+  { tAdditionalInfo :: Maybe Text,
+    tResultURL :: Maybe PaynowResultURL,
+    tReturnURL :: Maybe PaynowReturnURL
+  }
+
+instance ToKeyMap (Maybe TxData) where
+  toKeyMap Nothing = []
+  toKeyMap (Just (TxData {..})) = foldr 
+    (\(k, v) accum -> if (isJust v) then (k, fromMaybe "" v) : accum else accum )
+    []
+    [("additionalinfo", tAdditionalInfo), ("returnurl", tReturnURL), ("resulturl", tResultURL)]
+
+class ToKeyMap a where
+  toKeyMap :: a -> [(Text, Text)]
+
+
+-- Type aliases used to make documentation more readable
+type PhoneNumber = Text
+type Email = Text
+type Amount = Double
+type PaynowRef = Text
+type Instructions = Text
+
+data Status = Created | Error | Cancelled | Paid | Okay | Refunded | Failed | OtherStatus Text deriving (Show, Eq)
+type PollURL = Text
+type RedirectURL = Text
+type Hash = Text
+type Ref = Text
+
+type PaynowIntegrationID = Text
+type PaynowIntegrationKey = Text
+type PaynowResultURL = Text
+type PaynowReturnURL = Text
+
+
+-- | Card Details
+data Card = Card {cardNumber :: !Text, cardCVV :: !Text, cardExpiry :: !Text, cardName :: !Text} deriving (Show)
+
+-- | ToKeyMap typeclass
+instance ToKeyMap Card where
+  toKeyMap Card {..} = [("cardnumber", cardNumber), ("cardname", cardName), ("cardcvv", cardCVV), ("cardexpiry", cardExpiry)]
+
+-- | Billing Address
+data BillingAddress = BillingAddress {baAddressLine1 :: !Text, baAddressLine2 :: !Text, baCity :: !Text, baCountry :: !Text, baProvince :: Maybe Text} deriving (Show)
+
+instance ToKeyMap BillingAddress where
+  toKeyMap BillingAddress {..} =
+    [("billingline1", baAddressLine1), ("billingline2", baAddressLine2), ("billingcity", baCity), ("billingprovince", fromMaybe "" baProvince), ("billingcountry", baCountry)]
+
+-- | Visa Mastercard Details
+data VMC = VMC Card BillingAddress deriving (Show)
+
+instance ToKeyMap VMC where
+  toKeyMap (VMC card address) = (toKeyMap card) <> (toKeyMap address)
+
+-- | Ecocash, OneMoney, VisaMastercard payment methods
+data PaymentMethod = Ecocash PhoneNumber | OneMoney PhoneNumber | VisaMastercard VMC deriving (Show)
+
+instance ToKeyMap PaymentMethod where
+  toKeyMap (Ecocash phone) = [("phone", phone), ("method", "ecocash")]
+  toKeyMap (OneMoney phone) = [("phone", phone), ("method", "onemoney")]
+  toKeyMap (VisaMastercard vmcd) = toKeyMap vmcd
+
+-- | Checkout type
+data Checkout = ExpressCheckout PaymentMethod | ClassicCheckout deriving (Show)
+
+instance ToKeyMap Checkout where
+  toKeyMap ClassicCheckout = []
+  toKeyMap (ExpressCheckout method) = toKeyMap method
+
+data PaynowError = ServerError | InternalError | UnHandledException SomeException | OtherError Text deriving (Show)
+
+-- |
+-- | PaynowTXResult is the result of a transaction
+-- | 
+data PaynowTXResult = 
+  InitExpressPaymentSuccess Status PaynowRef Instructions PollURL  Hash | 
+  InitClassicCheckoutSuccess Status RedirectURL PollURL Hash  |
+  PollPaymentSuccess Status PaynowRef Ref Amount PollURL Hash  |
+  VerifySignatureSuccess deriving (Show, Eq)
+
+-- |
+--  PaynowTransaction is the type of transaction to be performed.
+--
+--  InitPayment: Initialize a payment
+--  PollPayment: Poll for the status of a payment
+--  VerifyStatusUpdate: Verify the authenticity of a status update
+data PaynowTransaction = InitPayment Ref Amount Email Checkout (Maybe TxData) | PollPayment PollURL | VerifyStatusUpdate [(Text, Text)]
+
+statusMessage :: [(Text, Text)]
+statusMessage = [("status", "Message")]
+
+-- | Ordering of keys for hashing and form body
+txOrdering :: [Text]
+txOrdering = ["resulturl", "returnurl", "reference", "amount", "id", "additionalinfo", "authemail", "phone", "method", "status", "hash"]
+
+orderTXKeyMap :: [(Text, Text)] -> [(Text, Text)]
+orderTXKeyMap l =
+  catMaybes $ (\k -> (k,) <$> M.lookup k m) <$> txOrdering
+  where
+    m :: M.Map Text Text
+    m = M.fromList l
+
+instance ToKeyMap PaynowTransaction where
+  toKeyMap (InitPayment ref amount email checkout mTxData) =
+    orderTXKeyMap $ statusMessage <> [("reference", ref), ("amount", T.pack $ show amount), ("authemail", email)] <> (toKeyMap checkout) <> (toKeyMap mTxData)
+  toKeyMap (PollPayment _) = statusMessage
+  toKeyMap _ = statusMessage
+
+class PaynowTXPayload a where
+  urlParamsWithHash :: a -> PaynowConfig -> (Hash, Text)
+  hash :: a -> PaynowConfig -> Text
+
+urlEncodeT :: [(Text, Text)] -> Text
+urlEncodeT tple = T.pack $ urlEncodeVars $ (\(k, v) -> (T.unpack k, T.unpack v)) <$> tple
+
+sha1Hex :: Text -> Hash
+sha1Hex t =
+  T.toUpper $ T.decodeUtf8 $ (C.digestToHexByteString (C.hash (T.encodeUtf8 t) :: C.Digest C.SHA512))
+
+instance PaynowTXPayload PaynowTransaction where
+  hash tx config =
+    sha1Hex $ (intercalate "" ((snd) <$> (orderTXKeyMap $ toKeyMap config <> toKeyMap tx))) <> (pncIntegrationKey config)
+
+  urlParamsWithHash tx config =
+    (hashed, urlEncodeT $ orderTXKeyMap ((toKeyMap config <> toKeyMap tx <> [("hash", hashed)])))
+    where
+      hashed :: Text
+      hashed = hash tx config
+
+-- |
+--  PaynowConfig is the configuration for the Paynow API.
+--  For more details, see https://www.paynow.co.zw/docs/api/
+--
+--  pncEndpoint: The endpoint to use for the API
+--  pncIntegrationID: The integration ID for the API
+--  pncIntegrationKey: The integration key for the API
+--  pncResultURL: The URL to redirect to after a transaction is completed
+--  pncReturnURL: The URL to redirect to after a transaction is completed
+data PaynowConfig = PaynowConfig
+  { pncEndpoint :: !Text,
+    pncIntegrationID :: !PaynowIntegrationID,
+    pncIntegrationKey :: !PaynowIntegrationKey,
+    pncResultURL :: !PaynowResultURL,
+    pncReturnURL :: !PaynowReturnURL
+  }
+  deriving (Show)
+
+checkoutEndpoint :: PaynowConfig -> Checkout -> Text
+checkoutEndpoint PaynowConfig {..} ClassicCheckout =
+  pncEndpoint <> "initiatetransaction"
+checkoutEndpoint PaynowConfig {..} (ExpressCheckout _) =
+  pncEndpoint <> "remotetransaction"
+
+instance ToKeyMap PaynowConfig where
+  toKeyMap PaynowConfig {..} =
+    [("id", pncIntegrationID), ("resulturl", pncResultURL), ("returnurl", pncReturnURL)]
+
+-- |
+-- | A PaynowClient is a client for the Paynow API. It contains the following functions:
+-- | - newExpressCheckout: Create a new express checkout transaction (mobile or visa mastercard)
+-- | - newClassicCheckout: Create a new classic checkout transaction (user will be redirected to url to complete payment)
+-- | - newPollPayment: Create a new poll payment transaction (poll for the status of a payment)
+-- | - newVerifyStatusUpdate: Create a new verify status update transaction (verify the authenticity of a status update)
+-- | - processTx: Process a transaction, sends an HTTP request to the Paynow API
+-- | - txHash: Get the hash of a transaction
+data PaynowClient m = PaynowClient
+  { newExpressCheckout :: PaymentMethod -> Ref -> Amount -> Email -> Maybe TxData -> m PaynowTransaction,
+    newClassicCheckout :: Ref -> Amount -> Email -> Maybe TxData -> m PaynowTransaction,
+    newPollPayment :: PollURL -> m PaynowTransaction,
+    newVerifyStatusUpdate :: [(Text, Text)] -> m PaynowTransaction,
+    processTx :: PaynowTransaction -> m (Either PaynowError PaynowTXResult),
+    txHash :: PaynowTransaction -> m Text
+  }
+
+-- | The default configuration for the Paynow production API
+defaultProdConfig :: PaynowIntegrationID -> PaynowIntegrationKey -> PaynowResultURL -> PaynowReturnURL -> PaynowConfig
+defaultProdConfig apiID apiSecret = PaynowConfig prodPaynowEndpoint apiID apiSecret
+
+
+
+-- |
+--  Create a new PaynowClient with the given configuration.
+-- @
+-- client <- newPaynowClient config
+-- payment <- newExpressCheckout (Ecocash "0779800700") "45" 37.50 Nothing
+-- result <- processTx payment
+-- case result of
+--    PaynowError e => //handle err
+--    PaynowTxResult r => //handle result
+-- @
+newPaynowClient :: forall m. (MonadUnliftIO m) => PaynowConfig -> (PaynowClient m)
+newPaynowClient config =
+  PaynowClient {..}
+  where
+    newExpressCheckout method ref amount email mTXData =
+      pure $ InitPayment ref amount email (ExpressCheckout method) mTXData
+
+    newClassicCheckout ref amount email mTXData =
+      pure $ InitPayment ref amount email ClassicCheckout mTXData
+
+    newPollPayment = pure . PollPayment
+
+    newVerifyStatusUpdate = pure . VerifyStatusUpdate
+
+    txHash = pure . fst . (`urlParamsWithHash` config)
+
+    processTx tx = processTxHandler `handleAny` case tx of
+      InitPayment _ _ _ checkout _ -> do
+        r <- pure $ parseRequest_ $ T.unpack $ "POST " <> (checkoutEndpoint config checkout)
+        let req = addRequestHeader hContentType "application/x-www-form-urlencoded" $  setRequestBodyLBS (BS.pack $ T.unpack $ snd $ urlParamsWithHash tx config) r
+        response <- httpLBS req
+        let body = T.pack . BS.unpack $ getResponseBody response
+        case getResponseStatusCode response of
+          200 -> pure $ serverResponseToResult body
+          _ -> pure $ Left $ ServerError
+      PollPayment url -> do
+        req <- pure $ parseRequest_ $ T.unpack $ "GET " <> url
+        response <- httpLBS req
+        case getResponseStatusCode response of
+          200 -> 
+            pure $ serverResponseToResult (T.pack . BS.unpack $ getResponseBody response)
+          _ -> pure $ Left $ ServerError
+      VerifyStatusUpdate _ -> 
+        pure $ Left $ OtherError "not yet implemented"
+
+    
+    processTxHandler :: (MonadUnliftIO m) => SomeException -> m (Either PaynowError PaynowTXResult)
+    processTxHandler ex = pure $ Left $ UnHandledException ex
+
+
+serverResponseToResult :: Text -> Either PaynowError PaynowTXResult
+serverResponseToResult resp = case ( sort $ M.keys kvp) of
+--  InitExpressPaymentSuccess Status PaynowRef Instructions PollURL  Hash  
+  ["hash", "instructions", "paynowreference", "pollurl", "status"] -> 
+    Right $ InitExpressPaymentSuccess (ls "status") (l "paynowreference") (l "instructions") (l "pollurl") (l "hash")
+--  InitClassicCheckoutSuccess Status RedirectURL PollURL Hash  
+  ["browserurl", "hash", "pollurl", "status"] -> 
+    Right $ InitClassicCheckoutSuccess (ls "status") (l "browserurl") (l "pollurl") (l "hash")
+--  PollPaymentSuccess Status PaynowRef Ref Amount PollURL Hash  
+  ["amount", "hash", "paynowreference", "pollurl", "reference", "status"] -> 
+    Right $ PollPaymentSuccess (ls "status") (l "paynowreference") (l "reference") (la "amount") (l "pollurl") (l "hash")
+  _ -> Left $ OtherError resp
+  
+  where
+    l :: Text -> Text
+    l k = fromMaybe "" $ M.lookup k kvp
+
+    la :: Text -> Amount
+    la k =  (read . T.unpack . fromMaybe "0" $ M.lookup k kvp) :: Amount
+
+    ls :: Text -> Status
+    ls t = case (T.toLower $ l t) of
+      "paid" -> Paid
+      "created" -> Created
+      "ok" -> Okay
+      "cancelled" -> Cancelled
+      "failed" -> Failed
+      "error" -> Error
+      "refunded" -> Refunded
+      other -> OtherStatus other
+    
+
+    kvp :: M.Map Text Text
+    kvp = M.fromList $ (\t -> explode $ splitOn "=" t) <$> splitOn "&" resp
+
+    explode :: [Text] -> (Text, Text)
+    explode [] = ("", "")
+    explode [k] = (k, "")
+    explode [k, v] = (k, T.pack . urlDecode $ T.unpack v)
+    explode (k : vs) = (k, T.pack . urlDecode $ T.unpack $ T.unwords vs)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes       #-}
+
+import          Test.Hspec
+
+import           Data.Either
+import UnliftIO
+import Network.Payments.Paynow
+
+-- | Run the tests for the paynow library
+main :: IO ()
+main = hspec $ do
+  describe "PaynowTransaction" transactionSpec 
+  describe "Transaction Hashing" hashingSpec
+  describe "Response Parsing" responseSpec
+  describe "Paynow Client" clientSpec
+
+defaultConfig :: PaynowConfig
+defaultConfig = defaultProdConfig "1201"
+                    "3e9fed89-60e1-4ce5-ab6e-6b1eb2d4f977"
+                    "http://localhost:3000/result"
+                    "http://localhost:3000/return"
+
+runWithDefaultClientConfig :: (MonadUnliftIO m) => (PaynowClient m -> PaynowConfig -> m a) -> m a
+runWithDefaultClientConfig f = do
+    f client config
+    where
+        config = defaultConfig
+        client = newPaynowClient config
+
+runWithDefaultClient :: (MonadUnliftIO m) => (PaynowClient m -> m a) -> m a
+runWithDefaultClient f = runWithDefaultClientConfig (\cl _ -> f cl)
+
+
+testRef :: Ref
+testRef = "TEST REF"
+
+testAmount :: Amount
+testAmount = 99.99
+
+testEmail :: Email
+testEmail = "customer@gmail.com"
+
+testVMCDetails :: VMC
+testVMCDetails = VMC (Card "4111111111111111" "588" "2023" "Trevor Sibanda") (BillingAddress "Address Line 1" "Line 2" "City" "Zimbabwe" Nothing)
+
+testPollResponse = "reference=01&paynowreference=15650881&amount=99.00&status=Cancelled&pollurl=https%3a%2f%2fwww.paynow.co.zw%2fInterface%2fCheckPayment%2f%3fguid%3dd80b830d-623b-4948-9ab1-aae4950929bd&hash=E4F6E55CF3408EBDF0F59951E9E7B74C17CD9798A8AE5DA7B5D68D04F014E40753C438AA55445EB42FD9E455EE7FFA7BF752AD6791DE878DAEF4B486A345EAC"
+
+testClassicCheckoutResponse = "status=Ok&browserurl=https%3a%2f%2fwww.paynow.co.zw%2fPayment%2fConfirmPayment%2f15655543%2fsibandatrevor%40gmail.com%2f%2f&pollurl=https%3a%2f%2fwww.paynow.co.zw%2fInterface%2fCheckPayment%2f%3fguid%3ddece867e-5a40-4961-bf0e-5d691c2a97f8&hash=83B31644B176661DC643CEDD480ADF3C0DECC9E2A00B004B8DC37B6A88A2A246D1A3DE57D5BA41B61BBA2FE2580CEFDCDEE30698200B88CD85D62FE75E76CD4B"
+
+testExpressCheckoutResponse = "status=Ok&instructions=Dial+*151*2*4%23+and+enter+your+EcoCash+PIN.+Once+you+have+authorized+the+payment+via+your+handset%2c+please+click+Check+For+Payment+below+to+conclude+this+transaction&paynowreference=15655549&pollurl=https%3a%2f%2fwww.paynow.co.zw%2fInterface%2fCheckPayment%2f%3fguid%3dc9f51998-3bbd-48b4-9107-819794c75ef8&hash=6DB1869C5C3961ADDF616B0476660E6D76E9071855ACAFA974F751A67600E0AD7416040518825BB235FF4AF3B57FD50A80884FB438DE8A2A6FAC62C71BECB51F"
+
+clientSpec :: Spec
+clientSpec = do
+    it "should wrap exceptions in a PaynowError" $ do
+        let client = newPaynowClient defaultConfig { pncEndpoint = "not-a-valid-url" }
+        payment <- newClassicCheckout client testRef testAmount testEmail Nothing
+        result <- processTx client payment
+        result `shouldSatisfy` isLeft
+
+responseSpec :: Spec
+responseSpec = do
+    it "should parse a successful express checkout response" $ do
+        case serverResponseToResult testExpressCheckoutResponse of
+            Right result -> 
+                 result `shouldBe` InitExpressPaymentSuccess Okay "15655549" "Dial+*151*2*4#+and+enter+your+EcoCash+PIN.+Once+you+have+authorized+the+payment+via+your+handset,+please+click+Check+For+Payment+below+to+conclude+this+transaction" "https://www.paynow.co.zw/Interface/CheckPayment/?guid=c9f51998-3bbd-48b4-9107-819794c75ef8" "6DB1869C5C3961ADDF616B0476660E6D76E9071855ACAFA974F751A67600E0AD7416040518825BB235FF4AF3B57FD50A80884FB438DE8A2A6FAC62C71BECB51F"
+            _ -> expectationFailure "Expected a successful poll response"  
+
+    it "should parse a successful classic checkout response" $ do
+        case serverResponseToResult testClassicCheckoutResponse of
+            Right result -> 
+                 result `shouldBe` InitClassicCheckoutSuccess Okay "https://www.paynow.co.zw/Payment/ConfirmPayment/15655543/sibandatrevor@gmail.com//" "https://www.paynow.co.zw/Interface/CheckPayment/?guid=dece867e-5a40-4961-bf0e-5d691c2a97f8" "83B31644B176661DC643CEDD480ADF3C0DECC9E2A00B004B8DC37B6A88A2A246D1A3DE57D5BA41B61BBA2FE2580CEFDCDEE30698200B88CD85D62FE75E76CD4B"
+            _ -> expectationFailure "Expected a successful poll response"
+
+    it "should successfully parse a poll response" $ do
+        case serverResponseToResult testPollResponse of
+            Right result -> 
+                 result `shouldBe` PollPaymentSuccess Cancelled "15650881" "01"  99.00  "https://www.paynow.co.zw/Interface/CheckPayment/?guid=d80b830d-623b-4948-9ab1-aae4950929bd" "E4F6E55CF3408EBDF0F59951E9E7B74C17CD9798A8AE5DA7B5D68D04F014E40753C438AA55445EB42FD9E455EE7FFA7BF752AD6791DE878DAEF4B486A345EAC"
+            _ -> expectationFailure "Expected a successful poll response"
+
+    it "should fail gracefully on bad input" $ do
+        serverResponseToResult "bad input" `shouldSatisfy` isLeft
+        
+  
+transactionSpec :: Spec
+transactionSpec = do
+
+    it "should override the default return url" $ do
+        runWithDefaultClientConfig $ \client config -> do
+            let txdata = TxData Nothing Nothing (Just "https://api.com/override_return_url")
+            payment <- newClassicCheckout client testRef testAmount testEmail (Just txdata)
+            (snd $ urlParamsWithHash payment config) `shouldBe` "resulturl=http%3A%2F%2Flocalhost%3A3000%2Fresult&returnurl=https%3A%2F%2Fapi.com%2Foverride_return_url&reference=TEST%20REF&amount=99.99&id=1201&authemail=customer%40gmail.com&status=Message&hash=1C236C65D6DA213FE2F1F0B6472C6B15787CE1F5D9001ED698FEBDCC0FDFDBDFAD2310BDCB6ED8245E65BCA48D17E48F11666172BC894422DC3146582EBED2DC"
+
+    it "should override the default result url" $ do
+        runWithDefaultClientConfig $ \client config -> do
+            let txdata = TxData Nothing (Just "https://api.com/override_result_url") Nothing
+            payment <- newClassicCheckout client testRef testAmount testEmail (Just txdata)
+            (snd $ urlParamsWithHash payment config) `shouldBe` "resulturl=https%3A%2F%2Fapi.com%2Foverride_result_url&returnurl=http%3A%2F%2Flocalhost%3A3000%2Freturn&reference=TEST%20REF&amount=99.99&id=1201&authemail=customer%40gmail.com&status=Message&hash=0AA2A87F4B0564341FCB47E9C3A0CCCECFE1E0F2A8DB6029081AB8D350CB189A631589279613B1A71EFA0511189C756368537EE717EC982BE8A1449AF44BC1D5"
+
+    it "should include the additional info" $ do
+        runWithDefaultClientConfig $ \client config -> do
+            let txdata = TxData (Just "additional info to pass") Nothing Nothing
+            payment <- newClassicCheckout client testRef testAmount testEmail (Just txdata)
+            (snd $ urlParamsWithHash payment config) `shouldBe` "resulturl=http%3A%2F%2Flocalhost%3A3000%2Fresult&returnurl=http%3A%2F%2Flocalhost%3A3000%2Freturn&reference=TEST%20REF&amount=99.99&id=1201&additionalinfo=additional%20info%20to%20pass&authemail=customer%40gmail.com&status=Message&hash=2EE631E7F58FDCF10C3FC2602604DECE1E443A4F3A2D1FE05B9672C1A384FDA369FC4E529BDEDA30D651F974FFAFE9CDF494D2F176519F567E9675FE3804AEC1"
+    
+
+
+hashingSpec :: Spec
+hashingSpec = do
+    -- See https://developers.paynow.co.zw/docs/generating_hash.html        
+    it "should generate sha512 hash correctly" $ do
+        let payload = "1201TEST REF99.99A test ticket transactionhttp://www.google.com/search?q=returnurlhttp://www.google.com/search?q=resulturlMessage3e9fed89-60e1-4ce5-ab6e-6b1eb2d4f977"
+            expectedHash = "2A033FC38798D913D42ECB786B9B19645ADEDBDE788862032F1BD82CF3B92DEF84F316385D5B40DBB35F1A4FD7D5BFE73835174136463CDD48C9366B0749C689"
+        sha1Hex payload `shouldBe` expectedHash
+
+
+    it "should generate a hash for a ClassicCheckout transaction" $ do
+        runWithDefaultClient $ \client -> do
+            payment <- newClassicCheckout client testRef testAmount testEmail Nothing
+            hash <- txHash client payment
+            hash `shouldBe` "79176952B0F3A8EA27B5FF22E1568B396A37017E65EFD1130A85175AB163C2894E5D97BC154184CE20CEC9C7B01456247FA7D942B583412DD42634579ABD9C33"
+
+    it "should generate a hash for a Ecocash/ExpressCheckout transaction" $ do
+        runWithDefaultClient $ \client -> do
+            payment <- newExpressCheckout client (Ecocash "0783102754") testRef testAmount testEmail Nothing
+            hash <- txHash client payment
+            hash `shouldBe` "7FCBF1103BA1B6E6EA026DCE955200CEA23F69E42A023050457ED3E0B4489459FB4313ADD1446047F1AA9F3E0CE8E5FADCC3C4DAB386636A09DB560BD01506A3"
+
+    it "should generate a hash for a OneMoney/ExpressCheckout transaction" $ do
+        runWithDefaultClient $ \client -> do
+            payment <- newExpressCheckout client (OneMoney "0783102754") testRef testAmount testEmail Nothing
+            hash <- txHash client payment
+            hash `shouldBe` "7E1770DC2A5BA3A9498B995F3096F9DCB83F6992816B7B1E418C0B1888ADF30252BF2175240209C53025E7025033202C6C07395707EF39C8B3349E136DE3CAEB"
+ 
+    it "should generate a hash for a VMC/ExpressCheckout transaction" $ do
+        runWithDefaultClient $ \client -> do
+            payment <- newExpressCheckout client (VisaMastercard testVMCDetails) testRef testAmount testEmail Nothing
+            hash <- txHash client payment
+            hash `shouldBe` "79176952B0F3A8EA27B5FF22E1568B396A37017E65EFD1130A85175AB163C2894E5D97BC154184CE20CEC9C7B01456247FA7D942B583412DD42634579ABD9C33"
