mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-15 15:30:36 +00:00
core, ios, android: drop the store purchase checking state, and hide unpaid records after a week
This commit is contained in:
@@ -662,7 +662,6 @@ data ChatCommand
|
||||
| APIRedeemBadgeCode {userId :: UserId, code :: Text} -- redeem a badge code with the configured badge service
|
||||
| APIPurchaseBadge {userId :: UserId, echoedInvoiceId :: Maybe Text, payment :: ServicePayment} -- redeem an App Store or Google Play purchase; without an invoice id it is credited by transaction reference
|
||||
| APICreateBadgeInvoice {userId :: UserId} -- the record of a store purchase, created before the store charges; answers the id the store echoes
|
||||
| APICloseBadgeInvoice {userId :: UserId, invoiceId :: Text} -- a store purchase that came to nothing
|
||||
| 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
|
||||
|
||||
@@ -3573,10 +3573,6 @@ processChatCommand cxt nm = \case
|
||||
invoiceId <- UUID.toText <$> liftIO V4.nextRandom
|
||||
_ <- withStore' $ \db -> createBadgeStoreReceipt db g user (Just invoiceId) Nothing now
|
||||
pure $ CRBadgeInvoice user invoiceId
|
||||
APICloseBadgeInvoice userId invoiceId -> withUserId userId $ \user -> do
|
||||
now <- liftIO getCurrentTime
|
||||
withStore' $ \db -> closeBadgeStoreInvoice db user invoiceId now
|
||||
ok user
|
||||
APIGetBadgeState userId -> withUserId' userId $ \user -> do
|
||||
-- the read also signals the worker, whose results follow as CEvtBadgeChanged
|
||||
lift $ startBadgeWork user
|
||||
@@ -5503,7 +5499,8 @@ emitBadgeAlert user emitted p@UserBadgePurchase {alertSnoozeUntil} shownCred now
|
||||
badgeStateResponse :: User -> CM ChatResponse
|
||||
badgeStateResponse user = do
|
||||
badgeState <- getUserBadgeState user
|
||||
CRBadgeState user badgeState <$> withStore' (`getOpenStorePurchases` user)
|
||||
now <- badgeNow
|
||||
CRBadgeState user badgeState <$> withStore' (\db -> getOpenStorePurchases db user now)
|
||||
|
||||
-- | Read from stored rows alone; the worker's results follow as CEvtBadgeChanged.
|
||||
getUserBadgeState :: User -> CM (Maybe BadgeState)
|
||||
@@ -6176,7 +6173,6 @@ chatCommandP =
|
||||
"/_service_request " *> (APISendServiceRequest <$> A.decimal <* A.space <*> strP <*> optional (" timeout=" *> (realToFrac <$> A.double)) <*> optional (" sign_key=" *> strP) <* A.space <*> jsonP),
|
||||
"/_redeem_badge_code " *> (APIRedeemBadgeCode <$> A.decimal <* A.space <*> textP),
|
||||
"/_badge purchase " *> (APIPurchaseBadge <$> A.decimal <*> optional (" invoice=" *> (safeDecodeUtf8 <$> A.takeTill (== ' '))) <* A.space <*> jsonP),
|
||||
"/_badge invoice close " *> (APICloseBadgeInvoice <$> A.decimal <* A.space <*> textP),
|
||||
"/_badge invoice " *> (APICreateBadgeInvoice <$> A.decimal),
|
||||
"/_badge state " *> (APIGetBadgeState <$> A.decimal),
|
||||
"/_badge ledger " *> (APIGetBadgeLedger <$> A.decimal <* A.space <*> A.decimal),
|
||||
|
||||
@@ -21,7 +21,6 @@ module Simplex.Chat.Store.Badges
|
||||
createBadgeCodeRedemption,
|
||||
getBadgeStoreReceiptUserId,
|
||||
attachBadgeStoreReceipt,
|
||||
closeBadgeStoreInvoice,
|
||||
getOpenStorePurchases,
|
||||
getBadgeStoreReceipt,
|
||||
createBadgeStoreReceipt,
|
||||
@@ -46,7 +45,7 @@ import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.Int (Int64)
|
||||
import Data.Maybe (isJust, mapMaybe)
|
||||
import Data.Text (Text)
|
||||
import Data.Time.Clock (UTCTime)
|
||||
import Data.Time.Clock (UTCTime, addUTCTime, nominalDay)
|
||||
import Simplex.Chat.Badges
|
||||
import Simplex.Chat.Badges.Ledger
|
||||
import Simplex.Chat.Badges.Service (StatementCreditType (..), StatementDebitType (..), StatementEntry (..), StatementEntryType (..))
|
||||
@@ -123,36 +122,30 @@ attachBadgeStoreReceipt db invoiceId_ txRef@StoreTransactionRef {provider, trans
|
||||
userId_ <-
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query db "SELECT user_id FROM badge_store_receipts WHERE invoice_id = ? AND transaction_ref IS NULL" (Only invoiceId)
|
||||
-- a closed record is reopened so the purchase stays on the profile that paid: only a record no
|
||||
-- receipt has reached can be closed, so its keys were never sent and nothing was credited to them
|
||||
forM_ userId_ $ \_ ->
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE badge_store_receipts SET provider = ?, transaction_ref = ?, closed_at = NULL WHERE invoice_id = ?"
|
||||
"UPDATE badge_store_receipts SET provider = ?, transaction_ref = ? WHERE invoice_id = ?"
|
||||
(provider, transactionRef, invoiceId)
|
||||
pure userId_
|
||||
|
||||
-- | Only a record no receipt has reached can be closed.
|
||||
closeBadgeStoreInvoice :: DB.Connection -> User -> Text -> UTCTime -> IO ()
|
||||
closeBadgeStoreInvoice db User {userId} invoiceId now =
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE badge_store_receipts SET closed_at = ? WHERE user_id = ? AND invoice_id = ? AND transaction_ref IS NULL"
|
||||
(now, userId, invoiceId)
|
||||
|
||||
getOpenStorePurchases :: DB.Connection -> User -> IO [OpenStorePurchase]
|
||||
getOpenStorePurchases db User {userId} =
|
||||
getOpenStorePurchases :: DB.Connection -> User -> UTCTime -> IO [OpenStorePurchase]
|
||||
getOpenStorePurchases db User {userId} now =
|
||||
map (uncurry OpenStorePurchase)
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT r.invoice_id, r.transaction_ref
|
||||
FROM badge_store_receipts r
|
||||
WHERE r.user_id = ? AND r.closed_at IS NULL
|
||||
WHERE r.user_id = ? AND (r.transaction_ref IS NOT NULL OR r.created_at > ?)
|
||||
AND NOT EXISTS (SELECT 1 FROM badge_purchases p WHERE p.badge_store_receipt_id = r.badge_store_receipt_id)
|
||||
ORDER BY r.badge_store_receipt_id
|
||||
|]
|
||||
(Only userId)
|
||||
(userId, receiptDueSince)
|
||||
where
|
||||
-- a week outlasts an Ask to Buy request (24 hours) and a Play slow payment (days), whose waiting screen
|
||||
-- needs the record; one a receipt reached is paid for, so it is listed until its badge arrives
|
||||
receiptDueSince = addUTCTime (negate $ 7 * nominalDay) now
|
||||
|
||||
getBadgeStoreReceipt :: DB.Connection -> User -> StoreTransactionRef -> IO (Maybe BadgeStash)
|
||||
getBadgeStoreReceipt db User {userId} StoreTransactionRef {provider, transactionRef} =
|
||||
|
||||
@@ -6,9 +6,9 @@ module Simplex.Chat.Store.Postgres.Migrations.M20260925_badge_store_receipts whe
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
-- | invoice_id is null when a receipt arrives naming no invoice, or one another row already holds: that row
|
||||
-- is then keyed by its transaction alone. provider and transaction_ref are null until a receipt arrives, and
|
||||
-- distinct NULLs let several rows await one at once.
|
||||
-- | invoice_id is null for a receipt naming one another row holds: the transaction still has to be
|
||||
-- creditable, and its reference identifies it. provider and transaction_ref are null until a receipt
|
||||
-- arrives, and distinct NULLs let several rows await one at once.
|
||||
m20260925_badge_store_receipts :: Text
|
||||
m20260925_badge_store_receipts =
|
||||
[r|
|
||||
@@ -22,7 +22,6 @@ CREATE TABLE badge_store_receipts(
|
||||
purchase_priv_key BYTEA NOT NULL,
|
||||
master_key BYTEA NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
closed_at TIMESTAMPTZ,
|
||||
UNIQUE(provider, transaction_ref)
|
||||
);
|
||||
|
||||
|
||||
@@ -305,8 +305,7 @@ CREATE TABLE test_chat_schema.badge_store_receipts (
|
||||
purchase_key bytea NOT NULL,
|
||||
purchase_priv_key bytea NOT NULL,
|
||||
master_key bytea NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
closed_at timestamp with time zone
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ module Simplex.Chat.Store.SQLite.Migrations.M20260925_badge_store_receipts where
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- | invoice_id is null when a receipt arrives naming no invoice, or one another row already holds: that row
|
||||
-- is then keyed by its transaction alone. provider and transaction_ref are null until a receipt arrives, and
|
||||
-- distinct NULLs let several rows await one at once.
|
||||
-- | invoice_id is null for a receipt naming one another row holds: the transaction still has to be
|
||||
-- creditable, and its reference identifies it. provider and transaction_ref are null until a receipt
|
||||
-- arrives, and distinct NULLs let several rows await one at once.
|
||||
m20260925_badge_store_receipts :: Query
|
||||
m20260925_badge_store_receipts =
|
||||
[sql|
|
||||
@@ -21,7 +21,6 @@ CREATE TABLE badge_store_receipts(
|
||||
purchase_priv_key BLOB NOT NULL,
|
||||
master_key BLOB NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
closed_at TEXT,
|
||||
UNIQUE(provider, transaction_ref)
|
||||
) STRICT;
|
||||
|
||||
|
||||
@@ -992,7 +992,6 @@ CREATE TABLE badge_store_receipts(
|
||||
purchase_priv_key BLOB NOT NULL,
|
||||
master_key BLOB NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
closed_at TEXT,
|
||||
UNIQUE(provider, transaction_ref)
|
||||
) STRICT;
|
||||
CREATE INDEX contact_profiles_index ON contact_profiles(
|
||||
|
||||
Reference in New Issue
Block a user