Subscription update

dev
Laurent 7 years ago
parent d8f847c86b
commit 5cc7efb4ad
  1. 3
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  2. 4
      app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
  3. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  4. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  5. 13
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  6. 10
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
  7. 63
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt

@ -16,6 +16,7 @@ import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.util.FakeDataManager
import net.pokeranalytics.android.util.PokerAnalyticsLogs
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
@ -27,7 +28,7 @@ class PokerAnalyticsApplication : Application() {
UserDefaults.init(this)
// AppGuard / Billing services
// AppGuard.load(this.applicationContext)
AppGuard.load(this.applicationContext)
// Realm
Realm.init(this)

@ -7,10 +7,6 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.util.billing.AppGuard
enum class IAPProducts(var identifier: String) {
PRO("pro")
}
class BillingActivity : PokerAnalyticsActivity() {
companion object {

@ -182,6 +182,12 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate {
*/
private fun createNewSession(isTournament: Boolean) {
// if (!AppGuard.isProUser) { // && !BuildConfig.DEBUG
// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show()
// BillingActivity.newInstance(requireContext())
// return
// }
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return
@ -219,7 +225,7 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate {
* Show end of beta message
*/
private fun showEndOfBetaMessage() {
Toast.makeText(context, "Beta has ended. Please update with the Google Play version", Toast.LENGTH_LONG).show()
Toast.makeText(context, "Beta has ended. Thanks a lot for your participation! Please update with the Google Play version to continue using the app", Toast.LENGTH_LONG).show()
}
}

@ -28,6 +28,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard
import java.util.*
@ -90,6 +91,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SettingRow.SUBSCRIPTION -> AppGuard.subscriptionStatus(requireContext())
SettingRow.VERSION -> BuildConfig.VERSION_NAME + if (BuildConfig.DEBUG) " (${BuildConfig.VERSION_CODE}) DEBUG" else ""
SettingRow.CURRENCY -> UserDefaults.currency.symbol
else -> ""

@ -10,11 +10,12 @@ import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsResponseListener
import kotlinx.android.synthetic.main.fragment_subscription.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.IAPProducts
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.IAPProducts
import net.pokeranalytics.android.util.billing.PurchaseDelegate
class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListener {
class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListener, PurchaseDelegate {
var selectedProduct: SkuDetails? = null
@ -53,7 +54,7 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
this.purchase.setOnClickListener {
this.selectedProduct?.let {
AppGuard.initiatePurchase(this.requireActivity(), it)
AppGuard.initiatePurchase(this.requireActivity(), it, this)
} ?: run {
throw IllegalStateException("Attempt to initiate purchase while no product has been chosen")
}
@ -65,4 +66,10 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
this.purchase.isEnabled = (this.selectedProduct != null)
}
// PurchaseDelegate
override fun purchaseDidSucceed() {
this.activity?.finish()
}
}

@ -9,6 +9,7 @@ import net.pokeranalytics.android.ui.view.RowViewType
enum class SettingRow : RowRepresentable {
// About
SUBSCRIPTION,
VERSION,
RATE_APP,
CONTACT_US,
@ -44,7 +45,7 @@ enum class SettingRow : RowRepresentable {
val rows = ArrayList<RowRepresentable>()
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.information))
rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT))
rows.addAll(arrayListOf(SUBSCRIPTION, VERSION, RATE_APP, CONTACT_US, BUG_REPORT))
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.follow_us))
rows.addAll(arrayListOf(FOLLOW_US))
@ -58,8 +59,8 @@ enum class SettingRow : RowRepresentable {
resId = R.string.data_management
)
)
rows.addAll(arrayListOf(BANKROLL, GAME, LOCATION, TOURNAMENT_NAME, TOURNAMENT_FEATURE))
//, TRANSACTION, TRANSACTION_TYPE //TODO add them back
rows.addAll(arrayListOf(BANKROLL, GAME, LOCATION, TOURNAMENT_NAME, TOURNAMENT_FEATURE, TRANSACTION_TYPE))
//, TRANSACTION, //TODO add them back
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.terms))
rows.addAll(arrayListOf(PRIVACY_POLICY, TERMS_OF_USE, GDPR))
@ -75,6 +76,7 @@ enum class SettingRow : RowRepresentable {
return it.resId
} ?: run {
return when (this) {
SUBSCRIPTION -> R.string.subscription
VERSION -> R.string.version
RATE_APP -> R.string.releasenote_rating
CONTACT_US -> R.string.contact
@ -94,7 +96,7 @@ enum class SettingRow : RowRepresentable {
override val viewType: Int
get() {
return when (this) {
VERSION -> RowViewType.TITLE_VALUE.ordinal
VERSION, SUBSCRIPTION -> RowViewType.TITLE_VALUE.ordinal
LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal
FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal
else -> RowViewType.TITLE_ARROW.ordinal

@ -3,10 +3,19 @@ package net.pokeranalytics.android.util.billing
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.*
import net.pokeranalytics.android.ui.activity.IAPProducts
import net.pokeranalytics.android.R
import timber.log.Timber
import java.io.IOException
import java.util.*
import kotlin.collections.ArrayList
enum class IAPProducts(var identifier: String) {
PRO("unlimited")
}
interface PurchaseDelegate {
fun purchaseDidSucceed()
}
/**
* the AppGuard object is in charge of contacting the Billing services to retrieve products,
@ -14,12 +23,13 @@ import java.io.IOException
*/
object AppGuard : PurchasesUpdatedListener {
private val BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VuL5bk2w0FNaZIwE3v2857ZxJlo0epIIsVJfVs7Pqh7zE1JN7uGBgZ/r2s5Rn3o0R1Ycqxp832kYg/B0FvlQJ3Ko6IZkoyfJQ3i1zuAcc7NLMxKUMJY8Mc0U6Go2bevjQ54WkvumIdAFWIlMjyuOOFcSZRZr8V7tlq0SYlenkuellQeHIq3V47M/0jlDrEbCFj59hsukN75eGIiafFAxBYO/8L/flkZLik8YyhV1uZTu+KziA0PsbIvXKyN+gCK9UmrscTyM4+hfmRgb74fro67UsEqq2OvmHFUhubPzCZDElOwPeauUDEGeQjJn43iUHZWIcSEVktVB9cFa/0JwIDAQAB"
private const val BASE_64_ENCODED_PUBLIC_KEY =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VuL5bk2w0FNaZIwE3v2857ZxJlo0epIIsVJfVs7Pqh7zE1JN7uGBgZ/r2s5Rn3o0R1Ycqxp832kYg/B0FvlQJ3Ko6IZkoyfJQ3i1zuAcc7NLMxKUMJY8Mc0U6Go2bevjQ54WkvumIdAFWIlMjyuOOFcSZRZr8V7tlq0SYlenkuellQeHIq3V47M/0jlDrEbCFj59hsukN75eGIiafFAxBYO/8L/flkZLik8YyhV1uZTu+KziA0PsbIvXKyN+gCK9UmrscTyM4+hfmRgb74fro67UsEqq2OvmHFUhubPzCZDElOwPeauUDEGeQjJn43iUHZWIcSEVktVB9cFa/0JwIDAQAB"
/**
* The Billing Client making requests with Google Billing services
*/
lateinit private var billingClient: BillingClient
private lateinit var billingClient: BillingClient
/**
* Whether the billing client is available
@ -32,6 +42,11 @@ object AppGuard : PurchasesUpdatedListener {
var isProUser: Boolean = false
private set
/**
* A delegate to notify when the purchase has succeeded
*/
private var purchaseDelegate: PurchaseDelegate? = null
/**
* Initialization of AppGuard
* Connects to billing services and restores purchases
@ -77,6 +92,7 @@ object AppGuard : PurchasesUpdatedListener {
* Restore purchases
*/
fun restorePurchases() {
this.resetPurchases()
// Automatically checks for purchases (when switching devices for example)
val purchasesResult =
billingClient.queryPurchases(BillingClient.SkuType.SUBS)
@ -85,6 +101,14 @@ object AppGuard : PurchasesUpdatedListener {
}
}
/**
* Reset all purchases
* This is done before restoring in order to ensure that subscriptions stops
*/
private fun resetPurchases() {
this.isProUser = false
}
/**
* Requests the product descriptions
*/
@ -109,7 +133,9 @@ object AppGuard : PurchasesUpdatedListener {
/**
* Initiates purchase with the product [skuDetails]
*/
fun initiatePurchase(activity: Activity, skuDetails: SkuDetails) {
fun initiatePurchase(activity: Activity, skuDetails: SkuDetails, delegate: PurchaseDelegate) {
this.purchaseDelegate = delegate
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
@ -117,6 +143,7 @@ object AppGuard : PurchasesUpdatedListener {
this.executeServiceRequest(Runnable {
val responseCode = billingClient.launchBillingFlow(activity, flowParams)
Timber.d("launchBillingFlow returned $responseCode")
})
}
@ -141,7 +168,7 @@ object AppGuard : PurchasesUpdatedListener {
}
/**
* Method called when a purchase has been made
* Method called when a [purchase] has been made
*/
private fun handlePurchase(purchase: Purchase) {
@ -150,7 +177,17 @@ object AppGuard : PurchasesUpdatedListener {
if (this.verifyValidSignature(purchase.originalJson, purchase.signature)) {
when (purchase.sku) {
IAPProducts.PRO.identifier -> {
val date = Date(purchase.purchaseTime)
Timber.d("*** Auto renewing = ${purchase.isAutoRenewing}")
Timber.d("*** purchaseTime = ${date}")
this.isProUser = true
this.purchaseDelegate?.let {
it.purchaseDidSucceed()
this.purchaseDelegate = null
}
}
else -> {
}
@ -162,15 +199,23 @@ object AppGuard : PurchasesUpdatedListener {
}
/**
* Verifies the validity of a purchase
* Verifies the validity of a purchase with its [signedData] and [signature]
*/
private fun verifyValidSignature(signedData: String, signature: String): Boolean {
try {
return Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
return try {
Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
} catch (e: IOException) {
Timber.d("Got an exception trying to validate a purchase: $e")
return false
false
}
}
/**
* Returns the subscription status of the user, using the [context]
*/
fun subscriptionStatus(context: Context): String {
val resId = if (this.isProUser) R.string.pro_sub_short_title else R.string.none
return context.getString(resId)
}
}
Loading…
Cancel
Save