mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 16:28:21 +00:00
core, ios, android: state the real reason in the badge purchase comments
This commit is contained in:
@@ -103,16 +103,18 @@ final class BadgeStore: ObservableObject {
|
||||
|
||||
@Published private var state: LoadState = .notLoaded
|
||||
private var products: [String: Product] = [:]
|
||||
// one-time transactions the store holds unfinished: each is a payment taken and not yet credited
|
||||
// transactions this run has started presenting and not finished - filled by claim, not by reading the
|
||||
// store, so it is empty at launch until the sweep reaches each one
|
||||
@Published private var unfinished: [UInt64: BadgeStoreReceipt] = [:]
|
||||
// core's open store purchases for the profile they were read for: core knows whose a purchase is
|
||||
@Published private var storePurchases: (userId: Int64, purchases: [OpenStorePurchase])? = nil
|
||||
// invoices whose purchase this session is still waiting on the store for
|
||||
// invoices this run has an open store sheet for, and whose interactive presentation has not returned
|
||||
@Published private var buying: Set<String> = []
|
||||
// kept for this run only: StoreKit lists no deferred purchase, and a declined one delivers nothing
|
||||
// by invoice id, so a pending purchase shows only under the profile whose record it names
|
||||
@Published private var waitingForApproval: Set<String> = []
|
||||
// until the store has been asked once, a purchase made while the app was not running is unknown
|
||||
// set when the first sweep has returned, which is after every held transaction has been presented
|
||||
// over the network - until then a purchase made while the app was not running is unknown
|
||||
@Published private var reconciledOnce = false
|
||||
// whether the purchase the user started waits on the presentation, so its outcome is shown whoever claimed it
|
||||
private var presenting: [UInt64: Bool] = [:]
|
||||
|
||||
+5
-3
@@ -150,15 +150,17 @@ object BadgeStore {
|
||||
private val state = mutableStateOf(LoadState.NotLoaded)
|
||||
// snapshot state so a composable reading only the products still recomposes when they arrive
|
||||
private val products = mutableStateOf<Map<BadgeStoreProductId, BadgeProduct>>(emptyMap())
|
||||
// one-time purchases the store holds unfinished: each is a payment taken and not yet credited
|
||||
// purchases this run has started presenting and not finished - filled by claim, not by reading the
|
||||
// store, so it is empty at launch until the sweep reaches each one
|
||||
private val unfinished = mutableStateOf<Map<String, BadgeStoreReceipt>>(emptyMap())
|
||||
// core's open store purchases for the profile they were read for: core knows whose a purchase is
|
||||
private val storePurchases = mutableStateOf<Triple<Long?, Long, List<OpenStorePurchase>>?>(null)
|
||||
// invoices whose purchase this session is still waiting on the store for
|
||||
// invoices this run has an open store sheet for, and whose interactive presentation has not returned
|
||||
private val buying = mutableStateOf<Set<String>>(emptySet())
|
||||
// by invoice id, so a pending purchase shows only under the profile whose record it names
|
||||
private val waitingForApproval = mutableStateOf<Set<String>>(emptySet())
|
||||
// until the store has been asked once, a purchase made while the app was not running is unknown
|
||||
// set when the first sweep has returned or failed, which is after every held purchase has been presented
|
||||
// over the network - until then a purchase made while the app was not running is unknown
|
||||
private val reconciledOnce = mutableStateOf(false)
|
||||
// whether the purchase the user started waits on the presentation, so its outcome is shown whoever claimed it;
|
||||
// read and written on the main thread only
|
||||
|
||||
@@ -25,8 +25,8 @@ import Simplex.Messaging.Util (catchOwn')
|
||||
import System.Timeout (timeout)
|
||||
|
||||
-- | What a store vouches for about one completed transaction.
|
||||
-- TODO [badges] reconcile with PaymentFunding's PFApple and PFGoogle, which record a payment the
|
||||
-- service stored rather than what a store attested, when PaymentFunding is built out.
|
||||
-- TODO [badges] PaymentFunding's PFApple and PFGoogle sketch the same thing and have no producer;
|
||||
-- delete them, or build them out and reconcile.
|
||||
data VerifiedStoreTransaction = VerifiedStoreTransaction
|
||||
{ transactionRef :: Text, -- from what was verified: Apple's transactionId, googlePurchaseRef of the token asked about
|
||||
productId :: Text,
|
||||
|
||||
@@ -660,7 +660,7 @@ data ChatCommand
|
||||
| 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
|
||||
| APIPurchaseBadge {userId :: UserId, echoedInvoiceId :: Maybe Text, payment :: ServicePayment} -- redeem an App Store or Google Play purchase; without an invoice id, as for one this install did not start, it is credited by transaction reference
|
||||
| APIPurchaseBadge {userId :: UserId, echoedInvoiceId :: Maybe Text, payment :: ServicePayment} -- redeem an App Store or Google Play purchase; without an invoice id, as for one the app never started, 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
|
||||
|
||||
@@ -138,8 +138,6 @@ data StoredPayment = StoredPayment
|
||||
deriving (Show)
|
||||
|
||||
-- to review
|
||||
-- TODO [badges] reconcile PFApple and PFGoogle with the badge service's VerifiedStoreTransaction,
|
||||
-- which is what a store attested rather than a payment recorded here, when this is built out.
|
||||
data PaymentFunding
|
||||
= PFInvoice
|
||||
{ invoiceId :: InvoiceId,
|
||||
|
||||
@@ -123,8 +123,8 @@ 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: new keys for a paid transaction would be refused as receipt_used,
|
||||
-- and the badge the old keys were credited with would be lost
|
||||
-- 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
|
||||
|
||||
@@ -6,9 +6,10 @@ module Simplex.Chat.Store.Postgres.Migrations.M20260925_badge_store_receipts whe
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
-- | invoice_id is null for a receipt this install did not start — a reinstall, a restored database, or the
|
||||
-- same store account on another device. 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 purchase the app never started, such as a store offer code, since the stores
|
||||
-- echo back only an id the app set. A reinstall or another device echoes one this database does not know,
|
||||
-- and that row is keyed by its transaction instead. 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|
|
||||
|
||||
@@ -5,9 +5,10 @@ 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 for a receipt this install did not start — a reinstall, a restored database, or the
|
||||
-- same store account on another device. 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 purchase the app never started, such as a store offer code, since the stores
|
||||
-- echo back only an id the app set. A reinstall or another device echoes one this database does not know,
|
||||
-- and that row is keyed by its transaction instead. 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|
|
||||
|
||||
Reference in New Issue
Block a user