stripe-scotty (empty) → 1.0.0.0
raw patch · 4 files changed
+270/−0 lines, 4 filesdep +aesondep +basedep +bytestring
Dependencies added: aeson, base, bytestring, http-types, scotty, stripe-concepts, stripe-signature, text, unordered-containers
Files
- changelog.md +12/−0
- library/Stripe/Scotty.hs +196/−0
- license.txt +18/−0
- stripe-scotty.cabal +44/−0
+ changelog.md view
@@ -0,0 +1,12 @@+# Changelog++All notable changes to this package 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/).++## 1.0.0.0 - 2018-12-20++- Initial release
+ library/Stripe/Scotty.hs view
@@ -0,0 +1,196 @@+{-# OPTIONS_GHC -Wall #-}++{- | For a typical webhook server implemented with Scotty, probably the only+thing you need from this module is one of these two actions:++ - 'requireSig' - If your live mode and test mode webhooks use separate+ URLs.++ - 'requireSig_eitherMode' - If your live mode and test mode webhooks use+ the same URL. -}++module Stripe.Scotty+ ( requireSig, requireSig_eitherMode+ , hasValidSig, getSig, getSigText+ ) where++-- aeson+import qualified Data.Aeson++-- base+import Control.Monad (when, (>=>))++-- bytestring+import qualified Data.ByteString+import qualified Data.ByteString.Lazy++-- http-types+import qualified Network.HTTP.Types.Status++-- scotty+import qualified Web.Scotty++-- stripe-concepts+import qualified Stripe.Concepts as Stripe++-- stripe-signature+import qualified Stripe.Signature as Stripe++-- text+import qualified Data.Text+import qualified Data.Text.Lazy+import qualified Data.Text.Lazy.Builder++-- unordered-containers+import qualified Data.HashMap.Strict++{- | Terminates request processing if the request does not contain a valid+Stripe signature header.++This action returns the request body as a strict byte string; it has to fully+evaluate the request body to do the signature check, so we might as well return+this information to you for subsequent use. -}++requireSig+ :: Stripe.WebhookSecretKey+ -> Web.Scotty.ActionM Data.ByteString.ByteString++requireSig secret =+ do+ body <- getBody+ okay <- hasValidSig secret body++ when (not okay) invalidSigAction++ return body++{- | Terminates request processing if the request does not contain a valid+Stripe signature header.++This action returns the mode and JSON request body; it has to do this much+parsing to determine the mode so that we can know which secret key to use in the+verification, so we might as well return this information to you for subsequent+use. -}++requireSig_eitherMode+ :: Stripe.BothModes (Maybe Stripe.WebhookSecretKey)+ -> Web.Scotty.ActionM (Stripe.Mode, Data.Aeson.Value)++requireSig_eitherMode secrets =+ do+ body <- getBody+ value <- parseBody body+ mode <- getMode value+ secret <- chooseSecret mode secrets+ okay <- hasValidSig secret body++ when (not okay) invalidSigAction++ return (mode, value)++getBody :: Web.Scotty.ActionM Data.ByteString.ByteString+getBody = Data.ByteString.Lazy.toStrict <$> Web.Scotty.body++invalidSigAction :: Web.Scotty.ActionM a+invalidSigAction =+ do+ Web.Scotty.status Network.HTTP.Types.Status.forbidden403+ Web.Scotty.text (Data.Text.Lazy.pack "Invalid Stripe signature")+ Web.Scotty.finish++missingKeyAction :: Stripe.Mode -> Web.Scotty.ActionM a+missingKeyAction mode =+ do+ Web.Scotty.status Network.HTTP.Types.Status.internalServerError500+ Web.Scotty.text message+ Web.Scotty.finish+ where+ message =+ Data.Text.Lazy.Builder.toLazyText $+ foldMap Data.Text.Lazy.Builder.fromString+ [ "Configuration error: No webhook secret for "+ , case mode of+ Stripe.LiveMode -> "live"+ Stripe.TestMode -> "test"+ , " mode."+ ]++chooseSecret+ :: Stripe.Mode+ -> Stripe.BothModes (Maybe Stripe.WebhookSecretKey)+ -> Web.Scotty.ActionM Stripe.WebhookSecretKey++chooseSecret mode secrets =+ case Stripe.applyMode mode secrets of+ Just x -> return x+ Nothing -> missingKeyAction mode++parseBody+ :: Data.ByteString.ByteString+ -> Web.Scotty.ActionM Data.Aeson.Value++parseBody bs =+ case Data.Aeson.eitherDecode (Data.ByteString.Lazy.fromStrict bs) of+ Left x ->+ do+ Web.Scotty.status Network.HTTP.Types.Status.badRequest400+ Web.Scotty.text (Data.Text.Lazy.pack x)+ Web.Scotty.finish+ Right x ->+ return x++getMode :: Data.Aeson.Value -> Web.Scotty.ActionM Stripe.Mode+getMode val =+ case (aesonAttr "livemode" >=> aesonBool) val of++ Nothing ->+ do+ Web.Scotty.status Network.HTTP.Types.Status.badRequest400+ Web.Scotty.text (Data.Text.Lazy.pack+ "Webhook attribute \"livemode\" is missing.")+ Web.Scotty.finish++ Just livemode ->+ return (Stripe.isLiveMode' livemode)++{- | Determines whether the request contains a valid Stripe signature header. -}++hasValidSig+ :: Stripe.WebhookSecretKey+ -> Data.ByteString.ByteString+ -> Web.Scotty.ActionM Bool++hasValidSig secret body =+ do+ sigMaybe <- getSig+ return $+ case sigMaybe of+ Nothing -> False+ Just sig -> Stripe.isSigValid sig secret body++getSigText :: Web.Scotty.ActionM (Maybe Data.Text.Text)+getSigText =+ do+ x <- Web.Scotty.header (Data.Text.Lazy.pack "Stripe-Signature")+ return (Data.Text.Lazy.toStrict <$> x)++getSig :: Web.Scotty.ActionM (Maybe Stripe.Sig)+getSig =+ do+ x <- getSigText+ return (x >>= Stripe.parseSig)++------------------------------------------------------------++-- Internal Aeson decoding functions++aesonAttr :: String -> Data.Aeson.Value -> Maybe Data.Aeson.Value+aesonAttr x = aesonObject >=> Data.HashMap.Strict.lookup (Data.Text.pack x)++aesonObject :: Data.Aeson.Value -> Maybe Data.Aeson.Object+aesonObject (Data.Aeson.Object x) = Just x+aesonObject _ = Nothing++aesonBool :: Data.Aeson.Value -> Maybe Bool+aesonBool (Data.Aeson.Bool x) = Just x+aesonBool _ = Nothing
+ license.txt view
@@ -0,0 +1,18 @@+Copyright 2018 Typeclass Consulting, LLC++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of+the Software, and to permit persons to whom the Software is furnished to do so,+subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ stripe-scotty.cabal view
@@ -0,0 +1,44 @@+name: stripe-scotty+version: 1.0.0.0++synopsis: Listen for Stripe webhook events with Scotty+category: Web++description:+ This package provides support for writing a+ <https://stripe.com/ Stripe> webhook server using Scotty.++homepage: https://github.com/typeclasses/stripe+bug-reports: https://github.com/typeclasses/stripe/issues++author: Chris Martin+maintainer: Chris Martin, Julie Moronuki++copyright: 2018 Typeclass Consulting, LLC+license: MIT+license-file: license.txt++build-type: Simple+cabal-version: >=1.10+tested-with: GHC==8.2.2, GHC==8.4.3, GHC==8.6.1++extra-source-files:+ changelog.md++library+ hs-source-dirs: library+ default-language: Haskell2010++ exposed-modules:+ Stripe.Scotty++ build-depends:+ aeson+ , base >=4.10 && <5+ , bytestring+ , http-types+ , scotty+ , stripe-concepts+ , stripe-signature+ , text+ , unordered-containers