store: rename badge service to StoreService

This commit is contained in:
shum committed 2026-10-21 12:19:49 +00:00
1 parent 8990562850
commit 4aaeadd591
28 files changed
+386 -386

No files matched your search

+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+62 -62
View File
@@ -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
+5 -5
View File
@@ -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
+24 -24
View File
@@ -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}
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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"