@ -3,10 +3,19 @@ package net.pokeranalytics.android.util.billing
import android.app.Activity
import android.app.Activity
import android.content.Context
import android.content.Context
import com.android.billingclient.api.*
import com.android.billingclient.api.*
import net.pokeranalytics.android.ui.activity.IAPProducts
import net.pokeranalytics.android.R
import timber.log.Timber
import timber.log.Timber
import java.io.IOException
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 ,
* 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 {
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
* 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
* Whether the billing client is available
@ -32,6 +42,11 @@ object AppGuard : PurchasesUpdatedListener {
var isProUser : Boolean = false
var isProUser : Boolean = false
private set
private set
/ * *
* A delegate to notify when the purchase has succeeded
* /
private var purchaseDelegate : PurchaseDelegate ? = null
/ * *
/ * *
* Initialization of AppGuard
* Initialization of AppGuard
* Connects to billing services and restores purchases
* Connects to billing services and restores purchases
@ -77,6 +92,7 @@ object AppGuard : PurchasesUpdatedListener {
* Restore purchases
* Restore purchases
* /
* /
fun restorePurchases ( ) {
fun restorePurchases ( ) {
this . resetPurchases ( )
// Automatically checks for purchases (when switching devices for example)
// Automatically checks for purchases (when switching devices for example)
val purchasesResult =
val purchasesResult =
billingClient . queryPurchases ( BillingClient . SkuType . SUBS )
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
* Requests the product descriptions
* /
* /
@ -109,7 +133,9 @@ object AppGuard : PurchasesUpdatedListener {
/ * *
/ * *
* Initiates purchase with the product [ skuDetails ]
* 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 ( )
val flowParams = BillingFlowParams . newBuilder ( )
. setSkuDetails ( skuDetails )
. setSkuDetails ( skuDetails )
@ -117,6 +143,7 @@ object AppGuard : PurchasesUpdatedListener {
this . executeServiceRequest ( Runnable {
this . executeServiceRequest ( Runnable {
val responseCode = billingClient . launchBillingFlow ( activity , flowParams )
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 ) {
private fun handlePurchase ( purchase : Purchase ) {
@ -150,7 +177,17 @@ object AppGuard : PurchasesUpdatedListener {
if ( this . verifyValidSignature ( purchase . originalJson , purchase . signature ) ) {
if ( this . verifyValidSignature ( purchase . originalJson , purchase . signature ) ) {
when ( purchase . sku ) {
when ( purchase . sku ) {
IAPProducts . PRO . identifier -> {
IAPProducts . PRO . identifier -> {
val date = Date ( purchase . purchaseTime )
Timber . d ( " *** Auto renewing = ${purchase.isAutoRenewing} " )
Timber . d ( " *** purchaseTime = ${date} " )
this . isProUser = true
this . isProUser = true
this . purchaseDelegate ?. let {
it . purchaseDidSucceed ( )
this . purchaseDelegate = null
}
}
}
else -> {
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 {
private fun verifyValidSignature ( signedData : String , signature : String ) : Boolean {
try {
return try {
return Security . verifyPurchase ( BASE _64 _ENCODED _PUBLIC _KEY , signedData , signature )
Security . verifyPurchase ( BASE _64 _ENCODED _PUBLIC _KEY , signedData , signature )
} catch ( e : IOException ) {
} catch ( e : IOException ) {
Timber . d ( " Got an exception trying to validate a purchase: $e " )
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 )
}
}
}