core, ios, android: drop the store purchase checking state, and hide unpaid records after a week

This commit is contained in:
spaced4ndy
2026-10-05 13:39:16 +04:00
parent 15a1782b76
commit c3629d0266
19 changed files with 101 additions and 204 deletions
-1
View File
@@ -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
+2 -6
View File
@@ -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),
+10 -17
View File
@@ -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(