mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-05 02:39:30 +02:00
Add server public key check before purchases
This commit is contained in:
parent
5a17045551
commit
13c97bc41e
7 changed files with 57 additions and 34 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -15,17 +15,41 @@
|
|||
*/
|
||||
package io.timelimit.android.sync.network
|
||||
|
||||
enum class CanDoPurchaseStatus {
|
||||
Yes, NotDueToOldPurchase, NoForUnknownReason
|
||||
import android.util.Base64
|
||||
import android.util.JsonReader
|
||||
|
||||
sealed class CanDoPurchaseStatus {
|
||||
class Yes(val publicKey: ByteArray?): CanDoPurchaseStatus()
|
||||
object NoForUnknownReason: CanDoPurchaseStatus()
|
||||
object NotDueToOldPurchase: CanDoPurchaseStatus()
|
||||
}
|
||||
|
||||
object CanDoPurchaseParser {
|
||||
private const val CAN_DO_PURCHASE = "canDoPurchase"
|
||||
private const val GPLAY_PUBLIC_KEY = "googlePlayPublicKey"
|
||||
private const val YES = "yes"
|
||||
private const val NO_DUE_TO_OLD_PURCHASE = "no due to old purchase"
|
||||
|
||||
fun parse(value: String) = when(value) {
|
||||
YES -> CanDoPurchaseStatus.Yes
|
||||
NO_DUE_TO_OLD_PURCHASE -> CanDoPurchaseStatus.NotDueToOldPurchase
|
||||
else -> CanDoPurchaseStatus.NoForUnknownReason
|
||||
fun parse(reader: JsonReader): CanDoPurchaseStatus {
|
||||
reader.beginObject()
|
||||
|
||||
var canDoPurchaseStatus: String? = null
|
||||
var publicKey: ByteArray? = null
|
||||
|
||||
while (reader.hasNext()) {
|
||||
when (reader.nextName()) {
|
||||
CAN_DO_PURCHASE -> canDoPurchaseStatus = reader.nextString()
|
||||
GPLAY_PUBLIC_KEY -> publicKey = Base64.decode(reader.nextString(), 0)
|
||||
else -> reader.skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
|
||||
return when (canDoPurchaseStatus!!) {
|
||||
YES -> CanDoPurchaseStatus.Yes(publicKey)
|
||||
NO_DUE_TO_OLD_PURCHASE -> CanDoPurchaseStatus.NotDueToOldPurchase
|
||||
else -> CanDoPurchaseStatus.NoForUnknownReason
|
||||
}
|
||||
}
|
||||
}
|
|
@ -476,18 +476,8 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
|||
|
||||
return Threads.network.executeAndWait {
|
||||
val reader = JsonReader(response.body()!!.charStream())
|
||||
var status: CanDoPurchaseStatus? = null
|
||||
|
||||
reader.beginObject()
|
||||
while (reader.hasNext()) {
|
||||
when (reader.nextName()) {
|
||||
"canDoPurchase" -> status = CanDoPurchaseParser.parse(reader.nextString())
|
||||
else -> reader.skipValue()
|
||||
}
|
||||
}
|
||||
reader.endObject()
|
||||
|
||||
status!!
|
||||
CanDoPurchaseParser.parse(reader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -194,7 +194,7 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
|
|||
return@runAsync
|
||||
}
|
||||
|
||||
if (server.api.canDoPurchase(server.deviceAuthToken) != CanDoPurchaseStatus.Yes) {
|
||||
if (!(server.api.canDoPurchase(server.deviceAuthToken) is CanDoPurchaseStatus.Yes)) {
|
||||
throw IOException("can not do purchase right now")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ class PurchaseFragment : Fragment(), FragmentWithCustomTitle {
|
|||
PurchaseFragmentNetworkError -> getString(R.string.error_network)
|
||||
PurchaseFragmentExistingPaymentError -> getString(R.string.purchase_error_existing_payment)
|
||||
PurchaseFragmentServerRejectedError -> getString(R.string.purchase_error_server_rejected)
|
||||
PurchaseFragmentServerHasDifferentPublicKey -> getString(R.string.purchase_error_server_different_key)
|
||||
}
|
||||
|
||||
binding.showRetryButton = when (fragmentStatus) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package io.timelimit.android.ui.payment
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Base64
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.timelimit.android.BuildConfig
|
||||
|
@ -65,22 +66,26 @@ class PurchaseModel(application: Application): AndroidViewModel(application) {
|
|||
else
|
||||
CanDoPurchaseStatus.NoForUnknownReason
|
||||
|
||||
if (canDoPurchase == CanDoPurchaseStatus.Yes) {
|
||||
val checkout = Checkout.forApplication(application.billing)
|
||||
if (canDoPurchase is CanDoPurchaseStatus.Yes) {
|
||||
if (canDoPurchase.publicKey?.equals(Base64.decode(BuildConfig.googlePlayKey, 0)) == false) {
|
||||
statusInternal.value = PurchaseFragmentServerHasDifferentPublicKey
|
||||
} else {
|
||||
val checkout = Checkout.forApplication(application.billing)
|
||||
|
||||
checkout.startAsync().use {
|
||||
if (!it.billingSupported) {
|
||||
statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice
|
||||
} else {
|
||||
val skus = it.requests.getSkusAsync(
|
||||
ProductTypes.IN_APP,
|
||||
PurchaseIds.BUY_SKUS
|
||||
)
|
||||
checkout.startAsync().use {
|
||||
if (!it.billingSupported) {
|
||||
statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice
|
||||
} else {
|
||||
val skus = it.requests.getSkusAsync(
|
||||
ProductTypes.IN_APP,
|
||||
PurchaseIds.BUY_SKUS
|
||||
)
|
||||
|
||||
statusInternal.value = PurchaseFragmentReady(
|
||||
monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(),
|
||||
yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString()
|
||||
)
|
||||
statusInternal.value = PurchaseFragmentReady(
|
||||
monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(),
|
||||
yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) {
|
||||
|
@ -110,3 +115,4 @@ object PurchaseFragmentErrorBillingNotSupportedByAppVariant: PurchaseFragmentUnr
|
|||
object PurchaseFragmentNetworkError: PurchaseFragmentRecoverableError()
|
||||
object PurchaseFragmentExistingPaymentError: PurchaseFragmentUnrecoverableError()
|
||||
object PurchaseFragmentServerRejectedError: PurchaseFragmentUnrecoverableError()
|
||||
object PurchaseFragmentServerHasDifferentPublicKey: PurchaseFragmentUnrecoverableError()
|
|
@ -41,6 +41,7 @@
|
|||
Sie haben bereits bezahlt. Sie können die Vollversion erst kurz vor dem Ablaufen (oder danach) verlängern
|
||||
</string>
|
||||
<string name="purchase_error_server_rejected">Der Server akzeptiert momentan keine Bezahlungen</string>
|
||||
<string name="purchase_error_server_different_key">Der Server unterstützt Bezahlungen, aber nicht über diesen App-Build</string>
|
||||
|
||||
<string name="purchase_done_title">Bezahlung abgeschlossen</string>
|
||||
<string name="purchase_done_text">Betätigen Sie die Zurück-Taste, um zur App zurückzukehren</string>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
You can only extend the premium version if it expired or shortly before that.
|
||||
</string>
|
||||
<string name="purchase_error_server_rejected">The server does not accept payments currently</string>
|
||||
<string name="purchase_error_server_different_key">The server does support payments, but not using this app build</string>
|
||||
|
||||
<string name="purchase_done_title">Purchase succeeded</string>
|
||||
<string name="purchase_done_text">Press back to go back to the App</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue