mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-05 10:49:26 +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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,17 +15,41 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.sync.network
|
package io.timelimit.android.sync.network
|
||||||
|
|
||||||
enum class CanDoPurchaseStatus {
|
import android.util.Base64
|
||||||
Yes, NotDueToOldPurchase, NoForUnknownReason
|
import android.util.JsonReader
|
||||||
|
|
||||||
|
sealed class CanDoPurchaseStatus {
|
||||||
|
class Yes(val publicKey: ByteArray?): CanDoPurchaseStatus()
|
||||||
|
object NoForUnknownReason: CanDoPurchaseStatus()
|
||||||
|
object NotDueToOldPurchase: CanDoPurchaseStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
object CanDoPurchaseParser {
|
object CanDoPurchaseParser {
|
||||||
|
private const val CAN_DO_PURCHASE = "canDoPurchase"
|
||||||
|
private const val GPLAY_PUBLIC_KEY = "googlePlayPublicKey"
|
||||||
private const val YES = "yes"
|
private const val YES = "yes"
|
||||||
private const val NO_DUE_TO_OLD_PURCHASE = "no due to old purchase"
|
private const val NO_DUE_TO_OLD_PURCHASE = "no due to old purchase"
|
||||||
|
|
||||||
fun parse(value: String) = when(value) {
|
fun parse(reader: JsonReader): CanDoPurchaseStatus {
|
||||||
YES -> CanDoPurchaseStatus.Yes
|
reader.beginObject()
|
||||||
NO_DUE_TO_OLD_PURCHASE -> CanDoPurchaseStatus.NotDueToOldPurchase
|
|
||||||
else -> CanDoPurchaseStatus.NoForUnknownReason
|
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 {
|
return Threads.network.executeAndWait {
|
||||||
val reader = JsonReader(response.body()!!.charStream())
|
val reader = JsonReader(response.body()!!.charStream())
|
||||||
var status: CanDoPurchaseStatus? = null
|
|
||||||
|
|
||||||
reader.beginObject()
|
CanDoPurchaseParser.parse(reader)
|
||||||
while (reader.hasNext()) {
|
|
||||||
when (reader.nextName()) {
|
|
||||||
"canDoPurchase" -> status = CanDoPurchaseParser.parse(reader.nextString())
|
|
||||||
else -> reader.skipValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.endObject()
|
|
||||||
|
|
||||||
status!!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* 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
|
* 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
|
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")
|
throw IOException("can not do purchase right now")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ class PurchaseFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
PurchaseFragmentNetworkError -> getString(R.string.error_network)
|
PurchaseFragmentNetworkError -> getString(R.string.error_network)
|
||||||
PurchaseFragmentExistingPaymentError -> getString(R.string.purchase_error_existing_payment)
|
PurchaseFragmentExistingPaymentError -> getString(R.string.purchase_error_existing_payment)
|
||||||
PurchaseFragmentServerRejectedError -> getString(R.string.purchase_error_server_rejected)
|
PurchaseFragmentServerRejectedError -> getString(R.string.purchase_error_server_rejected)
|
||||||
|
PurchaseFragmentServerHasDifferentPublicKey -> getString(R.string.purchase_error_server_different_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.showRetryButton = when (fragmentStatus) {
|
binding.showRetryButton = when (fragmentStatus) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package io.timelimit.android.ui.payment
|
package io.timelimit.android.ui.payment
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.util.Base64
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.timelimit.android.BuildConfig
|
import io.timelimit.android.BuildConfig
|
||||||
|
@ -65,22 +66,26 @@ class PurchaseModel(application: Application): AndroidViewModel(application) {
|
||||||
else
|
else
|
||||||
CanDoPurchaseStatus.NoForUnknownReason
|
CanDoPurchaseStatus.NoForUnknownReason
|
||||||
|
|
||||||
if (canDoPurchase == CanDoPurchaseStatus.Yes) {
|
if (canDoPurchase is CanDoPurchaseStatus.Yes) {
|
||||||
val checkout = Checkout.forApplication(application.billing)
|
if (canDoPurchase.publicKey?.equals(Base64.decode(BuildConfig.googlePlayKey, 0)) == false) {
|
||||||
|
statusInternal.value = PurchaseFragmentServerHasDifferentPublicKey
|
||||||
|
} else {
|
||||||
|
val checkout = Checkout.forApplication(application.billing)
|
||||||
|
|
||||||
checkout.startAsync().use {
|
checkout.startAsync().use {
|
||||||
if (!it.billingSupported) {
|
if (!it.billingSupported) {
|
||||||
statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice
|
statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice
|
||||||
} else {
|
} else {
|
||||||
val skus = it.requests.getSkusAsync(
|
val skus = it.requests.getSkusAsync(
|
||||||
ProductTypes.IN_APP,
|
ProductTypes.IN_APP,
|
||||||
PurchaseIds.BUY_SKUS
|
PurchaseIds.BUY_SKUS
|
||||||
)
|
)
|
||||||
|
|
||||||
statusInternal.value = PurchaseFragmentReady(
|
statusInternal.value = PurchaseFragmentReady(
|
||||||
monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(),
|
monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(),
|
||||||
yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString()
|
yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) {
|
} else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) {
|
||||||
|
@ -110,3 +115,4 @@ object PurchaseFragmentErrorBillingNotSupportedByAppVariant: PurchaseFragmentUnr
|
||||||
object PurchaseFragmentNetworkError: PurchaseFragmentRecoverableError()
|
object PurchaseFragmentNetworkError: PurchaseFragmentRecoverableError()
|
||||||
object PurchaseFragmentExistingPaymentError: PurchaseFragmentUnrecoverableError()
|
object PurchaseFragmentExistingPaymentError: PurchaseFragmentUnrecoverableError()
|
||||||
object PurchaseFragmentServerRejectedError: 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
|
Sie haben bereits bezahlt. Sie können die Vollversion erst kurz vor dem Ablaufen (oder danach) verlängern
|
||||||
</string>
|
</string>
|
||||||
<string name="purchase_error_server_rejected">Der Server akzeptiert momentan keine Bezahlungen</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_title">Bezahlung abgeschlossen</string>
|
||||||
<string name="purchase_done_text">Betätigen Sie die Zurück-Taste, um zur App zurückzukehren</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.
|
You can only extend the premium version if it expired or shortly before that.
|
||||||
</string>
|
</string>
|
||||||
<string name="purchase_error_server_rejected">The server does not accept payments currently</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_title">Purchase succeeded</string>
|
||||||
<string name="purchase_done_text">Press back to go back to the App</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