Implement WooCommerce order creation

This commit is contained in:
Rene Vergara 2022-12-01 14:36:06 -06:00
parent ebb87feee6
commit 3683567b81
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
8 changed files with 153 additions and 31 deletions

View file

@ -10,11 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New utility to refresh Xero tokens periodically. - New utility to refresh Xero tokens periodically.
- New module for WooCommerce interaction. - New module for WooCommerce interaction.
- New `/api/auth` endpoint to authenticate with the WooCommerce plugin - New `/auth` endpoint to authenticate with the WooCommerce plugin and corresponding tests
- New `/woopayment` endpoint to generate a new order from the WooCommerce plugin and corresponding tests
### Changed ### Changed
- Refactored code for requesting Xero tokens to make it reusable. - Refactored code for requesting Xero tokens to make it reusable.
- Change API authentication to allow for endpoints that don't require an `Authorization` header to support the WooCommerce integration
## [1.1.1] - 2022-10-08 ## [1.1.1] - 2022-10-08

View file

@ -54,6 +54,7 @@ library:
- jwt - jwt
- containers - containers
- base64-bytestring - base64-bytestring
- wai
executables: executables:
zgo-backend-exe: zgo-backend-exe:

View file

@ -191,6 +191,13 @@ upsertOrder o = do
else insert_ "orders" d else insert_ "orders" d
_ -> return () _ -> return ()
insertWooOrder :: ZGoOrder -> Action IO Database.MongoDB.Value
insertWooOrder o = do
let order = val $ updateOrderTotals o
case order of
Doc d -> insert "orders" d
_ -> fail "Couldn't parse order"
upsertXeroOrder :: ZGoOrder -> Action IO () upsertXeroOrder :: ZGoOrder -> Action IO ()
upsertXeroOrder o = do upsertXeroOrder o = do
let order = val $ updateOrderTotals o let order = val $ updateOrderTotals o

View file

@ -14,6 +14,8 @@ import Data.Aeson
import Data.Array import Data.Array
import qualified Data.Bson as B import qualified Data.Bson as B
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as C
import Data.Char import Data.Char
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import Data.HexString import Data.HexString
@ -26,6 +28,7 @@ import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.Text.Lazy as L import qualified Data.Text.Lazy as L
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
import Data.Time.Format
import Data.Typeable import Data.Typeable
import qualified Data.Vector as V import qualified Data.Vector as V
import Data.Vector.Internal.Check (doChecks) import Data.Vector.Internal.Check (doChecks)
@ -37,6 +40,7 @@ import Item
import Network.HTTP.Simple import Network.HTTP.Simple
import Network.HTTP.Types (created201) import Network.HTTP.Types (created201)
import Network.HTTP.Types.Status import Network.HTTP.Types.Status
import Network.Wai (Request, pathInfo)
import Network.Wai.Middleware.Cors import Network.Wai.Middleware.Cors
import Network.Wai.Middleware.HttpAuth import Network.Wai.Middleware.HttpAuth
import Numeric import Numeric
@ -488,6 +492,16 @@ upsertPayment pipe dbName p = do
upsert (select ["txid" =: txid p] "payments") d upsert (select ["txid" =: txid p] "payments") d
_ -> return () _ -> return ()
authSettings :: AuthSettings
authSettings = "ZGo Backend" {authIsProtected = needsAuth}
needsAuth :: Network.Wai.Request -> IO Bool
needsAuth req =
return $
case pathInfo req of
"api":_ -> True
_ -> False
-- | Main API routes -- | Main API routes
routes :: Pipe -> Config -> ScottyM () routes :: Pipe -> Config -> ScottyM ()
routes pipe config = do routes pipe config = do
@ -508,7 +522,7 @@ routes pipe config = do
middleware $ middleware $
basicAuth basicAuth
(\u p -> return $ u == "user" && secureMemFromByteString p == passkey) (\u p -> return $ u == "user" && secureMemFromByteString p == passkey)
"ZGo Backend" authSettings
--Get list of countries for UI --Get list of countries for UI
get "/api/countries" $ do get "/api/countries" $ do
countries <- liftAndCatchIO $ run listCountries countries <- liftAndCatchIO $ run listCountries
@ -606,13 +620,13 @@ routes pipe config = do
liftAndCatchIO $ run (addAccCode oAdd c) liftAndCatchIO $ run (addAccCode oAdd c)
status accepted202 status accepted202
-- Authenticate the WooCommerce plugin -- Authenticate the WooCommerce plugin
get "/api/auth" $ do get "/auth" $ do
oid <- param "ownerid" oid <- param "ownerid"
t <- param "token" t <- param "token"
siteurl <- param "siteurl" siteurl <- param "siteurl"
res <- liftAndCatchIO $ run (findWooToken (read oid)) res <- liftAndCatchIO $ run (findWooToken (read oid))
let c = cast' . Doc =<< res let c1 = cast' . Doc =<< res
case c of case c1 of
Nothing -> do Nothing -> do
status accepted202 status accepted202
Web.Scotty.json Web.Scotty.json
@ -645,6 +659,81 @@ routes pipe config = do
[ "authorized" .= False [ "authorized" .= False
, "message" .= ("Token mismatch" :: String) , "message" .= ("Token mismatch" :: String)
]) ])
get "/woopayment" $ do
oid <- param "ownerid"
t <- param "token"
ordId <- param "order_id"
date <- param "date"
curr <- param "currency"
amount <- param "amount"
sUrl <- param "siteurl"
res <- liftAndCatchIO $ run (findWooToken (read oid))
let c = cast' . Doc =<< res
case c of
Nothing -> do
status accepted202
Web.Scotty.json
(object ["message" .= ("Plugin not setup in ZGo" :: String)])
Just x ->
if t == w_token x &&
(E.decodeUtf8With lenientDecode . B64.decodeLenient . C.pack) sUrl ==
fromMaybe "" (w_url x)
then do
zecPriceDb <- liftAndCatchIO (run (findPrice curr))
let zecPrice = parseZGoPrice =<< zecPriceDb
case zecPrice of
Nothing -> do
status accepted202
Web.Scotty.json
(object ["message" .= ("Currency not supported" :: String)])
Just zP -> do
ownerDb <-
liftAndCatchIO $
run (findOwnerById (T.pack . show $ w_owner x))
let owner = cast' . Doc =<< ownerDb
case owner of
Nothing -> do
status accepted202
Web.Scotty.json
(object ["message" .= ("Owner not found" :: String)])
Just o ->
if opaid o
then do
let newOrder =
ZGoOrder
Nothing
(oaddress o)
("WC-" <> oname o)
(parseTimeOrError
True
defaultTimeLocale
"%Y-%0m-%0d"
date)
True
(T.pack curr)
(price zP)
0.0
0.0
[ LineItem
1.0
(oname o <> " order " <> ordId)
amount
]
False
(T.concat [T.pack sUrl, "-", ordId])
""
newId <- liftAndCatchIO $ run (insertWooOrder newOrder)
status ok200
Web.Scotty.json (object ["order" .= show newId])
else do
status accepted202
Web.Scotty.json
(object
["message" .= ("ZGo shop not paid for" :: String)])
else do
status accepted202
Web.Scotty.json
(object ["message" .= ("Incorrect plugin config" :: String)])
--Get user associated with session --Get user associated with session
get "/api/user" $ do get "/api/user" $ do
sess <- param "session" sess <- param "session"

