mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-21 19:00:01 +00:00
wip
This commit is contained in:
@@ -8,8 +8,9 @@ import chat.simplex.common.views.badges.BadgeStoreError
|
||||
fun loadPlayStoreCountry() {}
|
||||
|
||||
// No store in this flavor: no product is offered, so the purchase screen shows nothing to buy
|
||||
// TODO [badges] this build pays via Stripe/crypto - the badge service catalog replaces these
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
suspend fun loadBadgeProducts(productIds: List<String>): List<BadgeProduct> = emptyList()
|
||||
suspend fun loadBadgeProducts(oneTimeIds: List<String>, subscriptionIds: List<String>): List<BadgeProduct> = emptyList()
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
suspend fun purchaseBadge(productId: String, invoiceId: String): BadgePurchaseOutcome =
|
||||
|
||||
@@ -43,12 +43,12 @@ fun loadPlayStoreCountry() {
|
||||
@Volatile private var badgeProductDetails: Map<String, ProductDetails> = emptyMap()
|
||||
@Volatile private var badgePurchase: CompletableDeferred<BadgePurchaseOutcome>? = null
|
||||
|
||||
suspend fun loadBadgeProducts(productIds: List<String>): List<BadgeProduct> {
|
||||
suspend fun loadBadgeProducts(oneTimeIds: List<String>, subscriptionIds: List<String>): List<BadgeProduct> {
|
||||
val client = connectedBadgeBillingClient()
|
||||
// every id is queried as both types - Play returns only the ones that match, so the product type
|
||||
// does not have to be inferred from the id
|
||||
val details = queryBadgeProducts(client, productIds, BillingClient.ProductType.INAPP) +
|
||||
queryBadgeProducts(client, productIds, BillingClient.ProductType.SUBS)
|
||||
// two queries because Play rejects a product list mixing INAPP and SUBS
|
||||
val details = queryBadgeProducts(client, oneTimeIds, BillingClient.ProductType.INAPP) +
|
||||
queryBadgeProducts(client, subscriptionIds, BillingClient.ProductType.SUBS)
|
||||
val productIds = oneTimeIds + subscriptionIds
|
||||
if (details.size < productIds.size) {
|
||||
// Play drops ids it cannot resolve without saying why, so the package it was asked for and the
|
||||
// store country are logged with them - a debug build's applicationIdSuffix is a common cause
|
||||
|
||||
@@ -345,7 +345,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
|
||||
override fun androidLoadPlayStoreCountry() = loadPlayStoreCountry()
|
||||
|
||||
override suspend fun androidLoadBadgeProducts(productIds: List<String>): List<BadgeProduct> = loadBadgeProducts(productIds)
|
||||
override suspend fun androidLoadBadgeProducts(oneTimeIds: List<String>, subscriptionIds: List<String>): List<BadgeProduct> = loadBadgeProducts(oneTimeIds, subscriptionIds)
|
||||
|
||||
override suspend fun androidPurchaseBadge(productId: String, invoiceId: String): BadgePurchaseOutcome = purchaseBadge(productId, invoiceId)
|
||||
|
||||
|
||||
+5
-2
@@ -34,8 +34,11 @@ interface PlatformInterface {
|
||||
fun androidIsXiaomiDevice(): Boolean = false
|
||||
// Requests the Google Play account country into [androidPlayStoreCountry]
|
||||
fun androidLoadPlayStoreCountry() {}
|
||||
// Play Billing, only implemented in the google flavor - elsewhere no product is offered
|
||||
suspend fun androidLoadBadgeProducts(productIds: List<String>): List<BadgeProduct> = emptyList()
|
||||
// Play Billing, only implemented in the google flavor. A single Play query cannot mix product
|
||||
// types, so the ids are passed already grouped.
|
||||
// TODO [badges] desktop and foss pay via Stripe/crypto - these defaults leave them without any
|
||||
// product until that path is implemented
|
||||
suspend fun androidLoadBadgeProducts(oneTimeIds: List<String>, subscriptionIds: List<String>): List<BadgeProduct> = emptyList()
|
||||
suspend fun androidPurchaseBadge(productId: String, invoiceId: String): BadgePurchaseOutcome = throw BadgeStoreError.StoreUnavailable
|
||||
val androidApiLevel: Int? get() = null
|
||||
// The build distributed via Google Play, which has to follow its policies
|
||||
|
||||
+20
-3
@@ -21,10 +21,22 @@ fun badgeProductId(level: BadgeLevel, period: BadgePeriod): String = when (level
|
||||
}
|
||||
}
|
||||
|
||||
val badgeProductIds: List<String> = BadgeLevel.entries.flatMap { level ->
|
||||
BadgePeriod.entries.map { badgeProductId(level, it) }
|
||||
// the store sells one-time products and subscriptions through separate APIs, and a single Play
|
||||
// query cannot mix them
|
||||
enum class BadgeProductType { OneTime, Subscription }
|
||||
|
||||
val BadgePeriod.productType: BadgeProductType
|
||||
get() = when (this) {
|
||||
BadgePeriod.OneMonth -> BadgeProductType.OneTime
|
||||
BadgePeriod.Monthly, BadgePeriod.Annual -> BadgeProductType.Subscription
|
||||
}
|
||||
|
||||
fun badgeProductIds(type: BadgeProductType): List<String> = BadgeLevel.entries.flatMap { level ->
|
||||
BadgePeriod.entries.filter { it.productType == type }.map { badgeProductId(level, it) }
|
||||
}
|
||||
|
||||
val badgeProductIds: List<String> = BadgeProductType.entries.flatMap { badgeProductIds(it) }
|
||||
|
||||
// TODO [badges] replaced by APIGetBadgeInvoice, which creates the invoice row and returns its id.
|
||||
// Sent to Play as obfuscatedAccountId and echoed back on the purchase, which is how the service
|
||||
// learns which invoice a store transaction settles.
|
||||
@@ -125,7 +137,12 @@ object BadgeStore {
|
||||
suspend fun load() {
|
||||
if (!startLoading()) return
|
||||
try {
|
||||
val loaded = if (useBadgeTestProducts) testBadgeProducts else platform.androidLoadBadgeProducts(badgeProductIds)
|
||||
// TODO [badges] desktop and the foss build will price from the badge service catalog and pay
|
||||
// via Stripe/crypto instead of a store; only the google build reaches the platform store
|
||||
val loaded = if (useBadgeTestProducts) testBadgeProducts else platform.androidLoadBadgeProducts(
|
||||
oneTimeIds = badgeProductIds(BadgeProductType.OneTime),
|
||||
subscriptionIds = badgeProductIds(BadgeProductType.Subscription)
|
||||
)
|
||||
val byId = loaded.associateBy { it.productId }
|
||||
val missing = badgeProductIds.filter { !byId.containsKey(it) }
|
||||
if (missing.isNotEmpty()) {
|
||||
|
||||
+2
@@ -176,6 +176,8 @@ private fun PeriodCard(level: BadgeLevel, period: BadgePeriod, selectedPeriod: B
|
||||
private fun savingsPercent(level: BadgeLevel, period: BadgePeriod): Int? =
|
||||
if (period == BadgePeriod.Annual) BadgeStore.annualSavings(level) else null
|
||||
|
||||
// TODO [badges] on desktop and the foss build there is no store, so every price is Unavailable and
|
||||
// this button stays disabled - it will offer Stripe/crypto payment instead
|
||||
@Composable
|
||||
private fun PayButton(level: BadgeLevel, selectedPeriod: BadgePeriod, purchasing: MutableState<Boolean>, clipboard: ClipboardManager) {
|
||||
val price = BadgeStore.price(level, selectedPeriod)
|
||||
|
||||
Reference in New Issue
Block a user