mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 00:02:28 +00:00
ui: buy a badge in the app through the App Store or Google Play (#7608)
* ui: buy a badge in the app through the App Store or Google Play * core, ios, android: create the badge store purchase record before the store charges * core, ios, android: fixes from the store lane review * ios, android: show a store purchase's badge only on the profile it was credited to
This commit is contained in:
1 parent
0ee34926bc
commit
e3e4709381
38 files changed
+1607
-343
No files matched your search
@@ -2196,11 +2196,44 @@ func apiRedeemBadgeCode(_ userId: Int64, _ code: String) async throws -> (user:
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
enum BadgePurchaseResult {
|
||||
case redeemed(user: User, badgeState: BadgeState?)
|
||||
}
|
||||
|
||||
// log: false because a store receipt is a bearer secret, like a badge code - it is in the command.
|
||||
// nil when the user cancels the retry alert, which is offered only when retry is set.
|
||||
func apiPurchaseBadge(_ userId: Int64, _ echoedInvoiceId: String?, _ payment: ServicePayment, retry: Bool) async throws -> BadgePurchaseResult? {
|
||||
let cmd = ChatCommand.apiPurchaseBadge(userId: userId, echoedInvoiceId: echoedInvoiceId, payment: payment)
|
||||
let r: APIResult<ChatResponse2>?
|
||||
if retry {
|
||||
r = await chatApiSendCmdWithRetry(cmd, log: false)
|
||||
} else {
|
||||
let res: APIResult<ChatResponse2> = await chatApiSendCmd(cmd, log: false)
|
||||
r = res
|
||||
}
|
||||
guard let r else { return nil }
|
||||
switch r {
|
||||
case let .result(.badgeRedeemed(user, _, _, badgeState)): return .redeemed(user: user, badgeState: badgeState)
|
||||
default: throw r.unexpected
|
||||
}
|
||||
}
|
||||
|
||||
func apiCreateBadgeInvoice(_ userId: Int64) async throws -> String {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiCreateBadgeInvoice(userId: userId))
|
||||
if case let .badgeInvoice(_, invoiceId) = r { return invoiceId }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiCloseBadgeInvoice(_ userId: Int64, _ invoiceId: String) async throws {
|
||||
try await sendCommandOkResp(.apiCloseBadgeInvoice(userId: userId, invoiceId: invoiceId))
|
||||
}
|
||||
|
||||
// localized where the user can act on it; otherwise the error itself, so a screenshot says what happened
|
||||
func redeemErrorText(_ error: Error) -> String {
|
||||
if case let .error(.badgeRedeemError(e)) = error as? ChatError {
|
||||
switch e {
|
||||
case .invalidCode: return NSLocalizedString("This code is not valid.", comment: "alert message")
|
||||
case .invalidReceipt: break
|
||||
case .serviceNotConfigured: return NSLocalizedString("This app version cannot redeem badge codes.", comment: "alert message")
|
||||
case .badgeActive: return NSLocalizedString("This profile already has a badge. Redeem the code on another profile, or once this badge ends.", comment: "alert message")
|
||||
case let .serviceError(code): if let text = badgeServiceErrorText(code) { return text }
|
||||
@@ -2212,15 +2245,15 @@ func redeemErrorText(_ error: Error) -> String {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "alert message"), responseError(error))
|
||||
}
|
||||
|
||||
func apiGetBadgeState(_ userId: Int64) async throws -> BadgeState? {
|
||||
func apiGetBadgeState(_ userId: Int64) async throws -> (badgeState: BadgeState?, storePurchases: [BadgeStorePurchase]) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiGetBadgeState(userId: userId))
|
||||
if case let .badgeState(_, badgeState) = r { return badgeState }
|
||||
if case let .badgeState(_, badgeState, storePurchases) = r { return (badgeState, storePurchases ?? []) }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiGetBadgeStateSync(_ userId: Int64) throws -> BadgeState? {
|
||||
func apiGetBadgeStateSync(_ userId: Int64) throws -> (badgeState: BadgeState?, storePurchases: [BadgeStorePurchase]) {
|
||||
let r: ChatResponse2 = try chatSendCmdSync(.apiGetBadgeState(userId: userId))
|
||||
if case let .badgeState(_, badgeState) = r { return badgeState }
|
||||
if case let .badgeState(_, badgeState, storePurchases) = r { return (badgeState, storePurchases ?? []) }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
@@ -2232,7 +2265,7 @@ func apiGetBadgeLedger(_ userId: Int64, _ badgePurchaseId: Int64) async throws -
|
||||
|
||||
func apiAckBadgeAlert(_ userId: Int64, _ badgePurchaseId: Int64, _ alertKind: BadgeAlertKind, snooze: Bool, episode: String) async throws -> BadgeState? {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiAckBadgeAlert(userId: userId, badgePurchaseId: badgePurchaseId, alertKind: alertKind, snooze: snooze, episode: episode))
|
||||
if case let .badgeState(_, badgeState) = r { return badgeState }
|
||||
if case let .badgeState(_, badgeState, _) = r { return badgeState }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
@@ -2361,6 +2394,7 @@ func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws
|
||||
ChatReceiver.shared.start()
|
||||
m.chatRunning = true
|
||||
chatLastStartGroupDefault.set(Date.now)
|
||||
Task { await BadgeStore.shared.presentUnfinished() }
|
||||
}
|
||||
|
||||
func startChatWithTemporaryDatabase(ctrl: chat_ctrl) throws -> User? {
|
||||
@@ -2427,17 +2461,21 @@ func getUserChatData() throws {
|
||||
private func loadBadgeState() {
|
||||
do {
|
||||
let userId = try currentUserId("loadBadgeState")
|
||||
let badgeState = try apiGetBadgeStateSync(userId)
|
||||
let (badgeState, storePurchases) = try apiGetBadgeStateSync(userId)
|
||||
BadgeModel.shared.set(userId: userId, badgeState: badgeState)
|
||||
BadgeStore.shared.setStorePurchases(userId, storePurchases)
|
||||
} catch let error {
|
||||
logger.error("loadBadgeState: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
|
||||
private func loadBadgeStateAsync(_ userId: Int64) async {
|
||||
func loadBadgeStateAsync(_ userId: Int64) async {
|
||||
do {
|
||||
let badgeState = try await apiGetBadgeState(userId)
|
||||
await MainActor.run { BadgeModel.shared.set(userId: userId, badgeState: badgeState) }
|
||||
let (badgeState, storePurchases) = try await apiGetBadgeState(userId)
|
||||
await MainActor.run {
|
||||
BadgeModel.shared.set(userId: userId, badgeState: badgeState)
|
||||
BadgeStore.shared.setStorePurchases(userId, storePurchases)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("loadBadgeState: \(responseError(error))")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user