From 6c611dd625e505a829ab79c1fa628ddbf19a7519 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 30 Jun 2025 13:27:38 -0500 Subject: [PATCH] rm Amazon in-app purchase tracking Amazon doesn't have an official API for SDKs to track IAP, as a result we have been using reflection to hook into internal API's. This is extremely fragile, it breaks on every patch version of Amazon's IAP library. Due to the fact the market is moving away IAP through app stores and Amazon's pullback on bring the Amazon store to other platforms (like Windows) the importance has shrunk so low the maintenance isn't worth it anymore. --- .../java/com/onesignal/core/CoreModule.kt | 2 - .../purchases/impl/TrackAmazonPurchase.kt | 248 ------------------ 2 files changed, 250 deletions(-) delete mode 100644 OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackAmazonPurchase.kt diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt index 6d36caaf9f..9083cddade 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt @@ -29,7 +29,6 @@ import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.permissions.impl.RequestPermissionService import com.onesignal.core.internal.preferences.IPreferencesService import com.onesignal.core.internal.preferences.impl.PreferencesService -import com.onesignal.core.internal.purchases.impl.TrackAmazonPurchase import com.onesignal.core.internal.purchases.impl.TrackGooglePurchase import com.onesignal.core.internal.startup.IStartableService import com.onesignal.core.internal.time.ITime @@ -80,7 +79,6 @@ internal class CoreModule : IModule { .provides() // Purchase Tracking - builder.register().provides() builder.register().provides() // Register dummy services in the event they are not configured. These dummy services diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackAmazonPurchase.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackAmazonPurchase.kt deleted file mode 100644 index ab53a3a06c..0000000000 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackAmazonPurchase.kt +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Modified MIT License - * - * Copyright 2017 OneSignal - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * 1. The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * 2. All copies of substantial portions of the Software may only be used in connection - * with services provided by OneSignal. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.onesignal.core.internal.purchases.impl - -import com.amazon.device.iap.PurchasingListener -import com.amazon.device.iap.PurchasingService -import com.amazon.device.iap.model.ProductDataResponse -import com.amazon.device.iap.model.PurchaseResponse -import com.amazon.device.iap.model.PurchaseUpdatesResponse -import com.amazon.device.iap.model.RequestId -import com.amazon.device.iap.model.UserDataResponse -import com.onesignal.common.threading.suspendifyOnMain -import com.onesignal.core.internal.application.IApplicationLifecycleHandler -import com.onesignal.core.internal.application.IApplicationService -import com.onesignal.core.internal.config.ConfigModelStore -import com.onesignal.core.internal.operations.IOperationRepo -import com.onesignal.core.internal.startup.IStartableService -import com.onesignal.debug.internal.logging.Logging -import com.onesignal.user.internal.identity.IdentityModelStore -import com.onesignal.user.internal.operations.PurchaseInfo -import com.onesignal.user.internal.operations.TrackPurchaseOperation -import java.lang.reflect.Field -import java.lang.reflect.InvocationTargetException -import java.math.BigDecimal - -internal class TrackAmazonPurchase( - private val _applicationService: IApplicationService, - private val _operationRepo: IOperationRepo, - private val _configModelStore: ConfigModelStore, - private val _identityModelStore: IdentityModelStore, -) : IStartableService, IApplicationLifecycleHandler { - private var canTrack = false - private var osPurchasingListener: OSPurchasingListener? = null - private var listenerHandlerObject: Any? = null - private var listenerHandlerField: Field? = null - - // appstore v3.x requires PurchasingService.registerListener() to run on main UI thread - private var registerListenerOnMainThread = false - - override fun start() { - if (!canTrack()) { - return - } - - try { - // 2.0.1 - val listenerHandlerClass = Class.forName("com.amazon.device.iap.internal.d") - try { - // iap v2.x - listenerHandlerObject = listenerHandlerClass.getMethod("d").invoke(null) - } catch (err2x: NullPointerException) { - // iap v3.x - try { - // appstore v3.0.1 - v3.0.3 - listenerHandlerObject = listenerHandlerClass.getMethod("e").invoke(null) - registerListenerOnMainThread = true - } catch (err303: NullPointerException) { - try { - // appstore v3.0.4 - listenerHandlerObject = listenerHandlerClass.getMethod("g").invoke(null) - registerListenerOnMainThread = true - } catch (err304: NoSuchMethodException) { - // appstore v3.0.5 - listenerHandlerObject = listenerHandlerClass.getMethod("f").invoke(null) - registerListenerOnMainThread = true - } - } - } - val locListenerHandlerField = listenerHandlerClass.getDeclaredField("f") - locListenerHandlerField.isAccessible = true - osPurchasingListener = OSPurchasingListener(_operationRepo, _configModelStore, _identityModelStore) - osPurchasingListener!!.orgPurchasingListener = locListenerHandlerField.get(listenerHandlerObject) as PurchasingListener? - - listenerHandlerField = locListenerHandlerField - canTrack = true - setListener() - // Can replace all catches with ReflectiveOperationException win min API is 19 - } catch (e: ClassNotFoundException) { - logAmazonIAPListenerError(e) - } catch (e: IllegalAccessException) { - logAmazonIAPListenerError(e) - } catch (e: InvocationTargetException) { - logAmazonIAPListenerError(e) - } catch (e: NoSuchMethodException) { - logAmazonIAPListenerError(e) - } catch (e: NoSuchFieldException) { - logAmazonIAPListenerError(e) - } catch (e: ClassCastException) { - logAmazonIAPListenerError(e) - } - - _applicationService.addApplicationLifecycleHandler(this) - } - - private fun logAmazonIAPListenerError(e: Exception) { - Logging.error("Error adding Amazon IAP listener.", e) - e.printStackTrace() - } - - override fun onFocus(firedOnSubscribe: Boolean) { } - - override fun onUnfocused() { - if (!canTrack) return - try { - val curPurchasingListener = - listenerHandlerField!!.get(listenerHandlerObject) as PurchasingListener? - if (curPurchasingListener !== osPurchasingListener) { - osPurchasingListener!!.orgPurchasingListener = curPurchasingListener - setListener() - } - } catch (e: IllegalAccessException) { - e.printStackTrace() - } - } - - private fun setListener() { - if (registerListenerOnMainThread) { - suspendifyOnMain { - PurchasingService.registerListener(_applicationService.appContext, osPurchasingListener) - } - } else { - PurchasingService.registerListener(_applicationService.appContext, osPurchasingListener) - } - } - - private inner class OSPurchasingListener( - private val _operationRepo: IOperationRepo, - private val _configModelStore: ConfigModelStore, - private val _identityModelStore: IdentityModelStore, - ) : PurchasingListener { - var orgPurchasingListener: PurchasingListener? = null - private var lastRequestId: RequestId? = null - private var currentMarket: String? = null - - private fun marketToCurrencyCode(market: String?): String { - when (market) { - "US" -> return "USD" - "GB" -> return "GBP" - "DE", "FR", "ES", "IT" -> return "EUR" - "JP" -> return "JPY" - "CA" -> return "CDN" - "BR" -> return "BRL" - "AU" -> return "AUD" - } - return "" - } - - override fun onProductDataResponse(response: ProductDataResponse) { - if (lastRequestId != null && lastRequestId.toString() == response.requestId.toString()) { - when (response.requestStatus) { - ProductDataResponse.RequestStatus.SUCCESSFUL -> { - val purchasesToReport = mutableListOf() - val products = response.productData - var amountSpent = BigDecimal(0) - for (key in products.keys) { - val product = products[key] - val sku = product!!.sku - val iso = marketToCurrencyCode(currentMarket) - var priceStr = product.price - - if (!priceStr.matches("^[0-9]".toRegex())) { - priceStr = priceStr.substring(1) - } - - val price = BigDecimal(priceStr) - - amountSpent += price - purchasesToReport.add(PurchaseInfo(sku, iso, price)) - } - - _operationRepo.enqueue( - TrackPurchaseOperation( - _configModelStore.model.appId, - _identityModelStore.model.onesignalId, - false, - amountSpent, - purchasesToReport, - ), - ) - } - else -> { } - } - } else if (orgPurchasingListener != null) { - orgPurchasingListener!!.onProductDataResponse( - response, - ) - } - } - - override fun onPurchaseResponse(response: PurchaseResponse) { - val status = response.requestStatus - if (status == PurchaseResponse.RequestStatus.SUCCESSFUL) { - currentMarket = response.userData.marketplace - val productSkus: MutableSet = HashSet() - productSkus.add(response.receipt.sku) - lastRequestId = PurchasingService.getProductData(productSkus) - } - if (orgPurchasingListener != null) orgPurchasingListener!!.onPurchaseResponse(response) - } - - override fun onPurchaseUpdatesResponse(response: PurchaseUpdatesResponse) { - if (orgPurchasingListener != null) { - orgPurchasingListener!!.onPurchaseUpdatesResponse( - response, - ) - } - } - - override fun onUserDataResponse(response: UserDataResponse) { - if (orgPurchasingListener != null) orgPurchasingListener!!.onUserDataResponse(response) - } - } - - companion object { - fun canTrack(): Boolean { - try { - Class.forName("com.amazon.device.iap.PurchasingListener") - return true - } catch (e: ClassNotFoundException) { - } - return false - } - } -}