From 5ecdc27fe4fc6911d2e71895627d71b10e34ec2f Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Tue, 19 Nov 2019 13:13:06 -0500
Subject: [PATCH] Generate recognizable unpaid and double-spend responses

---
 src/PaymentServer/Redemption.hs | 43 ++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs
index e75cb0f..f12b247 100644
--- a/src/PaymentServer/Redemption.hs
+++ b/src/PaymentServer/Redemption.hs
@@ -8,7 +8,6 @@
 module PaymentServer.Redemption
   ( RedemptionAPI
   , Redeem(Redeem)
-  , Result(Failed, Succeeded)
   , redemptionServer
   ) where
 
@@ -19,7 +18,8 @@ import Control.Monad.IO.Class
   ( liftIO
   )
 import Data.Text
-  ( pack
+  ( Text
+  , pack
   )
 import qualified Data.Text.IO as TextIO
 import Data.Text.Encoding
@@ -69,7 +69,9 @@ import PaymentServer.Issuer
   )
 
 data Result
-  = Failed
+  = Unpaid -- ^ A voucher has not been paid for.
+  | DoubleSpend -- ^ A voucher has already been redeemed.
+  | OtherFailure Text -- ^ Some other unrecognized failure mode.
   | Succeeded PublicKey [Signature] Proof
   deriving (Show, Eq)
 
@@ -87,7 +89,18 @@ instance ToJSON Redeem where
   toEncoding = genericToEncoding defaultOptions
 
 instance ToJSON Result where
-  toJSON Failed = object [ "success" .= False ]
+  toJSON Unpaid = object
+    [ "success" .= False
+    , "reason" .= ("unpaid" :: Text)
+    ]
+  toJSON DoubleSpend = object
+    [ "success" .= False
+    , "reason" .= ("double-spend" :: Text)
+    ]
+  toJSON (OtherFailure description) = object
+    [ "success" .= False
+    , "reason" .= description
+    ]
   toJSON (Succeeded key signatures proof) = object
     [ "success" .= True
     , "public-key" .= key
@@ -103,13 +116,18 @@ instance FromJSON Result where
         <$> v .: "public-key"
         <*> v .: "signatures"
         <*> v .: "proof"
-      else
-        return Failed
+      else do
+        reason <- v .: "reason"
+        if reason == "unpaid"
+        then return Unpaid
+        else if reason == "double-spend"
+        then return DoubleSpend
+        else return $ OtherFailure reason
 
 type RedemptionAPI = ReqBody '[JSON] Redeem :> Post '[JSON] Result
 
-jsonErr400 = err400
-  { errBody = encode Failed
+jsonErr400 reason = err400
+  { errBody = encode reason
   , errHeaders = [ ("Content-Type", "application/json;charset=utf-8") ]
   }
 
@@ -125,17 +143,14 @@ redeem issue database (Redeem voucher tokens) = do
   result <- liftIO $ PaymentServer.Persistence.redeemVoucher database voucher fingerprint
   case result of
     Left NotPaid -> do
-      liftIO $ putStrLn "Attempt to redeem unpaid voucher"
-      throwError jsonErr400
+      throwError $ jsonErr400 Unpaid
     Left AlreadyRedeemed -> do
-      liftIO $ putStrLn "Attempt to double-spend paid voucher"
-      throwError jsonErr400
+      throwError $ jsonErr400 DoubleSpend
     Right () -> do
       let result = issue tokens
       case result of
         Left reason -> do
-          liftIO $ TextIO.putStrLn reason
-          throwError jsonErr400
+          throwError $ jsonErr400 $ OtherFailure reason
         Right (ChallengeBypass key signatures proof) ->
           return $ Succeeded key signatures proof
 
-- 
GitLab