mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-14 02:37:44 +00:00
store: rename badge service to StoreService
This commit is contained in:
1 parent
8990562850
commit
4aaeadd591
28 files changed
+386
-386
No files matched your search
+1
-1
@@ -71,7 +71,7 @@ defaultChatConfig =
|
||||
chatVRange = supportedChatVRange,
|
||||
callVRange = supportedCallVRange,
|
||||
badgePublicKeys = M.mapKeys fromIntegral entitlementIssuerKeys,
|
||||
badgeServiceAddress = Just $ either error id $ strDecode "https://smp5.simplex.im/a#ooSNWlEZTO2RPE0Ff5ZoybAs5zEhWLMlQrXesnhaZHM",
|
||||
storeServiceAddress = Just $ either error id $ strDecode "https://smp5.simplex.im/a#ooSNWlEZTO2RPE0Ff5ZoybAs5zEhWLMlQrXesnhaZHM",
|
||||
badgeCurrentTime = getCurrentTime,
|
||||
badgeRetryInterval = RetryInterval {initialInterval = 30_000000, increaseAfter = 0, maxInterval = 3600_000000},
|
||||
confirmMigrations = MCConsole,
|
||||
|
||||
@@ -188,7 +188,7 @@ mkBadgeStatus now verified BadgeInfo {badgeExpiry = e} = case verified of
|
||||
|
||||
-- A badge credential (own, secret) and a proof (a presentation) are independent records.
|
||||
-- badgeKeyIdx is the issuer key index: it tells verifiers which configured key to use.
|
||||
-- Only proofs ride the wire (in a profile); credentials come from the badge service. Neither is
|
||||
-- Only proofs ride the wire (in a profile); credentials come from the store service. Neither is
|
||||
-- ever serialized as a sum - each travels as its own record, so the JSON carries no credential/proof tag.
|
||||
|
||||
data BadgeCredential = BadgeCredential
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | Badge redemption codes, shared by the client, the badge service and the checkout site.
|
||||
-- | Badge redemption codes, shared by the client, the store service and the checkout site.
|
||||
--
|
||||
-- A code is "SB-" and 20 Crockford base32 characters in four groups of five:
|
||||
-- 19 payload characters and a final check character.
|
||||
|
||||
@@ -25,7 +25,7 @@ import Data.Time.Calendar (addDays, addGregorianMonthsClip, toGregorian)
|
||||
import Data.Time.Calendar.WeekDate (toWeekDate)
|
||||
import Data.Time.Clock (NominalDiffTime, UTCTime (..), addUTCTime)
|
||||
import Simplex.Chat.Badges (BadgeType)
|
||||
import Simplex.Chat.Badges.Service (StatementCreditType (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..))
|
||||
import Simplex.Chat.StoreService (StatementCreditType (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..))
|
||||
|
||||
-- The calendar difference overshoots by at most one month, so one comparison settles it.
|
||||
monthsBetween :: UTCTime -> UTCTime -> Integer
|
||||
|
||||
@@ -19,7 +19,7 @@ module Simplex.Chat.Badges.Types
|
||||
LedgerCreditType (..),
|
||||
LedgerDebitType (..),
|
||||
BadgeAlertKind (..),
|
||||
BadgeServiceErrorCode (..),
|
||||
StoreServiceErrorCode (..),
|
||||
BadgeIssueFailure (..),
|
||||
BadgeIssueError (..),
|
||||
BadgeFunding (..),
|
||||
@@ -225,31 +225,31 @@ data BadgeAlertPrice = BadgeAlertPrice
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data BadgeServiceErrorCode
|
||||
= BSEBadRequest
|
||||
| BSEUnsupportedVersion
|
||||
| BSEUnknownPurchaseKey
|
||||
| BSEUnknownOfferId
|
||||
| BSEOfferDisabled
|
||||
| BSEOfferMismatch
|
||||
| BSEProductUnavailable
|
||||
| BSEPaymentNotEntitled
|
||||
| BSEPaymentPending
|
||||
| BSEProviderUnavailable
|
||||
| BSERateLimited
|
||||
| BSECodeInvalid
|
||||
| BSECodeUsed
|
||||
| BSECodeExpired
|
||||
| BSEReceiptInvalid
|
||||
| BSEReceiptUsed
|
||||
| BSEInternal
|
||||
| BSEUnknown Text -- forwards-compatible: service is deployed ahead of clients
|
||||
data StoreServiceErrorCode
|
||||
= STEBadRequest
|
||||
| STEUnsupportedVersion
|
||||
| STEUnknownPurchaseKey
|
||||
| STEUnknownOfferId
|
||||
| STEOfferDisabled
|
||||
| STEOfferMismatch
|
||||
| STEProductUnavailable
|
||||
| STEPaymentNotEntitled
|
||||
| STEPaymentPending
|
||||
| STEProviderUnavailable
|
||||
| STERateLimited
|
||||
| STECodeInvalid
|
||||
| STECodeUsed
|
||||
| STECodeExpired
|
||||
| STEReceiptInvalid
|
||||
| STEReceiptUsed
|
||||
| STEInternal
|
||||
| STEUnknown Text -- forwards-compatible: service is deployed ahead of clients
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | Why a renewal request ended without a credential stored. retryable is the service's own view of
|
||||
-- transience: it gave retryAfter, which holds for codes this version does not know.
|
||||
data BadgeIssueFailure
|
||||
= BIFServiceError {code :: BadgeServiceErrorCode, retryable :: Bool}
|
||||
= BIFServiceError {code :: StoreServiceErrorCode, retryable :: Bool}
|
||||
| BIFServiceTimeout
|
||||
| BIFNetwork {agentError :: Text}
|
||||
| BIFInvalidCredential
|
||||
@@ -313,52 +313,52 @@ instance FromField BadgeCodePaymentStatus where fromField = fromTextField_ textD
|
||||
|
||||
instance ToField BadgeCodePaymentStatus where toField = toField . textEncode
|
||||
|
||||
instance TextEncoding BadgeServiceErrorCode where
|
||||
instance TextEncoding StoreServiceErrorCode where
|
||||
textEncode = \case
|
||||
BSEBadRequest -> "bad_request"
|
||||
BSEUnsupportedVersion -> "unsupported_version"
|
||||
BSEUnknownPurchaseKey -> "unknown_purchase_key"
|
||||
BSEUnknownOfferId -> "unknown_offer_id"
|
||||
BSEOfferDisabled -> "offer_disabled"
|
||||
BSEOfferMismatch -> "offer_mismatch"
|
||||
BSEProductUnavailable -> "product_unavailable"
|
||||
BSEPaymentNotEntitled -> "payment_not_entitled"
|
||||
BSEPaymentPending -> "payment_pending"
|
||||
BSEProviderUnavailable -> "provider_unavailable"
|
||||
BSERateLimited -> "rate_limited"
|
||||
BSECodeInvalid -> "code_invalid"
|
||||
BSECodeUsed -> "code_used"
|
||||
BSECodeExpired -> "code_expired"
|
||||
BSEReceiptInvalid -> "receipt_invalid"
|
||||
BSEReceiptUsed -> "receipt_used"
|
||||
BSEInternal -> "internal"
|
||||
BSEUnknown t -> t
|
||||
STEBadRequest -> "bad_request"
|
||||
STEUnsupportedVersion -> "unsupported_version"
|
||||
STEUnknownPurchaseKey -> "unknown_purchase_key"
|
||||
STEUnknownOfferId -> "unknown_offer_id"
|
||||
STEOfferDisabled -> "offer_disabled"
|
||||
STEOfferMismatch -> "offer_mismatch"
|
||||
STEProductUnavailable -> "product_unavailable"
|
||||
STEPaymentNotEntitled -> "payment_not_entitled"
|
||||
STEPaymentPending -> "payment_pending"
|
||||
STEProviderUnavailable -> "provider_unavailable"
|
||||
STERateLimited -> "rate_limited"
|
||||
STECodeInvalid -> "code_invalid"
|
||||
STECodeUsed -> "code_used"
|
||||
STECodeExpired -> "code_expired"
|
||||
STEReceiptInvalid -> "receipt_invalid"
|
||||
STEReceiptUsed -> "receipt_used"
|
||||
STEInternal -> "internal"
|
||||
STEUnknown t -> t
|
||||
textDecode s = Just $ case s of
|
||||
"bad_request" -> BSEBadRequest
|
||||
"unsupported_version" -> BSEUnsupportedVersion
|
||||
"unknown_purchase_key" -> BSEUnknownPurchaseKey
|
||||
"unknown_offer_id" -> BSEUnknownOfferId
|
||||
"offer_disabled" -> BSEOfferDisabled
|
||||
"offer_mismatch" -> BSEOfferMismatch
|
||||
"product_unavailable" -> BSEProductUnavailable
|
||||
"payment_not_entitled" -> BSEPaymentNotEntitled
|
||||
"payment_pending" -> BSEPaymentPending
|
||||
"provider_unavailable" -> BSEProviderUnavailable
|
||||
"rate_limited" -> BSERateLimited
|
||||
"code_invalid" -> BSECodeInvalid
|
||||
"code_used" -> BSECodeUsed
|
||||
"code_expired" -> BSECodeExpired
|
||||
"receipt_invalid" -> BSEReceiptInvalid
|
||||
"receipt_used" -> BSEReceiptUsed
|
||||
"internal" -> BSEInternal
|
||||
t -> BSEUnknown t
|
||||
"bad_request" -> STEBadRequest
|
||||
"unsupported_version" -> STEUnsupportedVersion
|
||||
"unknown_purchase_key" -> STEUnknownPurchaseKey
|
||||
"unknown_offer_id" -> STEUnknownOfferId
|
||||
"offer_disabled" -> STEOfferDisabled
|
||||
"offer_mismatch" -> STEOfferMismatch
|
||||
"product_unavailable" -> STEProductUnavailable
|
||||
"payment_not_entitled" -> STEPaymentNotEntitled
|
||||
"payment_pending" -> STEPaymentPending
|
||||
"provider_unavailable" -> STEProviderUnavailable
|
||||
"rate_limited" -> STERateLimited
|
||||
"code_invalid" -> STECodeInvalid
|
||||
"code_used" -> STECodeUsed
|
||||
"code_expired" -> STECodeExpired
|
||||
"receipt_invalid" -> STEReceiptInvalid
|
||||
"receipt_used" -> STEReceiptUsed
|
||||
"internal" -> STEInternal
|
||||
t -> STEUnknown t
|
||||
|
||||
instance ToJSON BadgeServiceErrorCode where
|
||||
instance ToJSON StoreServiceErrorCode where
|
||||
toJSON = textToJSON
|
||||
toEncoding = textToEncoding
|
||||
|
||||
instance FromJSON BadgeServiceErrorCode where
|
||||
parseJSON = textParseJSON "BadgeServiceErrorCode"
|
||||
instance FromJSON StoreServiceErrorCode where
|
||||
parseJSON = textParseJSON "StoreServiceErrorCode"
|
||||
|
||||
instance StrEncoding BadgeIssueFailure where
|
||||
strEncode = \case
|
||||
@@ -388,7 +388,7 @@ instance StrEncoding BadgeIssueFailure where
|
||||
"retry" -> pure True
|
||||
"final" -> pure False
|
||||
_ -> fail "bad BadgeIssueFailure retry flag"
|
||||
codeP = A.takeByteString >>= maybe (fail "bad BadgeServiceErrorCode") pure . textDecode . safeDecodeUtf8
|
||||
codeP = A.takeByteString >>= maybe (fail "bad StoreServiceErrorCode") pure . textDecode . safeDecodeUtf8
|
||||
restP = safeDecodeUtf8 <$> (A.space *> A.takeByteString)
|
||||
|
||||
instance ToField BadgeIssueFailure where toField = toField . decodeLatin1 . strEncode
|
||||
|
||||
@@ -85,7 +85,7 @@ import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SMPWebPortServers (..), SocksMode (..))
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Chat.Badges (BadgeCredential, FileSizeLimits, LocalBadge)
|
||||
import Simplex.Chat.Badges.Service (BadgeServiceErrorCode, StatementEntry)
|
||||
import Simplex.Chat.StoreService (StatementEntry, StoreServiceErrorCode)
|
||||
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind, BadgeState (..))
|
||||
import Simplex.Messaging.Crypto.BBS (BBSPublicKey)
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
@@ -148,8 +148,8 @@ data ChatConfig = ChatConfig
|
||||
callVRange :: VersionRangeCall,
|
||||
-- issuer public keys by index: credentials and proofs name the key that signed them, for rotation
|
||||
badgePublicKeys :: Map Int BBSPublicKey,
|
||||
-- Nothing until the badge service is deployed
|
||||
badgeServiceAddress :: Maybe (ConnectTarget 'CMContact),
|
||||
-- Nothing until the store service is deployed
|
||||
storeServiceAddress :: Maybe (ConnectTarget 'CMContact),
|
||||
-- the only clock badge code reads, so tests can shift it; production arithmetic is unchanged
|
||||
badgeCurrentTime :: IO UTCTime,
|
||||
-- how long a badge worker waits before repeating a renewal that failed for a passing reason
|
||||
@@ -667,7 +667,7 @@ data ChatCommand
|
||||
| UpdateProfileImage (Maybe ImageData) -- UserId (not used in UI)
|
||||
| UpdateProfileImageFromFile FilePath -- set profile image from a .png/.jpg/.jpeg file
|
||||
| AddBadge BadgeCredential -- attach an issued badge credential (testing; credential from `simplex-chat badge sign`)
|
||||
| APIRedeemBadgeCode {userId :: UserId, code :: Text} -- redeem a badge code with the configured badge service
|
||||
| APIRedeemBadgeCode {userId :: UserId, code :: Text} -- redeem a badge code with the configured store service
|
||||
| APIGetBadgeState {userId :: UserId} -- the user's badges, their balances and any current alert
|
||||
| APIGetBadgeLedger {userId :: UserId, badgePurchaseId :: Int64} -- the purchase's ledger, oldest first
|
||||
-- episode is last because it is free text: it is the value that makes one occurrence of an
|
||||
@@ -1525,7 +1525,7 @@ data BadgeRedeemError
|
||||
= BREInvalidCode -- format or check character
|
||||
| BREServiceNotConfigured
|
||||
| BREBadgeActive
|
||||
| BREServiceError {serviceError :: BadgeServiceErrorCode}
|
||||
| BREServiceError {serviceError :: StoreServiceErrorCode}
|
||||
| BREInvalidResponse {message :: String}
|
||||
| BREUnknownKeyIndex
|
||||
| BRECredentialNotVerified
|
||||
|
||||
@@ -63,7 +63,7 @@ import Simplex.Chat.Badges (BadgeCredential (..), BadgeInfo (..), BadgeMasterKey
|
||||
import qualified Simplex.Chat.Badges.Ledger as L
|
||||
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind (..), BadgeIssueError (..), BadgeIssueFailure (..), BadgeState (..))
|
||||
import Simplex.Chat.Badges.Code (badgeCodeText, parseBadgeCode)
|
||||
import Simplex.Chat.Badges.Service (BadgeBalance (..), BadgeServiceCommand (..), BadgeServiceErrorCode (..), BadgeServiceRequest (..), BadgeServiceResponse (..), BadgeStatement (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..), currentBadgeServiceVersion)
|
||||
import Simplex.Chat.StoreService (BadgeBalance (..), BadgeStatement (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..), StoreServiceCommand (..), StoreServiceErrorCode (..), StoreServiceRequest (..), StoreServiceResponse (..), currentStoreServiceVersion)
|
||||
import Simplex.Chat.Names (SimplexDomainProof (..), SimplexDomainClaim (..), claimDomain, mkDomainClaim)
|
||||
import Simplex.Chat.Wallet (deriveAccount, masterMnemonic, newEntropy)
|
||||
import Simplex.Chat.Call
|
||||
@@ -5432,7 +5432,7 @@ presentUserBadgeToContacts user'@User {userId, profile = LocalProfile {localBadg
|
||||
redeemBadgeCode :: NetworkRequestMode -> User -> Text -> CM ChatResponse
|
||||
redeemBadgeCode nm user@User {userId} codeText = do
|
||||
code <- maybe (throwRedeemError BREInvalidCode) pure $ parseBadgeCode codeText
|
||||
sendTarget <- asks (badgeServiceAddress . config) >>= maybe (throwRedeemError BREServiceNotConfigured) pure
|
||||
sendTarget <- asks (storeServiceAddress . config) >>= maybe (throwRedeemError BREServiceNotConfigured) pure
|
||||
g <- asks random
|
||||
now <- liftIO getCurrentTime
|
||||
let codeSent = badgeCodeText code
|
||||
@@ -5446,15 +5446,15 @@ redeemBadgeCode nm user@User {userId} codeText = do
|
||||
unless replaying $ whenM (withStore' (`userHasBadge` user)) $ throwRedeemError BREBadgeActive
|
||||
redemption@BadgeCodeRedemption {purchaseKey, purchasePrivKey, masterKey} <-
|
||||
maybe (withStore' $ \db -> createBadgeCodeRedemption db g user codeSent now) pure redemption_
|
||||
let req = BadgeServiceRequest {version = currentBadgeServiceVersion, purchaseKey = Just purchaseKey, request = BSCRedeemBadgeCode {masterKey, code = codeSent}}
|
||||
let req = StoreServiceRequest {version = currentStoreServiceVersion, purchaseKey = Just purchaseKey, request = STCRedeemBadgeCode {masterKey, code = codeSent}}
|
||||
respBytes <- sendServiceRequestBytes nm user sendTarget Nothing (Just purchasePrivKey) req
|
||||
respData <- either (const $ throwRedeemError $ BREInvalidResponse "not JSON") pure $ J.eitherDecodeStrict' respBytes
|
||||
case J.fromJSON (J.Object respData) of
|
||||
J.Error _ -> throwRedeemError $ BREInvalidResponse "not a badge service response"
|
||||
J.Success BSPError {code = errCode} -> do
|
||||
J.Error _ -> throwRedeemError $ BREInvalidResponse "not a store service response"
|
||||
J.Success STPError {code = errCode} -> do
|
||||
when (terminalCodeError errCode) $ withStore' $ \db -> deleteBadgeCodeRedemption db (redemptionId redemption)
|
||||
throwRedeemError $ BREServiceError errCode
|
||||
J.Success BSPBadgeCredential {credential = Just cred, statement} -> storeRedeemedBadge user redemption cred statement
|
||||
J.Success STPBadgeCredential {credential = Just cred, statement} -> storeRedeemedBadge user redemption cred statement
|
||||
J.Success _ -> throwRedeemError $ BREInvalidResponse "unexpected response type"
|
||||
-- outside the badge lock: the chat lock must not be taken under it
|
||||
mapM_ presentUserBadgeToContacts present_
|
||||
@@ -5462,23 +5462,23 @@ redeemBadgeCode nm user@User {userId} codeText = do
|
||||
where
|
||||
-- the code will never work, so the keys stashed for it are dead; a timeout keeps them
|
||||
terminalCodeError = \case
|
||||
BSECodeInvalid -> True
|
||||
BSECodeUsed -> True
|
||||
BSECodeExpired -> True
|
||||
STECodeInvalid -> True
|
||||
STECodeUsed -> True
|
||||
STECodeExpired -> True
|
||||
_ -> False
|
||||
|
||||
throwRedeemError :: BadgeRedeemError -> CM a
|
||||
throwRedeemError = throwChatError . CEBadgeRedeemError
|
||||
|
||||
badgeServiceErrorText :: BadgeServiceErrorCode -> Text
|
||||
badgeServiceErrorText = textEncode . boundedServiceErrorCode
|
||||
storeServiceErrorText :: StoreServiceErrorCode -> Text
|
||||
storeServiceErrorText = textEncode . boundedServiceErrorCode
|
||||
|
||||
-- | An unknown code is reported and recorded, since the service is deployed ahead of clients, but
|
||||
-- its text is the service's - so it is bounded and stripped before it reaches a terminal that acts
|
||||
-- on controls, or a sentence the app shows the user as its own.
|
||||
boundedServiceErrorCode :: BadgeServiceErrorCode -> BadgeServiceErrorCode
|
||||
boundedServiceErrorCode :: StoreServiceErrorCode -> StoreServiceErrorCode
|
||||
boundedServiceErrorCode = \case
|
||||
BSEUnknown t -> BSEUnknown $ case T.filter errorCodeChar (T.take 32 t) of
|
||||
STEUnknown t -> STEUnknown $ case T.filter errorCodeChar (T.take 32 t) of
|
||||
"" -> "unknown"
|
||||
t' -> t'
|
||||
code -> code
|
||||
@@ -5492,7 +5492,7 @@ badgeNow = asks (badgeCurrentTime . config) >>= liftIO
|
||||
-- | A signal carries nothing: each pass derives its work from stored state, so a signal lost or
|
||||
-- duplicated changes no outcome.
|
||||
startBadgeWork :: User -> CM' ()
|
||||
startBadgeWork user = whenM (isJust <$> asks (badgeServiceAddress . config)) $ void $ getBadgeWorker user
|
||||
startBadgeWork user = whenM (isJust <$> asks (storeServiceAddress . config)) $ void $ getBadgeWorker user
|
||||
|
||||
-- | Exactly one caller starts the thread and the rest wait for it: the lookup and the create cannot
|
||||
-- be one transaction, because starting a thread is not STM.
|
||||
@@ -5751,7 +5751,7 @@ badgeIssueFailure e = case e of
|
||||
badgeFailureTransient :: BadgeIssueFailure -> Bool
|
||||
badgeFailureTransient = \case
|
||||
-- the service withholds retryAfter from internal to avoid being pressed while failing, not because it is final
|
||||
BIFServiceError {code = BSEInternal} -> True
|
||||
BIFServiceError {code = STEInternal} -> True
|
||||
BIFServiceError {retryable} -> retryable
|
||||
BIFServiceTimeout -> True
|
||||
BIFNetwork {} -> True
|
||||
@@ -5770,21 +5770,21 @@ badgeErrorRetry = badgeFailureTransient . badgeIssueFailure
|
||||
-- nothing else records one.
|
||||
requestBadgeIssue :: UserId -> UserBadgePurchase -> UTCTime -> CM (Either UTCTime StatementEntry)
|
||||
requestBadgeIssue userId UserBadgePurchase {badgePurchaseId, badgeType, purchaseKey, purchasePrivKey, masterKey} now = do
|
||||
sendTarget <- asks (badgeServiceAddress . config) >>= maybe (throwCmdError "badge service not configured") pure
|
||||
sendTarget <- asks (storeServiceAddress . config) >>= maybe (throwCmdError "store service not configured") pure
|
||||
withEntityLock "badgeIssue" (CLBadgeUser userId) $ do
|
||||
user <- withStore $ \db -> getUser db userId
|
||||
lastEntry <- withStore' (`getBadgeLedgerLastEntry` badgePurchaseId) >>= maybe (throwCmdError "badge ledger has no entry to assert") pure
|
||||
let req =
|
||||
BadgeServiceRequest
|
||||
{ version = currentBadgeServiceVersion,
|
||||
StoreServiceRequest
|
||||
{ version = currentStoreServiceVersion,
|
||||
purchaseKey = Just purchaseKey,
|
||||
request = BSCIssueBadge {balance = BadgeBalance {lastEntry}}
|
||||
request = STCIssueBadge {balance = BadgeBalance {lastEntry}}
|
||||
}
|
||||
respData <-
|
||||
sendServiceRequestTo NRMBackground user sendTarget Nothing (Just purchasePrivKey) req
|
||||
`catchAllErrors` \e -> recordFailure (badgeIssueFailure e) >> throwError e
|
||||
case J.fromJSON (J.Object respData) of
|
||||
J.Success BSPBadgeCredential {credential = sentCred_, statement} -> do
|
||||
J.Success STPBadgeCredential {credential = sentCred_, statement} -> do
|
||||
verifiedCred_ <- verifyIssuedCredential masterKey sentCred_
|
||||
g <- asks random
|
||||
-- read again: now was taken before a lock wait and an untimed request, and the check reads
|
||||
@@ -5798,11 +5798,11 @@ requestBadgeIssue userId UserBadgePurchase {badgePurchaseId, badgeType, purchase
|
||||
(Just _, Nothing, _) -> recordFailure BIFInvalidCredential
|
||||
(_, _, False) -> recordUnexpected "issued badge credential has no ledger row to store it against"
|
||||
-- no credential is the service saying the months ran out, which its statement then shows
|
||||
(Nothing, _, _) | maybe False ((> 0) . balanceMonths) balance_ -> recordUnexpected "badge service issued no credential"
|
||||
(Nothing, _, _) | maybe False ((> 0) . balanceMonths) balance_ -> recordUnexpected "store service issued no credential"
|
||||
_ -> pure ()
|
||||
maybe (throwCmdError "badge ledger has no balance") (pure . Right) balance_
|
||||
J.Success BSPError {code, retryAfter} -> do
|
||||
eToView $ ChatError $ CECommandError $ "badge service error: " <> T.unpack (badgeServiceErrorText code)
|
||||
J.Success STPError {code, retryAfter} -> do
|
||||
eToView $ ChatError $ CECommandError $ "store service error: " <> T.unpack (storeServiceErrorText code)
|
||||
recordFailure BIFServiceError {code = boundedServiceErrorCode code, retryable = isJust retryAfter}
|
||||
ri <- asks $ badgeRetryInterval . config
|
||||
pure $ Left $ badgeRetryAfter ri retryAfter `addUTCTime` now
|
||||
@@ -5810,7 +5810,7 @@ requestBadgeIssue userId UserBadgePurchase {badgePurchaseId, badgeType, purchase
|
||||
recordFailure BIFUnexpected {message = unexpectedResponse}
|
||||
throwCmdError $ T.unpack unexpectedResponse
|
||||
where
|
||||
unexpectedResponse = "unexpected badge service response"
|
||||
unexpectedResponse = "unexpected store service response"
|
||||
recordUnexpected message = do
|
||||
eToView $ ChatError $ CEInternalError $ T.unpack message
|
||||
recordFailure BIFUnexpected {message}
|
||||
|
||||
@@ -40,7 +40,7 @@ import Data.Text (Text)
|
||||
import Data.Time.Clock (UTCTime)
|
||||
import Simplex.Chat.Badges
|
||||
import Simplex.Chat.Badges.Ledger
|
||||
import Simplex.Chat.Badges.Service (StatementCreditType (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..))
|
||||
import Simplex.Chat.StoreService (StatementCreditType (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..))
|
||||
import Simplex.Chat.Badges.Types (BadgeAlertKind, BadgeIssueError (..), BadgeIssueFailure, BadgePurchaseStatus (..))
|
||||
import Simplex.Chat.Store.Shared (insertedRowId)
|
||||
import Simplex.Chat.Types
|
||||
|
||||
@@ -7,7 +7,7 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
-- the same tables in the client and in the badge service; '@' is replaced with the table name prefix
|
||||
-- the same tables in the client and in the store service; '@' is replaced with the table name prefix
|
||||
badgeSchema :: Text -> Text
|
||||
badgeSchema pfx = withPrefix pfx badgeSchemaTables
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import qualified Data.Text as T
|
||||
import Database.SQLite.Simple (Query (..))
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- the same tables in the client and in the badge service; '@' is replaced with the table name prefix
|
||||
-- the same tables in the client and in the store service; '@' is replaced with the table name prefix
|
||||
badgeSchema :: Text -> Query
|
||||
badgeSchema pfx = withPrefix pfx badgeSchemaTables
|
||||
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Simplex.Chat.Badges.Service
|
||||
( BadgeServiceRequest (..),
|
||||
BadgeServiceCommand (..),
|
||||
BadgeServiceVersion,
|
||||
VersionBadgeService,
|
||||
VersionRangeBadgeService,
|
||||
pattern VersionBadgeService,
|
||||
initialBadgeServiceVersion,
|
||||
currentBadgeServiceVersion,
|
||||
supportedBadgeServiceVRange,
|
||||
module Simplex.Chat.StoreService
|
||||
( StoreServiceRequest (..),
|
||||
StoreServiceCommand (..),
|
||||
StoreServiceVersion,
|
||||
VersionStoreService,
|
||||
VersionRangeStoreService,
|
||||
pattern VersionStoreService,
|
||||
initialStoreServiceVersion,
|
||||
currentStoreServiceVersion,
|
||||
supportedStoreServiceVRange,
|
||||
BadgeUpgrade (..),
|
||||
BadgeServiceResponse (..),
|
||||
BadgeServiceErrorCode (..),
|
||||
StoreServiceResponse (..),
|
||||
StoreServiceErrorCode (..),
|
||||
BadgeCatalog (..),
|
||||
BadgePrice (..),
|
||||
BadgeOffer (..),
|
||||
@@ -46,60 +46,60 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, taggedObjectJSON)
|
||||
import Simplex.Messaging.Version (VersionRange, VersionScope, mkVersionRange)
|
||||
import Simplex.Messaging.Version.Internal (Version (..))
|
||||
|
||||
data BadgeServiceVersion
|
||||
data StoreServiceVersion
|
||||
|
||||
instance VersionScope BadgeServiceVersion
|
||||
instance VersionScope StoreServiceVersion
|
||||
|
||||
type VersionBadgeService = Version BadgeServiceVersion
|
||||
type VersionStoreService = Version StoreServiceVersion
|
||||
|
||||
pattern VersionBadgeService :: Word16 -> VersionBadgeService
|
||||
pattern VersionBadgeService v = Version v
|
||||
pattern VersionStoreService :: Word16 -> VersionStoreService
|
||||
pattern VersionStoreService v = Version v
|
||||
|
||||
type VersionRangeBadgeService = VersionRange BadgeServiceVersion
|
||||
type VersionRangeStoreService = VersionRange StoreServiceVersion
|
||||
|
||||
initialBadgeServiceVersion :: VersionBadgeService
|
||||
initialBadgeServiceVersion = VersionBadgeService 1
|
||||
initialStoreServiceVersion :: VersionStoreService
|
||||
initialStoreServiceVersion = VersionStoreService 1
|
||||
|
||||
currentBadgeServiceVersion :: VersionBadgeService
|
||||
currentBadgeServiceVersion = VersionBadgeService 1
|
||||
currentStoreServiceVersion :: VersionStoreService
|
||||
currentStoreServiceVersion = VersionStoreService 1
|
||||
|
||||
-- the service is deployed ahead of app releases, so it answers within the client's version
|
||||
supportedBadgeServiceVRange :: VersionRangeBadgeService
|
||||
supportedBadgeServiceVRange = mkVersionRange initialBadgeServiceVersion currentBadgeServiceVersion
|
||||
supportedStoreServiceVRange :: VersionRangeStoreService
|
||||
supportedStoreServiceVRange = mkVersionRange initialStoreServiceVersion currentStoreServiceVersion
|
||||
|
||||
data BadgeServiceRequest = BadgeServiceRequest
|
||||
{ version :: VersionBadgeService,
|
||||
purchaseKey :: Maybe C.PublicKeyEd25519, -- optional for BSCGetBadgeCatalog, required for other commands
|
||||
request :: BadgeServiceCommand
|
||||
data StoreServiceRequest = StoreServiceRequest
|
||||
{ version :: VersionStoreService,
|
||||
purchaseKey :: Maybe C.PublicKeyEd25519, -- optional for STCGetBadgeCatalog, required for other commands
|
||||
request :: StoreServiceCommand
|
||||
}
|
||||
|
||||
data BadgeServiceCommand
|
||||
= BSCGetBadgeCatalog
|
||||
| BSCGetBadgeInvoice
|
||||
data StoreServiceCommand
|
||||
= STCGetBadgeCatalog
|
||||
| STCGetBadgeInvoice
|
||||
{ priceId :: BadgePriceId,
|
||||
offerId :: Maybe BadgeOfferId, -- absent for 1 month at the badge price
|
||||
badgeInfo :: BadgeInfo,
|
||||
paymentVia :: ServicePaymentMethod,
|
||||
upgrade :: Maybe BadgeUpgrade -- upgrade non-store badge
|
||||
}
|
||||
| BSCRedeemBadgeCode
|
||||
| STCRedeemBadgeCode
|
||||
{ masterKey :: BadgeMasterKey,
|
||||
code :: Text -- no badgeRequest: a code carries no tier for the client to state
|
||||
}
|
||||
| BSCPurchaseBadge
|
||||
| STCPurchaseBadge
|
||||
{ badgeRequest :: BadgeRequest,
|
||||
payment :: ServicePayment,
|
||||
upgrade :: Maybe BadgeUpgrade -- upgrade store 1-time badge
|
||||
}
|
||||
| BSCUpgradeBadgeSubscription
|
||||
| STCUpgradeBadgeSubscription
|
||||
{ badgeRequest :: BadgeRequest,
|
||||
payment :: ServicePayment, -- store payments only
|
||||
balance :: BadgeBalance
|
||||
}
|
||||
| BSCIssueBadge
|
||||
| STCIssueBadge
|
||||
{ balance :: BadgeBalance -- no badgeRequest: the service holds the key, the tier and the expiry
|
||||
}
|
||||
| BSCPauseBadge
|
||||
| STCPauseBadge
|
||||
|
||||
data BadgeUpgrade = BadgeUpgrade
|
||||
{ fromPurchaseKey :: C.PublicKeyEd25519,
|
||||
@@ -108,23 +108,23 @@ data BadgeUpgrade = BadgeUpgrade
|
||||
balance :: BadgeBalance
|
||||
}
|
||||
|
||||
data BadgeServiceResponse
|
||||
= BSPBadgeCatalog
|
||||
data StoreServiceResponse
|
||||
= STPBadgeCatalog
|
||||
{ catalog :: BadgeCatalog,
|
||||
badgeStatement :: Maybe BadgeStatement -- for signed getBadgeCatalog
|
||||
}
|
||||
| BSPBadgeInvoice
|
||||
| STPBadgeInvoice
|
||||
{ invoice :: ServiceInvoice,
|
||||
badgeType :: BadgeType,
|
||||
months :: Word8
|
||||
}
|
||||
| BSPBadgeCredential
|
||||
| STPBadgeCredential
|
||||
{ credential :: Maybe BadgeCredential, -- Nothing when no balance to issueBadge or no current credential for pause
|
||||
receipt :: Maybe Text, -- not provided for lifetime badges
|
||||
statement :: BadgeStatement
|
||||
}
|
||||
| BSPError
|
||||
{ code :: BadgeServiceErrorCode,
|
||||
| STPError
|
||||
{ code :: StoreServiceErrorCode,
|
||||
message :: Maybe Text,
|
||||
retryAfter :: Maybe Word32
|
||||
}
|
||||
@@ -246,9 +246,9 @@ $(JQ.deriveJSON defaultJSON ''BadgeBalance)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''BadgeUpgrade)
|
||||
|
||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "BSC") ''BadgeServiceCommand)
|
||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "STC") ''StoreServiceCommand)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''BadgeServiceRequest)
|
||||
$(JQ.deriveJSON defaultJSON ''StoreServiceRequest)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''BadgePrice)
|
||||
|
||||
@@ -256,4 +256,4 @@ $(JQ.deriveJSON defaultJSON ''BadgeOffer)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''BadgeCatalog)
|
||||
|
||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "BSP") ''BadgeServiceResponse)
|
||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "STP") ''StoreServiceResponse)
|
||||
@@ -41,11 +41,11 @@ import Numeric (showFFloat)
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Help
|
||||
import Simplex.Chat.Library.Commands (badgeServiceErrorText, maxImageSize)
|
||||
import Simplex.Chat.Library.Commands (maxImageSize, storeServiceErrorText)
|
||||
import Simplex.Chat.Markdown
|
||||
import Simplex.Chat.Badges (BadgeInfo (..), BadgeStatus (..), BadgeType (..), LocalBadge, localBadgeInfo, localBadgeStatus)
|
||||
import Simplex.Chat.Badges.Ledger (creditTypeTag, debitTypeTag)
|
||||
import Simplex.Chat.Badges.Service (StatementEntry (..), StatementEntryType (..))
|
||||
import Simplex.Chat.StoreService (StatementEntry (..), StatementEntryType (..))
|
||||
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeIssueError (..), BadgeState (..))
|
||||
import Simplex.Chat.Messages hiding (NewChatItem (..))
|
||||
import Simplex.Chat.Messages.CIContent
|
||||
@@ -2921,9 +2921,9 @@ viewChatError isCmd logLevel testView = \case
|
||||
CEBadgeRedeemError e ->
|
||||
let reason = case e of
|
||||
BREInvalidCode -> "invalid code"
|
||||
BREServiceNotConfigured -> "badge service not configured"
|
||||
BREServiceNotConfigured -> "store service not configured"
|
||||
BREBadgeActive -> "badge already active"
|
||||
BREServiceError code -> "badge service error: " <> T.unpack (badgeServiceErrorText code)
|
||||
BREServiceError code -> "store service error: " <> T.unpack (storeServiceErrorText code)
|
||||
BREInvalidResponse m -> "invalid service response: " <> m
|
||||
BREUnknownKeyIndex -> "credential names an unknown badge key index"
|
||||
BRECredentialNotVerified -> "credential does not verify against configured key"
|
||||
|
||||
Reference in new issue
Block a user