Skip to content

Commit

Permalink
Release/1.3.1 (#160)
Browse files Browse the repository at this point in the history
* catch connectivity exceptions (#157)

* webview crash handler (#158)

* webview crash handler

* pr updates

* Feature/inapppurchase (#159)

* Dev/purchase (#118)

* Dev/volatile data (#114)

* wip

* tests

* Dev/visitor fix (#115)

* visitor profile fix

* vs version

* Dev/bug fixes (#116)

* WebView Consent Sync sample

* Fixes for visitor Id, app data, consent logging profile

* Collect consent logging with batching enabled

* Update per code review feedback

Co-authored-by: Karen Tamayo <[email protected]>

* ConsentManager cleanup + test fixes

* In app purchase tracking

* Latest WIP

* Cleanup

* WIP - code cleanup

* In App Purchase module

* Purchase updates + sample app (#119)

Co-authored-by: Karen Tamayo <[email protected]>

Co-authored-by: James Keith <[email protected]>
Co-authored-by: Karen Tamayo <[email protected]>

* Cleanup module

* Tracker startConnection

Co-authored-by: James Keith <[email protected]>
Co-authored-by: Karen Tamayo <[email protected]>

* remove error count config

* inapppurchase proguard

* test timeouts

Co-authored-by: Karen Tamayo <[email protected]>
Co-authored-by: Karen Tamayo <[email protected]>
  • Loading branch information
3 people authored Feb 16, 2022
1 parent 82c5382 commit b9f313e
Show file tree
Hide file tree
Showing 33 changed files with 892 additions and 28 deletions.
1 change: 1 addition & 0 deletions inapppurchase/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
92 changes: 92 additions & 0 deletions inapppurchase/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'maven-publish'
}

version = '1.0.0'

android {
compileSdkVersion 30
buildToolsVersion "30.0.3"

defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
buildConfigField 'String', 'TAG', "\"Tealium-InAppPurchaseTracker-$version\""
buildConfigField 'String', 'LIBRARY_VERSION', "\"$version\""

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}

debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
testCoverageEnabled true
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}

testOptions {
unitTests.returnDefaultValues = true
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api project(":tealiumlibrary")
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.android.billingclient:billing:4.0.0'

testImplementation 'androidx.test:core:1.2.0'
testImplementation 'androidx.arch.core:core-testing:2.1.0'
testImplementation "io.mockk:mockk:$mockk_version"
testImplementation "org.robolectric:robolectric:4.3.1"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.5'
testImplementation 'junit:junit:4.+'

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
groupId = 'com.tealium'
artifactId = 'kotlin-inapp-purchase'
}
}
repositories {
// maven {
// url "s3://maven.tealiumiq.com/android/releases/"
// credentials(AwsCredentials) {
// accessKey AWS_ACCESS_KEY
// secretKey AWS_SECRET_KEY
// sessionToken AWS_SESSION_TOKEN
// }
// }
}
}
}
Empty file.
35 changes: 35 additions & 0 deletions inapppurchase/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
-keepparameternames
-keeppackagenames
-renamesourcefileattribute SourceFile
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

-keep class kotlin.Metadata { *; }

# Keep all public interfaces
-keep public interface com.tealium.** { *; }
-keep class com.tealium.inapppurchase.InAppPurchaseManager,
com.tealium.inapppurchase.InAppPurchaseManager$Companion,
com.tealium.inapppurchase.InAppPurchaseManagerKt{
*;
}
5 changes: 5 additions & 0 deletions inapppurchase/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tealium.inapppurchase">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.tealium.inapppurchase

import android.app.Activity
import com.android.billingclient.api.*
import com.tealium.core.Logger
import com.tealium.core.TealiumContext
import com.tealium.dispatcher.TealiumEvent

class InAppPurchaseAutoTracker(
private val context: TealiumContext,
private val purchaseListener: PurchasesUpdatedListener = PurchaseListener(),
private var billingClient: BillingClient? = null
) : InAppPurchaseTracker {

private fun startConnection() {
if (billingClient == null) {
billingClient = BillingClient.newBuilder(context.config.application)
.setListener(purchaseListener)
.enablePendingPurchases()
.build()
}

if (billingClient?.isReady == true) {
billingClient?.startConnection(this)
}
}

private fun endConnection() {
billingClient?.endConnection()
billingClient = null
}

override fun onBillingSetupFinished(billingResult: BillingResult) {
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
Logger.dev(BuildConfig.TAG, "Connection established to BillingClient")
}
else -> {
Logger.dev(BuildConfig.TAG, "Unable to connect: BillingClient is not available")
}
}
}

override fun onBillingServiceDisconnected() {
startConnection()
}

override fun trackInAppPurchase(purchaseItem: Purchase, data: Map<String, Any>?) {
val purchaseData = mutableMapOf<String, Any>(
"autotracked" to true,
PURCHASE_ORDER_ID to purchaseItem.orderId,
PURCHASE_TIMESTAMP to purchaseItem.purchaseTime,
PURCHASE_QUANTITY to purchaseItem.quantity,
PURCHASE_SKUS to purchaseItem.skus,
PURCHASE_AUTORENEWING to purchaseItem.isAutoRenewing,
PURCHASE_STATE to purchaseItem.purchaseState
)

data?.let {
purchaseData.putAll(it)
}

context.track(TealiumEvent(IN_APP_PURCHASE_EVENT, purchaseData))
}

override fun onActivityPaused(activity: Activity?) {
// do nothing
}

override fun onActivityResumed(activity: Activity?) {
if (billingClient == null) {
startConnection()
}
}

override fun onActivityStopped(activity: Activity?, isChangingConfiguration: Boolean) {
billingClient?.let {
if (it.connectionState == BillingClient.ConnectionState.CLOSED) {
endConnection()
}
}
}

private companion object {
private const val IN_APP_PURCHASE_EVENT = "in_app_purchase"
const val PURCHASE_ORDER_ID = "purchase_order_id"
const val PURCHASE_TIMESTAMP = "purchase_timestamp"
const val PURCHASE_QUANTITY = "purchase_quantity"
const val PURCHASE_SKUS = "purchase_skus"
const val PURCHASE_AUTORENEWING = "purchase_is_auto_renewing"
const val PURCHASE_STATE = "purchase_state"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.tealium.inapppurchase

import com.android.billingclient.api.*
import com.tealium.core.*

class InAppPurchaseManager(
private val context: TealiumContext,
private val purchaseListener: PurchasesUpdatedListener = PurchaseListener(),
private val purchaseTracker: InAppPurchaseTracker = InAppPurchaseAutoTracker(context, purchaseListener)
) : Module {

override val name: String = MODULE_NAME
override var enabled: Boolean = true

// Manually track purchase
fun trackInAppPurchase(purchaseItem: Purchase, data: Map<String, Any>? = null) {
purchaseTracker.trackInAppPurchase(purchaseItem, data)
}

companion object : ModuleFactory {
const val MODULE_NAME = "InAppPurchaseManager"
const val MODULE_VERSION = BuildConfig.LIBRARY_VERSION

@Volatile private var instance: InAppPurchaseManager? = null
private val contexts = mutableListOf<TealiumContext>()

override fun create(context: TealiumContext): Module {
contexts.add(context)
return instance ?: synchronized(this) {
instance ?: InAppPurchaseManager(context).also {
instance = it
}
}
}
}
}

val Modules.InAppPurchaseManager: ModuleFactory
get() = com.tealium.inapppurchase.InAppPurchaseManager

val Tealium.inAppPurchaseManager: InAppPurchaseManager?
get() = modules.getModule(InAppPurchaseManager::class.java)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tealium.inapppurchase

import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.Purchase
import com.tealium.core.messaging.ActivityObserverListener

interface InAppPurchaseTracker: BillingClientStateListener, ActivityObserverListener {
fun trackInAppPurchase(purchaseItem: Purchase, data: Map<String, Any>? = null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.tealium.inapppurchase

import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesUpdatedListener
import com.tealium.core.Logger
import com.tealium.core.Tealium

class PurchaseListener: PurchasesUpdatedListener {
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
Tealium.names().forEach { instanceName ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchases?.forEach { purchaseItem ->
when(purchaseItem.purchaseState) {
Purchase.PurchaseState.PURCHASED -> {
Logger.dev(BuildConfig.TAG, "Tracking purchase with order id: ${purchaseItem.orderId}")

Tealium[instanceName]?.inAppPurchaseManager?.trackInAppPurchase(purchaseItem)
}
Purchase.PurchaseState.PENDING -> {
Logger.dev(BuildConfig.TAG, "Purchase pending for order id: ${purchaseItem.orderId}")
Tealium[instanceName]?.inAppPurchaseManager?.trackInAppPurchase(purchaseItem)
}
else -> {
Logger.dev(BuildConfig.TAG, "Unable to track purchase: ${purchaseItem.orderId}")
}
}
}
}

BillingClient.BillingResponseCode.USER_CANCELED -> {
Logger.dev(BuildConfig.TAG, "Unable to track purchase. User cancelled.")
}

BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
Logger.dev(BuildConfig.TAG, "Unable to track purchase. Item is already owned.")
}
else -> {
Logger.dev(BuildConfig.TAG, "Unable to track purchases")
}
}
}
}
}
Loading

0 comments on commit b9f313e

Please sign in to comment.