Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

in app messages support #1290

Merged
merged 7 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use activity lifecycle callback to get activity and display billing c…
…lient inapp messages + other fixes
  • Loading branch information
tonidero committed Sep 26, 2023
commit 76509bfae62fdda29bd3401731baf8fd7d4ef645
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ static void checkConfiguration(final Context context,
.service(executorService)
.diagnosticsEnabled(true)
.entitlementVerificationMode(EntitlementVerificationMode.INFORMATIONAL)
.showDeclinedPaymentMessagesAutomatically(true)
.build();

final Boolean showDeclinedPaymentMessagesAutomatically = build.getShowDeclinedPaymentMessagesAutomatically();

final Purchases instance = Purchases.getSharedInstance();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ private class PurchasesCommonAPI {
.informationalVerificationModeAndDiagnosticsEnabled(true)
.build()

val showDeclinedPaymentMessagesAutomatically: Boolean = build.showDeclinedPaymentMessagesAutomatically

val instance: Purchases = Purchases.sharedInstance
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ class Purchases internal constructor(
*
* https://developer.android.com/google/play/billing/subscriptions#in-app-messaging
*/
fun showDeclinedPaymentMessageIfNeeded() {
purchasesOrchestrator.showDeclinedPaymentMessageIfNeeded()
fun showDeclinedPaymentMessageIfNeeded(activity: Activity) {
purchasesOrchestrator.showDeclinedPaymentMessageIfNeeded(activity)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,19 @@ class Purchases internal constructor(
purchasesOrchestrator.removeUpdatedCustomerInfoListener()
}

/**
* Google Play only, no-op for Amazon.
* If the user has had a payment declined, this will show a toast notification notifying them and
* providing instructions for recovery of the subscription.
* If [PurchasesConfiguration.showDeclinedPaymentMessagesAutomatically] is enabled, this will be done
* automatically on each Activity's onStart.
*
* For more info: https://developer.android.com/google/play/billing/subscriptions#in-app-messaging
*/
fun showDeclinedPaymentMessageIfNeeded(activity: Activity) {
purchasesOrchestrator.showDeclinedPaymentMessageIfNeeded(activity)
}

/**
* Invalidates the cache for customer information.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ open class PurchasesConfiguration(builder: Builder) {

/**
* Enable this setting to show a toast with recovery options for users who have had a declined payment
* automatically. https://developer.android.com/google/play/billing/subscriptions#in-app-messaging
* automatically. Default is disabled.
* For more info: https://developer.android.com/google/play/billing/subscriptions#in-app-messaging
*
* If this setting is disabled, you can show the toast by calling
* [Purchases.showDeclinedPaymentMessageIfNeeded]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ import com.revenuecat.purchases.strings.IdentityStrings
import com.revenuecat.purchases.strings.PurchaseStrings
import com.revenuecat.purchases.strings.RestoreStrings
import com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager
import com.revenuecat.purchases.utils.CustomActivityLifecycleHandler
import com.revenuecat.purchases.utils.isAndroidNOrNewer
import java.net.URL
import java.util.Collections

@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
internal class PurchasesOrchestrator constructor(
private val application: Application,
backingFieldAppUserID: String?,
Expand All @@ -81,7 +82,7 @@ internal class PurchasesOrchestrator constructor(
private val offeringsManager: OfferingsManager,
// This is nullable due to: https://github.com/RevenueCat/purchases-flutter/issues/408
private val mainHandler: Handler? = Handler(Looper.getMainLooper()),
) : LifecycleDelegate {
) : LifecycleDelegate, CustomActivityLifecycleHandler {

/** @suppress */
@Suppress("RedundantGetter", "RedundantSetter")
Expand Down Expand Up @@ -136,10 +137,6 @@ internal class PurchasesOrchestrator constructor(
billing.stateListener = object : BillingAbstract.StateListener {
override fun onConnected() {
postPendingTransactionsHelper.syncPendingPurchaseQueue(allowSharingPlayStoreAccount)
if (appConfig.showDeclinedPaymentMessagesAutomatically) {
// todo: pass activity
billing.showInAppMessagesIfNeeded()
}
}
}
billing.purchasesUpdatedListener = getPurchasesUpdatedListener()
Expand All @@ -148,6 +145,7 @@ internal class PurchasesOrchestrator constructor(
// This needs to happen after the billing client listeners have been set. This is because
// we perform operations with the billing client in the lifecycle observer methods.
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleHandler)
application.registerActivityLifecycleCallbacks(this)
}

if (!appConfig.dangerousSettings.autoSyncPurchases) {
Expand Down Expand Up @@ -192,6 +190,12 @@ internal class PurchasesOrchestrator constructor(
offlineEntitlementsManager.updateProductEntitlementMappingCacheIfStale()
}

override fun onActivityStarted(activity: Activity) {
if (appConfig.showDeclinedPaymentMessagesAutomatically) {
billing.showInAppMessagesIfNeeded(activity)
}
}

// region Public Methods

fun syncPurchases(
Expand Down Expand Up @@ -458,9 +462,8 @@ internal class PurchasesOrchestrator constructor(
this.updatedCustomerInfoListener = null
}

fun showDeclinedPaymentMessageIfNeeded() {
// TODO: get app activity
billing.showInAppMessagesIfNeeded()
fun showDeclinedPaymentMessageIfNeeded(activity: Activity) {
billing.showInAppMessagesIfNeeded(activity)
}

fun invalidateCustomerInfoCache() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.revenuecat.purchases.common

import android.app.Activity
import com.android.billingclient.api.InAppMessageParams
import com.revenuecat.purchases.ProductType
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.InAppMessageParams
import com.android.billingclient.api.InAppMessageResponseListener
import com.android.billingclient.api.InAppMessageResult
import com.android.billingclient.api.ProductDetailsResponseListener
import com.android.billingclient.api.Purchase
Expand All @@ -40,13 +39,15 @@ import com.revenuecat.purchases.common.ReplaceProductInfo
import com.revenuecat.purchases.common.StoreProductsCallback
import com.revenuecat.purchases.common.between
import com.revenuecat.purchases.common.caching.DeviceCache
import com.revenuecat.purchases.common.debugLog
import com.revenuecat.purchases.common.diagnostics.DiagnosticsTracker
import com.revenuecat.purchases.common.errorLog
import com.revenuecat.purchases.common.firstProductId
import com.revenuecat.purchases.common.log
import com.revenuecat.purchases.common.sha1
import com.revenuecat.purchases.common.sha256
import com.revenuecat.purchases.common.toHumanReadableDescription
import com.revenuecat.purchases.common.verboseLog
import com.revenuecat.purchases.models.GoogleProrationMode
import com.revenuecat.purchases.models.GooglePurchasingData
import com.revenuecat.purchases.models.PurchaseState
Expand All @@ -59,7 +60,7 @@ import com.revenuecat.purchases.strings.RestoreStrings
import com.revenuecat.purchases.utils.Result
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.IllegalStateException
import java.lang.ref.WeakReference
import java.util.Date
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.math.min
Expand Down Expand Up @@ -758,19 +759,23 @@ internal class BillingWrapper(

override fun showInAppMessagesIfNeeded(activity: Activity) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting the activity might actually be tricky here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was able to get it by using an ActivityLifecycleCallback in PurchasesOrchestrator

val inAppMessageParams = InAppMessageParams.newBuilder()
.addInAppMessageCategoryToShow(InAppMessageParams.InAppMessageCategoryId.TRANSACTIONAL)
.build()

billingClient?.showInAppMessages(activity,
inAppMessageParams
) { inAppMessageResult ->
if (inAppMessageResult.responseCode == InAppMessageResult.InAppMessageResponseCode.NO_ACTION_NEEDED) {
// No in-app message was available.
// TODO: log
} else if (inAppMessageResult.responseCode
== InAppMessageResult.InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED
) {
// TODO: post receipt
.addInAppMessageCategoryToShow(InAppMessageParams.InAppMessageCategoryId.TRANSACTIONAL)
.build()
val weakActivity = WeakReference(activity)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a weak reference here since I want to avoid any chance of leaking the activity if there is a problem connecting to the billing client.


withConnectedClient {
val activity = weakActivity.get() ?: run {
debugLog("Activity is null, not showing Google Play in-app message.")
return@withConnectedClient
}
showInAppMessages(activity, inAppMessageParams) { inAppMessageResult ->
if (inAppMessageResult.responseCode == InAppMessageResult.InAppMessageResponseCode.NO_ACTION_NEEDED) {
verboseLog("No Google Play in-app message was available.")
} else if (inAppMessageResult.responseCode
== InAppMessageResult.InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED
) {
debugLog("Subscription status was updated from In-App Message.")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.revenuecat.purchases.utils

import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle

@Suppress("EmptyFunctionBlock")
internal interface CustomActivityLifecycleHandler : ActivityLifecycleCallbacks {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface just adds default empty implementations to all callbacks so we can just use the ones we need in PurchasesOrchestrator


override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}

override fun onActivityStarted(activity: Activity) {}

override fun onActivityResumed(activity: Activity) {}

override fun onActivityPaused(activity: Activity) {}

override fun onActivityStopped(activity: Activity) {}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

override fun onActivityDestroyed(activity: Activity) {}
}