View file

@ -17,8 +17,8 @@
# #
# resolver: ./custom-snapshot.yaml # resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: resolver: lts-19.33
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml #url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml
# User packages to be built. # User packages to be built.
# Various formats can be used as shown in the example below. # Various formats can be used as shown in the example below.

View file

@ -5,20 +5,19 @@
packages: packages:
- completed: - completed:
commit: 085c16fb21b9f856a435a3faab980e7e0b319341
git: https://github.com/reach-sh/haskell-hexstring.git
name: hexstring name: hexstring
version: 0.11.1
git: https://github.com/reach-sh/haskell-hexstring.git
pantry-tree: pantry-tree:
size: 687
sha256: 9ecf67856f59dfb382b283eceb42e4fc1865935d1a7e59111556ed381c6a2ffd sha256: 9ecf67856f59dfb382b283eceb42e4fc1865935d1a7e59111556ed381c6a2ffd
commit: 085c16fb21b9f856a435a3faab980e7e0b319341 size: 687
version: 0.11.1
original: original:
git: https://github.com/reach-sh/haskell-hexstring.git
commit: 085c16fb21b9f856a435a3faab980e7e0b319341 commit: 085c16fb21b9f856a435a3faab980e7e0b319341
git: https://github.com/reach-sh/haskell-hexstring.git
snapshots: snapshots:
- completed: - completed:
size: 618683 sha256: 6d1532d40621957a25bad5195bfca7938e8a06d923c91bc52aa0f3c41181f2d4
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml size: 619204
sha256: d4ee004c46ba878d2f304f5d748d493057be579192a8d148527f3ba55c9df57f url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/33.yaml
original: original: lts-19.33
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml

View file

