Skip to content

Commit

Permalink
Parse product details of GPBL v5 - v7
Browse files Browse the repository at this point in the history
Summary: The Product Detail JSON Object returned by Google stores the price and currency information in a json object called [OneTimePurchaseOfferDetails](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.OneTimePurchaseOfferDetails). Therefore, to auto-log IAP events coming from GPBL v5+, we need to parse this new object for the purchase data.

Reviewed By: jjiang10

Differential Revision: D62040332

fbshipit-source-id: a18a416ee965e2f6cdd69ad757d0ab50494a8710
  • Loading branch information
maxalbrightmeta authored and facebook-github-bot committed Sep 11, 2024
1 parent f598208 commit 97e922b
Show file tree
Hide file tree
Showing 3 changed files with 456 additions and 347 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.facebook.appevents.AppEventsConstants
import com.facebook.appevents.AppEventsLogger
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.appevents.iap.InAppPurchaseEventManager
import com.facebook.appevents.iap.InAppPurchaseUtils
import com.facebook.internal.FetchedAppGateKeepersManager.getGateKeeperForKey
import com.facebook.internal.FetchedAppSettingsManager.getAppSettingsWithoutQuery
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
Expand All @@ -32,132 +33,197 @@ import org.json.JSONObject
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object AutomaticAnalyticsLogger {
// Constants
private val TAG = AutomaticAnalyticsLogger::class.java.canonicalName
private const val APP_EVENTS_IF_AUTO_LOG_SUBS = "app_events_if_auto_log_subs"
private val internalAppEventsLogger = InternalAppEventsLogger(FacebookSdk.getApplicationContext())
// Constants
private val TAG = AutomaticAnalyticsLogger::class.java.canonicalName
private const val APP_EVENTS_IF_AUTO_LOG_SUBS = "app_events_if_auto_log_subs"
private val internalAppEventsLogger =
InternalAppEventsLogger(FacebookSdk.getApplicationContext())

@JvmStatic
fun logActivateAppEvent() {
val context = FacebookSdk.getApplicationContext()
val appId = FacebookSdk.getApplicationId()
val autoLogAppEvents = FacebookSdk.getAutoLogAppEventsEnabled()
if (autoLogAppEvents) {
if (context is Application) {
AppEventsLogger.activateApp(context, appId)
} else { // Context is probably originated from ContentProvider or Mocked
Log.w(
TAG,
"Automatic logging of basic events will not happen, because " +
"FacebookSdk.getApplicationContext() returns object that is not " +
"instance of android.app.Application. Make sure you call " +
"FacebookSdk.sdkInitialize() from Application class and pass " +
"application context.")
}
@JvmStatic
fun logActivateAppEvent() {
val context = FacebookSdk.getApplicationContext()
val appId = FacebookSdk.getApplicationId()
val autoLogAppEvents = FacebookSdk.getAutoLogAppEventsEnabled()
if (autoLogAppEvents) {
if (context is Application) {
AppEventsLogger.activateApp(context, appId)
} else { // Context is probably originated from ContentProvider or Mocked
Log.w(
TAG,
"Automatic logging of basic events will not happen, because " +
"FacebookSdk.getApplicationContext() returns object that is not " +
"instance of android.app.Application. Make sure you call " +
"FacebookSdk.sdkInitialize() from Application class and pass " +
"application context."
)
}
}
}
}

@JvmStatic
fun logActivityTimeSpentEvent(activityName: String?, timeSpentInSeconds: Long) {
val context = FacebookSdk.getApplicationContext()
val appId = FacebookSdk.getApplicationId()
val settings = queryAppSettings(appId, false)
if (settings != null && settings.automaticLoggingEnabled && timeSpentInSeconds > 0) {
val logger = InternalAppEventsLogger(context)
val params = Bundle(1)
params.putCharSequence(Constants.AA_TIME_SPENT_SCREEN_PARAMETER_NAME, activityName)
logger.logEvent(Constants.AA_TIME_SPENT_EVENT_NAME, timeSpentInSeconds.toDouble(), params)
@JvmStatic
fun logActivityTimeSpentEvent(activityName: String?, timeSpentInSeconds: Long) {
val context = FacebookSdk.getApplicationContext()
val appId = FacebookSdk.getApplicationId()
val settings = queryAppSettings(appId, false)
if (settings != null && settings.automaticLoggingEnabled && timeSpentInSeconds > 0) {
val logger = InternalAppEventsLogger(context)
val params = Bundle(1)
params.putCharSequence(Constants.AA_TIME_SPENT_SCREEN_PARAMETER_NAME, activityName)
logger.logEvent(
Constants.AA_TIME_SPENT_EVENT_NAME,
timeSpentInSeconds.toDouble(),
params
)
}
}
}

@JvmStatic
fun logPurchase(purchase: String, skuDetails: String, isSubscription: Boolean) {
if (!isImplicitPurchaseLoggingEnabled()) {
return
}
val loggingParameters = getPurchaseLoggingParameters(purchase, skuDetails) ?: return
val logAsSubs =
isSubscription &&
getGateKeeperForKey(APP_EVENTS_IF_AUTO_LOG_SUBS, FacebookSdk.getApplicationId(), false)
if (logAsSubs) {
val eventName =
if (InAppPurchaseEventManager.hasFreeTrialPeirod(skuDetails)) {
AppEventsConstants.EVENT_NAME_START_TRIAL
} else {
AppEventsConstants.EVENT_NAME_SUBSCRIBE
}
internalAppEventsLogger.logEventImplicitly(
eventName,
loggingParameters.purchaseAmount,
loggingParameters.currency,
loggingParameters.param)
} else {
internalAppEventsLogger.logPurchaseImplicitly(
loggingParameters.purchaseAmount, loggingParameters.currency, loggingParameters.param)
@JvmStatic
fun logPurchase(purchase: String, skuDetails: String, isSubscription: Boolean) {
if (!isImplicitPurchaseLoggingEnabled()) {
return
}
val loggingParameters = getPurchaseLoggingParameters(purchase, skuDetails) ?: return
val logAsSubs =
isSubscription &&
getGateKeeperForKey(
APP_EVENTS_IF_AUTO_LOG_SUBS,
FacebookSdk.getApplicationId(),
false
)
if (logAsSubs) {
val eventName =
if (InAppPurchaseEventManager.hasFreeTrialPeirod(skuDetails)) {
AppEventsConstants.EVENT_NAME_START_TRIAL
} else {
AppEventsConstants.EVENT_NAME_SUBSCRIBE
}
internalAppEventsLogger.logEventImplicitly(
eventName,
loggingParameters.purchaseAmount,
loggingParameters.currency,
loggingParameters.param
)
} else {
internalAppEventsLogger.logPurchaseImplicitly(
loggingParameters.purchaseAmount,
loggingParameters.currency,
loggingParameters.param
)
}
}
}

@JvmStatic
fun isImplicitPurchaseLoggingEnabled(): Boolean {
val appId = FacebookSdk.getApplicationId()
val settings = getAppSettingsWithoutQuery(appId)
return settings != null &&
FacebookSdk.getAutoLogAppEventsEnabled() &&
settings.iAPAutomaticLoggingEnabled
}
@JvmStatic
fun isImplicitPurchaseLoggingEnabled(): Boolean {
val appId = FacebookSdk.getApplicationId()
val settings = getAppSettingsWithoutQuery(appId)
return settings != null &&
FacebookSdk.getAutoLogAppEventsEnabled() &&
settings.iAPAutomaticLoggingEnabled
}

private fun getPurchaseLoggingParameters(
purchase: String,
skuDetails: String
): PurchaseLoggingParameters? {
return getPurchaseLoggingParameters(purchase, skuDetails, HashMap())
}
private fun getPurchaseLoggingParameters(
purchase: String,
skuDetails: String
): PurchaseLoggingParameters? {
return getPurchaseLoggingParameters(purchase, skuDetails, HashMap())
}

private fun getPurchaseLoggingParameters(
purchase: String,
skuDetails: String,
extraParameter: Map<String, String>
): PurchaseLoggingParameters? {
return try {
val purchaseJSON = JSONObject(purchase)
val skuDetailsJSON = JSONObject(skuDetails)
val params = Bundle(1)
params.putCharSequence(Constants.IAP_PRODUCT_ID, purchaseJSON.getString("productId"))
params.putCharSequence(Constants.IAP_PURCHASE_TIME, purchaseJSON.getString("purchaseTime"))
params.putCharSequence(Constants.IAP_PURCHASE_TOKEN, purchaseJSON.getString("purchaseToken"))
params.putCharSequence(Constants.IAP_PACKAGE_NAME, purchaseJSON.optString("packageName"))
params.putCharSequence(Constants.IAP_PRODUCT_TITLE, skuDetailsJSON.optString("title"))
params.putCharSequence(
Constants.IAP_PRODUCT_DESCRIPTION, skuDetailsJSON.optString("description"))
val type = skuDetailsJSON.optString("type")
params.putCharSequence(Constants.IAP_PRODUCT_TYPE, type)
if (type == "subs") {
params.putCharSequence(
Constants.IAP_SUBSCRIPTION_AUTORENEWING,
java.lang.Boolean.toString(purchaseJSON.optBoolean("autoRenewing", false)))
params.putCharSequence(
Constants.IAP_SUBSCRIPTION_PERIOD, skuDetailsJSON.optString("subscriptionPeriod"))
params.putCharSequence(
Constants.IAP_FREE_TRIAL_PERIOD, skuDetailsJSON.optString("freeTrialPeriod"))
val introductoryPriceCycles = skuDetailsJSON.optString("introductoryPriceCycles")
if (!introductoryPriceCycles.isEmpty()) {
params.putCharSequence(
Constants.IAP_INTRO_PRICE_AMOUNT_MICROS,
skuDetailsJSON.optString("introductoryPriceAmountMicros"))
params.putCharSequence(Constants.IAP_INTRO_PRICE_CYCLES, introductoryPriceCycles)
private fun getPurchaseLoggingParameters(
purchase: String,
skuDetails: String,
extraParameter: Map<String, String>
): PurchaseLoggingParameters? {
try {
val purchaseJSON = JSONObject(purchase)
val skuDetailsJSON = JSONObject(skuDetails)
val params = Bundle(1)
params.putCharSequence(
Constants.IAP_PRODUCT_ID,
purchaseJSON.getString(Constants.GP_IAP_PRODUCT_ID)
)
params.putCharSequence(
Constants.IAP_PURCHASE_TIME,
purchaseJSON.getString(Constants.GP_IAP_PURCHASE_TIME)
)
params.putCharSequence(
Constants.IAP_PURCHASE_TOKEN,
purchaseJSON.getString(Constants.GP_IAP_PURCHASE_TOKEN)
)
params.putCharSequence(
Constants.IAP_PACKAGE_NAME,
purchaseJSON.optString(Constants.GP_IAP_PACKAGE_NAME)
)
params.putCharSequence(
Constants.IAP_PRODUCT_TITLE,
skuDetailsJSON.optString(Constants.GP_IAP_TITLE)
)
params.putCharSequence(
Constants.IAP_PRODUCT_DESCRIPTION,
skuDetailsJSON.optString(Constants.GP_IAP_DESCRIPTION)
)
val type = skuDetailsJSON.optString(Constants.GP_IAP_TYPE)
params.putCharSequence(Constants.IAP_PRODUCT_TYPE, type)
if (type == InAppPurchaseUtils.IAPProductType.SUBS.type) {
params.putCharSequence(
Constants.IAP_SUBSCRIPTION_AUTORENEWING,
java.lang.Boolean.toString(
purchaseJSON.optBoolean(
Constants.GP_IAP_AUTORENEWING,
false
)
)
)
params.putCharSequence(
Constants.IAP_SUBSCRIPTION_PERIOD,
skuDetailsJSON.optString(Constants.GP_IAP_SUBSCRIPTION_PERIOD)
)
params.putCharSequence(
Constants.IAP_FREE_TRIAL_PERIOD,
skuDetailsJSON.optString(Constants.GP_IAP_FREE_TRIAL_PERIOD)
)
val introductoryPriceCycles =
skuDetailsJSON.optString(Constants.GP_IAP_INTRODUCTORY_PRICE_CYCLES)
if (introductoryPriceCycles.isNotEmpty()) {
params.putCharSequence(
Constants.IAP_INTRO_PRICE_AMOUNT_MICROS,
skuDetailsJSON.optString(Constants.GP_IAP_INTRODUCTORY_PRICE_AMOUNT_MICROS)
)
params.putCharSequence(
Constants.IAP_INTRO_PRICE_CYCLES,
introductoryPriceCycles
)
}
}
extraParameter.forEach { (k, v) -> params.putCharSequence(k, v) }
if (skuDetailsJSON.has(Constants.GP_IAP_PRICE_AMOUNT_MICROS_V2V4) && skuDetailsJSON.has(
Constants.GP_IAP_PRICE_CURRENCY_CODE_V2V4
)
) {
return PurchaseLoggingParameters(
BigDecimal(skuDetailsJSON.getLong(Constants.GP_IAP_PRICE_AMOUNT_MICROS_V2V4) / 1_000_000.0),
Currency.getInstance(skuDetailsJSON.getString(Constants.GP_IAP_PRICE_CURRENCY_CODE_V2V4)),
params
)
} else if (skuDetailsJSON.has(Constants.GP_IAP_ONE_TIME_PURCHASE_OFFER_DETAILS)) {
val oneTimePurchaseOfferDetailsJSON =
skuDetailsJSON.getJSONObject(Constants.GP_IAP_ONE_TIME_PURCHASE_OFFER_DETAILS)
return PurchaseLoggingParameters(
BigDecimal(oneTimePurchaseOfferDetailsJSON.getLong(Constants.GP_IAP_PRICE_AMOUNT_MICROS_V5V7) / 1_000_000.0),
Currency.getInstance(oneTimePurchaseOfferDetailsJSON.getString(Constants.GP_IAP_PRICE_CURRENCY_CODE_V5V7)),
params
)
} else {
return null
}
} catch (e: JSONException) {
Log.e(TAG, "Error parsing in-app purchase/subscription data.", e)
return null
} catch (e: Exception) {
Log.e(TAG, "Failed to get purchase logging parameters,", e)
return null
}
}
extraParameter.forEach { (k, v) -> params.putCharSequence(k, v) }
PurchaseLoggingParameters(
BigDecimal(skuDetailsJSON.getLong("price_amount_micros") / 1_000_000.0),
Currency.getInstance(skuDetailsJSON.getString("price_currency_code")),
params)
} catch (e: JSONException) {
Log.e(TAG, "Error parsing in-app subscription data.", e)
null
}
}

private class PurchaseLoggingParameters
internal constructor(var purchaseAmount: BigDecimal, var currency: Currency, var param: Bundle)
private class PurchaseLoggingParameters
internal constructor(var purchaseAmount: BigDecimal, var currency: Currency, var param: Bundle)
}
Loading

0 comments on commit 97e922b

Please sign in to comment.