mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-20 08:02:24 +00:00
core, ios, android: name the resolved store purchase so the app finishes it without a sweep (#7659)
This commit is contained in:
@@ -85,7 +85,7 @@ import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyM
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Chat.Badges (BadgeCredential, FileSizeLimits, LocalBadge)
|
||||
import Simplex.Chat.Badges.Service (BadgeServiceErrorCode, StatementEntry)
|
||||
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind, BadgeState (..), OpenStorePurchase (..))
|
||||
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind, BadgeIssueFailure, BadgeState (..), OpenStorePurchase (..))
|
||||
import Simplex.Chat.PaymentService (ServicePayment)
|
||||
import Simplex.Messaging.Crypto.BBS (BBSPublicKey)
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
@@ -999,7 +999,8 @@ data ChatEvent
|
||||
| CEvtServiceReplySent {connectionId :: AgentConnId}
|
||||
| CEvtBadgeChanged {user :: User, badgeState :: Maybe BadgeState} -- badge state changed, including a renewal that arrived without a command
|
||||
| CEvtBadgeAlert {user :: User, badgeAlert :: BadgeAlert}
|
||||
| CEvtStorePurchaseResolved {user :: User} -- a held store receipt was credited or refused, so its store transaction can be finished
|
||||
| CEvtStorePurchaseCredited {user :: User, invoiceId :: Text} -- the held store receipt of this invoice was credited, so its store transaction can be finished
|
||||
| CEvtStorePurchaseRefused {user :: User, invoiceId :: Text, refusal :: BadgeIssueFailure} -- the held store receipt of this invoice was refused
|
||||
| CEvtContactRequestRejected {user :: User, contact :: Contact, rejectionReason :: Maybe ContactRejectionReason}
|
||||
| CEvtAcceptingContactRequest {user :: User, contact :: Contact} -- there is the same command response
|
||||
| CEvtAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo}
|
||||
|
||||
@@ -5458,7 +5458,7 @@ runStoreReceiptWorker a userId Worker {doWork} = do
|
||||
forever $ do
|
||||
lift $ waitForWork doWork
|
||||
withWork_ a doWork (getDueReceipt scheduled) $ \case
|
||||
BadgeReceiptRecord {receiptId, status = RSHeld {stash, payment, retryDelay}} -> creditStoreReceipt a userId receiptId stash payment retryDelay
|
||||
BadgeReceiptRecord {receiptId, status = RSHeld {stash, invoiceId, payment, retryDelay}} -> creditStoreReceipt a userId receiptId stash invoiceId payment retryDelay
|
||||
BadgeReceiptRecord {receiptId} -> notHeld receiptId
|
||||
where
|
||||
getDueReceipt scheduled =
|
||||
@@ -5479,31 +5479,36 @@ runStoreReceiptWorker a userId Worker {doWork} = do
|
||||
atomically $ modifyTVar' scheduled $ S.delete receiptId
|
||||
void $ atomically $ tryPutTMVar doWork ()
|
||||
|
||||
data StoreReceiptOutcome = SROCredited (Maybe User) | SRORefused | SRORetry | SRODeferred
|
||||
data StoreReceiptOutcome = SROCredited (Maybe User) | SRORefused BadgeIssueFailure | SRORetry | SRODeferred
|
||||
|
||||
-- | Under the owner's badge lock, which keeps a profile's requests to the service serial.
|
||||
creditStoreReceipt :: AgentClient -> UserId -> Int64 -> BadgeStash -> Text -> Maybe Int64 -> CM ()
|
||||
creditStoreReceipt a userId receiptId stash@BadgeStash {masterKey} payment retryDelay = do
|
||||
creditStoreReceipt :: AgentClient -> UserId -> Int64 -> BadgeStash -> Maybe Text -> Text -> Maybe Int64 -> CM ()
|
||||
creditStoreReceipt a userId receiptId stash@BadgeStash {masterKey} invoiceId_ payment retryDelay = do
|
||||
ChatConfig {badgeRetryInterval = ri, badgeConsecutiveRetries} <- asks config
|
||||
withRetryIntervalCount (maybe ri (\d -> ri {initialInterval = d, increaseAfter = 0}) retryDelay) $ \n delay loop -> do
|
||||
liftIO $ waitWhileSuspended a
|
||||
liftIO $ waitForUserNetwork a
|
||||
-- decided under the lock and acted on outside it: the retry sleeps, and announcing takes the chat lock
|
||||
outcome <- withEntityLock "badgePurchase" (CLBadgeUser userId) $
|
||||
tryAllErrors attempt >>= \case
|
||||
Right (Right (present_, _)) -> pure $ SROCredited present_
|
||||
Right (Left (code, retryAfter))
|
||||
| storeReceiptRefused code -> SRORefused <$ withStore' (\db -> refuseStoreReceipt db receiptId $ serviceFailure code retryAfter)
|
||||
| storeReceiptRefused code -> do
|
||||
let failure = serviceFailure code retryAfter
|
||||
SRORefused failure <$ withStore' (\db -> refuseStoreReceipt db receiptId failure)
|
||||
| otherwise -> failed ri delay (serviceFailure code retryAfter) retryAfter
|
||||
Left e -> failed ri delay (badgeIssueFailure e) Nothing
|
||||
-- only a settlement is announced, and the sweep it prompts signals this worker only for a receipt still held, so it cannot loop
|
||||
-- the invoice id is the app's only key to the store transaction, so without one nothing is emitted and its sweep finishes it
|
||||
case outcome of
|
||||
SROCredited present_ -> do
|
||||
user <- withStore $ \db -> getUser db userId
|
||||
toView . CEvtBadgeChanged user =<< getUserBadgeState user
|
||||
toView $ CEvtStorePurchaseResolved user
|
||||
-- last, so a failed broadcast cannot lose the settlement; outside the badge lock, as it takes the chat lock
|
||||
forM_ invoiceId_ $ toView . CEvtStorePurchaseCredited user
|
||||
-- last, so a failed broadcast cannot lose the settlement
|
||||
mapM_ presentUserBadgeToContacts present_
|
||||
SRORefused -> withStore (`getUser` userId) >>= toView . CEvtStorePurchaseResolved
|
||||
SRORefused failure -> forM_ invoiceId_ $ \invoiceId -> do
|
||||
user <- withStore (`getUser` userId)
|
||||
toView $ CEvtStorePurchaseRefused user invoiceId failure
|
||||
SRORetry | n + 1 < badgeConsecutiveRetries -> loop
|
||||
_ -> pure ()
|
||||
where
|
||||
|
||||
@@ -118,7 +118,7 @@ data BadgeReceiptRecord = BadgeReceiptRecord
|
||||
}
|
||||
|
||||
data BadgeReceiptStatus
|
||||
= RSHeld {stash :: BadgeStash, payment :: Text, nextAttemptAt :: UTCTime, retryDelay :: Maybe Int64}
|
||||
= RSHeld {stash :: BadgeStash, invoiceId :: Maybe Text, payment :: Text, nextAttemptAt :: UTCTime, retryDelay :: Maybe Int64}
|
||||
| RSCredited {badgePurchaseId :: Int64}
|
||||
| RSRefused {refusal :: Maybe BadgeIssueFailure}
|
||||
|
||||
@@ -171,18 +171,18 @@ getReceiptRecord db StoreTransactionRef {provider, transactionRef} =
|
||||
db
|
||||
[sql|
|
||||
SELECT r.badge_store_receipt_id, r.user_id, r.purchase_key, r.purchase_priv_key, r.master_key,
|
||||
r.payment, r.next_attempt_at, r.retry_delay, p.badge_purchase_id, r.credit_error
|
||||
r.invoice_id, r.payment, r.next_attempt_at, r.retry_delay, p.badge_purchase_id, r.credit_error
|
||||
FROM badge_store_receipts r
|
||||
LEFT JOIN badge_purchases p ON p.badge_store_receipt_id = r.badge_store_receipt_id
|
||||
WHERE r.provider = ? AND r.transaction_ref = ?
|
||||
|]
|
||||
(provider, transactionRef)
|
||||
where
|
||||
toReceiptRecord ((receiptId, ownerId, purchaseKey, purchasePrivKey, mk) :. (payment_, nextAttemptAt_, retryDelay, purchaseId_, refusal)) =
|
||||
toReceiptRecord ((receiptId, ownerId, purchaseKey, purchasePrivKey, mk) :. (invoiceId, payment_, nextAttemptAt_, retryDelay, purchaseId_, refusal)) =
|
||||
BadgeReceiptRecord {receiptId, ownerId, status}
|
||||
where
|
||||
status = case (payment_, nextAttemptAt_) of
|
||||
(Just payment, Just nextAttemptAt) -> RSHeld {stash = toBadgeStash BSRStoreReceipt (receiptId, purchaseKey, purchasePrivKey, mk), payment, nextAttemptAt, retryDelay}
|
||||
(Just payment, Just nextAttemptAt) -> RSHeld {stash = toBadgeStash BSRStoreReceipt (receiptId, purchaseKey, purchasePrivKey, mk), invoiceId, payment, nextAttemptAt, retryDelay}
|
||||
_ -> maybe (RSRefused refusal) RSCredited purchaseId_
|
||||
|
||||
-- | The record made when Buy is tapped, before any receipt: the store echoes its invoice id.
|
||||
@@ -220,7 +220,7 @@ getNextHeldStoreReceipt db userId =
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT r.badge_store_receipt_id, r.purchase_key, r.purchase_priv_key, r.master_key, r.payment, r.next_attempt_at, r.retry_delay
|
||||
SELECT r.badge_store_receipt_id, r.purchase_key, r.purchase_priv_key, r.master_key, r.invoice_id, r.payment, r.next_attempt_at, r.retry_delay
|
||||
FROM badge_store_receipts r
|
||||
JOIN users u ON u.user_id = r.user_id
|
||||
WHERE r.user_id = ? AND r.payment IS NOT NULL AND u.shown_badge_id IS NULL
|
||||
@@ -229,8 +229,8 @@ getNextHeldStoreReceipt db userId =
|
||||
|]
|
||||
(Only userId)
|
||||
where
|
||||
toHeld (stashRow@(receiptId, _, _, _) :. (payment, nextAttemptAt, retryDelay)) =
|
||||
BadgeReceiptRecord {receiptId, ownerId = userId, status = RSHeld {stash = toBadgeStash BSRStoreReceipt stashRow, payment, nextAttemptAt, retryDelay}}
|
||||
toHeld (stashRow@(receiptId, _, _, _) :. (invoiceId, payment, nextAttemptAt, retryDelay)) =
|
||||
BadgeReceiptRecord {receiptId, ownerId = userId, status = RSHeld {stash = toBadgeStash BSRStoreReceipt stashRow, invoiceId, payment, nextAttemptAt, retryDelay}}
|
||||
|
||||
recordStoreReceiptFailure :: DB.Connection -> Int64 -> Int64 -> UTCTime -> BadgeIssueFailure -> IO ()
|
||||
recordStoreReceiptFailure db receiptId retryDelay nextAttemptAt failure =
|
||||
|
||||
@@ -484,7 +484,8 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtServiceReplySent (AgentConnId cId) -> [plain $ "service reply sent, connection id: " <> safeDecodeUtf8 (strEncode cId)]
|
||||
CEvtBadgeChanged u st -> ttyUser u $ viewUserBadgeState st
|
||||
CEvtBadgeAlert u alert -> ttyUser u $ viewBadgeAlert alert
|
||||
CEvtStorePurchaseResolved u -> ttyUser u ["store purchase resolved"]
|
||||
CEvtStorePurchaseCredited u invoiceId -> ttyUser u ["store purchase credited: invoice " <> plain invoiceId]
|
||||
CEvtStorePurchaseRefused u invoiceId refusal -> ttyUser u ["store purchase refused: invoice " <> plain invoiceId <> ", " <> plain (safeDecodeUtf8 $ strEncode refusal)]
|
||||
CEvtContactRequestRejected u Contact {localDisplayName = c} _reason -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
||||
CEvtRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci
|
||||
CEvtRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci
|
||||
|
||||
Reference in New Issue
Block a user