@ -255,8 +255,8 @@ main = do
it "generate token" pending it "generate token" pending
it "authenticate with incorrect owner" $ do it "authenticate with incorrect owner" $ do
req <- req <-
testGet testPublicGet
"/api/auth/" "/auth"
[ ("ownerid", Just "62cca13f5530331e2a900001") [ ("ownerid", Just "62cca13f5530331e2a900001")
, ("token", Just "89bd9d8d69a674e0f467cc8796ed151a") , ("token", Just "89bd9d8d69a674e0f467cc8796ed151a")
, ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8") , ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8")
@ -265,9 +265,9 @@ main = do
getResponseStatus (res :: Response A.Value) `shouldBe` accepted202 getResponseStatus (res :: Response A.Value) `shouldBe` accepted202
it "authenticate with incorrect token" $ do it "authenticate with incorrect token" $ do
req <- req <-
testGet testPublicGet
"/api/auth/" "/auth"
[ ("ownerid", Just "62cca13f5530331e2a97c78e") [ ("ownerid", Just "627ad3492b05a76be3000001")
, ("token", Just "89bd9d8d69a674e0f467cc8796000000") , ("token", Just "89bd9d8d69a674e0f467cc8796000000")
, ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8") , ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8")
] ]
@ -275,9 +275,9 @@ main = do
getResponseStatus (res :: Response A.Value) `shouldBe` accepted202 getResponseStatus (res :: Response A.Value) `shouldBe` accepted202
it "authenticate with correct token" $ do it "authenticate with correct token" $ do
req <- req <-
testGet testPublicGet
"/api/auth/" "/auth"
[ ("ownerid", Just "62cca13f5530331e2a97c78e") [ ("ownerid", Just "627ad3492b05a76be3000001")
, ("token", Just "89bd9d8d69a674e0f467cc8796ed151a") , ("token", Just "89bd9d8d69a674e0f467cc8796ed151a")
, ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8") , ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8")
] ]
@ -285,14 +285,28 @@ main = do
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "authenticate with correct token on existing shop" $ do it "authenticate with correct token on existing shop" $ do
req <- req <-
testGet testPublicGet
"/api/auth/" "/auth"
[ ("ownerid", Just "62cca13f5530331e2a97c78e") [ ("ownerid", Just "627ad3492b05a76be3000001")
, ("token", Just "89bd9d8d69a674e0f467cc8796ed151a") , ("token", Just "89bd9d8d69a674e0f467cc8796ed151a")
, ("siteurl", Just "aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8") , ("siteurl", Just "aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8")
] ]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` accepted202 getResponseStatus (res :: Response A.Value) `shouldBe` accepted202
it "request order creation" $ do
req <-
testPublicGet
"/woopayment"
[ ("ownerid", Just "627ad3492b05a76be3000001")
, ("token", Just "89bd9d8d69a674e0f467cc8796ed151a")
, ("siteurl", Just "aHR0cHM6Ly93d3cudGVjcHJvdmFsLmNvbS8")
, ("order_id", Just "1234")
, ("currency", Just "usd")
, ("amount", Just "100.0")
, ("date", Just "2022-12-01")
]
res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200
around handleDb $ around handleDb $
describe "Database actions" $ do describe "Database actions" $ do
describe "authentication" $ do describe "authentication" $ do
@ -540,6 +554,15 @@ testGet endpoint body = do
setRequestMethod "GET" $ setRequestPath endpoint defaultRequest setRequestMethod "GET" $ setRequestPath endpoint defaultRequest
return testRequest return testRequest
testPublicGet ::
B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> IO Request
testPublicGet endpoint body = do
let testRequest =
setRequestQueryString body $
setRequestPort 3000 $
setRequestMethod "GET" $ setRequestPath endpoint defaultRequest
return testRequest
testPost :: B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> IO Request testPost :: B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> IO Request
testPost endpoint body = do testPost endpoint body = do
let user = "user" let user = "user"
@ -664,7 +687,7 @@ startAPI config = do
"" ""
"bubbarocks.io" "bubbarocks.io"
"United States" "United States"
False True
False False
False False
(UTCTime (fromGregorian 2022 4 16) (secondsToDiffTime 0)) (UTCTime (fromGregorian 2022 4 16) (secondsToDiffTime 0))
@ -711,7 +734,7 @@ startAPI config = do
let myWooToken = let myWooToken =
WooToken WooToken
Nothing Nothing
(read "62cca13f5530331e2a97c78e") (read "627ad3492b05a76be3000001")
"89bd9d8d69a674e0f467cc8796ed151a" "89bd9d8d69a674e0f467cc8796ed151a"
Nothing Nothing
let wooTest = val myWooToken let wooTest = val myWooToken

View file

@ -1,6 +1,6 @@
cabal-version: 1.12 cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.34.4. -- This file has been generated from package.yaml by hpack version 0.35.0.
-- --
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
@ -66,6 +66,7 @@ library
, time , time
, unordered-containers , unordered-containers
, vector , vector
, wai
, wai-cors , wai-cors
, wai-extra , wai-extra
, warp-tls , warp-tls