Skip to content

Commit

Permalink
Merge pull request #29 from eggheadgames/split-amazon-and-google-sdk
Browse files Browse the repository at this point in the history
Split Amazon and Google SDK
  • Loading branch information
mikemee authored Aug 13, 2020
2 parents d838992 + 2bb4b11 commit 3c7f0be
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 68 deletions.
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A simple wrapper library that provides sample Google and Amazon in-app purchase
* actively maintained by [Egghead Games](http://eggheadgames.com) for their cross-platform mobile/tablet apps ([quality brain puzzles with no ads](https://play.google.com/store/apps/dev?id=8905223606155014113)!)
* subscriptions for Google Play
* subscriptions for Amazon
* `appstore` flavor support in version 3.0 ensures that Amazon library is not included for Google

### Not supported:
* receipt validation (either local or server)
Expand All @@ -46,11 +47,26 @@ allprojects {
}
```

Add a dependency to your application related `build.gradle`
Add an `appstore` flavor to your application related `build.gradle` and the corresponding google and amazon dependencies.

```gradle
android {
flavorDimensions "appstore"
productFlavors {
google {
dimension "appstore"
}
amazon {
dimension "appstore"
}
}
dependencies {
compile 'com.github.eggheadgames:android-in-app-payments:<actual version>'
googleImplementation "com.github.eggheadgames:android-in-app-payments:x.y.z:google@aar"
googleImplementation 'com.android.billingclient:billing:3.0.0' // fixme: not sure why this is not automatic
amazonImplementation "com.github.eggheadgames:android-in-app-payments:x.y.z:amazon@aar"
}
```

Expand All @@ -59,7 +75,7 @@ dependencies {
The following code snippet initializes the billing module:

```java
IAPManager.build(context, IAPManager.BUILD_TARGET_GOOGLE, skuList /*can be ignored for Google traget*/);
IAPManager.build(context, skuList, ArrayList<>());
IAPManager.addPurchaseListener(new PurchaseServiceListener() {
@Override
public void onPricesUpdated(Map<String, String> map) {
Expand All @@ -81,18 +97,18 @@ The following code snippet initializes the billing module:

1. Setup billing module.
```
IAPManager.build(Context context, int buildTarget, List<String> skuList)
IAPManager.build(Context context, List<String> skuList, List<String> subscriptionSkuList)
```

`int buildTarget` can be either `IAPManager.BUILD_TARGET_GOOGLE` or `IAPManager.BUILD_TARGET_AMAZON`
`List<String> skuList` - a list of products to fetch information about (relevant only for `IAPManager.BUILD_TARGET_AMAZON`)
* `List<String> skuList` - a list of products to fetch information about
* `List<String> subscriptionSkuList` - a list of subscription products to fetch information about (or empty if none)

2. Request info about available and owned products
```
IAPManager.init(String rot13LicenseKey)
```

`String rot13LicenseKey` is relevant only for `IAPManager.BUILD_TARGET_GOOGLE`, and can be ignored for `IAPManager.BUILD_TARGET_AMAZON`. Note that this is the required Google License Key obtained from the app's Google Play console, after applying the [ROT 13 algorithm](https://en.wikipedia.org/wiki/ROT13).
`String rot13LicenseKey` is relevant only for the `google` flavor, and can be ignored for `IAPManager.BUILD_TARGET_AMAZON`. Note that this is the required Google License Key obtained from the app's Google Play console, after applying the [ROT 13 algorithm](https://en.wikipedia.org/wiki/ROT13).
You might choose to store the key as ROT-13 in your app to avoid casual decoding of the strings, however, this is not really secure, so you are advised to follow [Google's advice](https://developer.android.com/training/in-app-billing/preparing-iab-app.html) and then ROT-13 the key before passing it to the API:

> Security Recommendation: Google highly recommends that you do not hard-code the exact public license key string value as provided by Google Play. Instead, construct the whole public license key string at runtime from substrings or retrieve it from an encrypted store before passing it to the constructor. This approach makes it more difficult for malicious third parties to modify the public license key string in your APK file.
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ buildscript {
ext.kotlin_version = '1.3.72'
ext {
app = [
versionCode: 21,
versionName: "2.1.0"
versionCode: 22,
versionName: "3.0.0"
]

general = [
Expand Down
52 changes: 47 additions & 5 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ android {
versionName rootProject.ext.general.versionName
}

flavorDimensions "appstore"
productFlavors {
google {
dimension "appstore"
}
amazon {
dimension "appstore"
}
}

lintOptions {
warningsAsErrors true
// ok disables
Expand All @@ -32,12 +42,13 @@ android {
}

dependencies {
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.core:core-ktx:1.3.1"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

api files('libs/in-app-purchasing-2.0.76.jar')
api 'androidx.appcompat:appcompat:1.1.0'
api 'com.android.billingclient:billing:2.2.0'
amazonApi files('libs/in-app-purchasing-2.0.76.jar')

googleApi 'com.android.billingclient:billing:3.0.0'

testImplementation 'junit:junit:4.13'
testImplementation 'org.mockito:mockito-core:2.24.0'
Expand All @@ -60,4 +71,35 @@ staticAnalysis {

excludeFilter rootProject.file('library/findbugs_excludes.xml')
}
}
}

// the following code is used to force jitpack to build different builds for each flavour.
// If you change it, please make sure that it builds correctly, since billing library is used on the all Eggheadgames apps.
// START JITPACK ARTIFACTS CONFIG
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.javaDirectories
}

artifacts {
archives sourcesJar
}

if (android.productFlavors.size() > 0) {
android.libraryVariants.all { variant ->
if (variant.name.toLowerCase().contains("debug")) {
return
}

def bundleTask = tasks["bundle${variant.name.capitalize()}Aar"]

artifacts {
archives(bundleTask.archivePath) {
classifier variant.flavorName
builtBy bundleTask
name = project.name
}
}
}
}
// END JITPACK ARTIFACTS CONFIG
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.billing.amazon
package com.billing

import android.util.Log
import com.amazon.device.iap.PurchasingListener
import com.amazon.device.iap.PurchasingService
import com.amazon.device.iap.model.*

internal class AmazonBillingListener(private val amazonBillingService: AmazonBillingService) : PurchasingListener {
internal class AmazonBillingListener(private val amazonBillingService: BillingService) : PurchasingListener {

var mDebugLog = false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.billing.amazon
package com.billing

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.amazon.device.iap.PurchasingService
import com.billing.BillingService

class AmazonBillingService(val context: Context, private val iapKeys: List<String>) : BillingService() {
class BillingService(private val context: Context, private val inAppSkuKeys: List<String>,
private val subscriptionSkuKeys: List<String>) : IBillingService() {

private var mAmazonBillingListener: AmazonBillingListener? = null

override fun init(key: String?) {
mAmazonBillingListener = AmazonBillingListener(this)
PurchasingService.registerListener(context, mAmazonBillingListener)

iapKeys.splitMessages(MAX_SKU_LIMIT).forEach {
val keys: MutableList<String> = ArrayList()
keys.addAll(inAppSkuKeys)
keys.addAll(subscriptionSkuKeys)

keys.splitMessages(MAX_SKU_LIMIT).forEach {
PurchasingService.getProductData(it.toSet())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.billing.google
package com.billing

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import com.android.billingclient.api.*
import com.billing.BillingService

class GoogleBillingService2(val context: Context, private val inAppSkuKeys: List<String>, private val subscriptionSkuKeys: List<String>)
: BillingService(), PurchasesUpdatedListener, BillingClientStateListener, AcknowledgePurchaseResponseListener {
class BillingService(val context: Context, private val inAppSkuKeys: List<String>, private val subscriptionSkuKeys: List<String>)
: IBillingService(), PurchasesUpdatedListener, BillingClientStateListener, AcknowledgePurchaseResponseListener {

private lateinit var mBillingClient: BillingClient
private var decodedKey: String? = null
Expand Down Expand Up @@ -71,9 +70,11 @@ class GoogleBillingService2(val context: Context, private val inAppSkuKeys: List

private fun launchBillingFlow(activity: Activity, sku: String, type: String) {
sku.toSkuDetails(type) { skuDetails ->
val purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails).build()
mBillingClient.launchBillingFlow(activity, purchaseParams)
if (skuDetails != null) {
val purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails).build()
mBillingClient.launchBillingFlow(activity, purchaseParams)
}
}
}

Expand All @@ -99,12 +100,7 @@ class GoogleBillingService2(val context: Context, private val inAppSkuKeys: List
/**
* Called by the Billing Library when new purchases are detected.
*/
override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: List<Purchase>?) {
if (billingResult == null) {
Log.wtf(TAG, "onSkuDetailsResponse: null BillingResult")
return
}

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
log("onPurchasesUpdated: responseCode:$responseCode debugMessage: $debugMessage")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package com.billing.google

package com.billing

import android.text.TextUtils
import android.util.Base64
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.os.Handler
import android.os.Looper
import androidx.annotation.CallSuper

abstract class BillingService {
abstract class IBillingService {

private val purchaseServiceListeners: MutableList<PurchaseServiceListener> = mutableListOf()
private val subscriptionServiceListeners: MutableList<SubscriptionServiceListener> = mutableListOf()
Expand Down
25 changes: 5 additions & 20 deletions library/src/main/java/com/eggheadgames/inapppayments/IAPManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,25 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import com.billing.BillingService
import com.billing.IBillingService
import com.billing.PurchaseServiceListener
import com.billing.SubscriptionServiceListener
import com.billing.amazon.AmazonBillingService
import com.billing.google.GoogleBillingService2
import java.util.*

//Public front-end for IAP functionality.
object IAPManager {
const val BUILD_TARGET_GOOGLE = 0
const val BUILD_TARGET_AMAZON = 1

@SuppressLint("StaticFieldLeak")
private var mBillingService: BillingService? = null
private var mBillingService: IBillingService? = null

/**
* @param context - application context
* @param buildTarget - IAPManager.BUILD_TARGET_GOOGLE or IAPManager.BUILD_TARGET_AMAZON
* @param iapKeys - list of sku for purchases
* @param subscriptionKeys - list of sku for subscriptions
*/
@JvmStatic
fun build(context: Context, buildTarget: Int, iapKeys: List<String>, subscriptionKeys: List<String> = emptyList()) {
fun build(context: Context, iapKeys: List<String>, subscriptionKeys: List<String> = emptyList()) {
val contextLocal = context.applicationContext ?: context

//Build-specific initializations
if (buildTarget == BUILD_TARGET_GOOGLE) {
mBillingService = GoogleBillingService2(contextLocal, iapKeys, subscriptionKeys)

} else if (buildTarget == BUILD_TARGET_AMAZON) {
val keys: MutableList<String> = ArrayList()
keys.addAll(iapKeys)
keys.addAll(subscriptionKeys)
mBillingService = AmazonBillingService(contextLocal, keys)
}
mBillingService = BillingService(contextLocal, iapKeys, subscriptionKeys)
}

/**
Expand Down Expand Up @@ -92,7 +77,7 @@ object IAPManager {
}

@JvmStatic
fun getBillingService(): BillingService {
fun getBillingService(): IBillingService {
return mBillingService ?: let {
throw RuntimeException("Call IAPManager.build to initialize billing service")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.eggheadgames.inapppayments

import com.billing.amazon.AmazonBillingService
import com.billing.amazon.splitMessages
import com.billing.BillingService
import com.billing.splitMessages
import org.junit.Assert
import org.junit.Test

Expand All @@ -14,7 +14,7 @@ class AmazonSkuLimitTest {
iapKeys.add("sku_$i")
}

val splitMessages = iapKeys.splitMessages(AmazonBillingService.MAX_SKU_LIMIT)
val splitMessages = iapKeys.splitMessages(BillingService.MAX_SKU_LIMIT)

Assert.assertEquals(3, splitMessages.size)

Expand All @@ -30,7 +30,7 @@ class AmazonSkuLimitTest {
iapKeys.add("sku_$i")
}

val splitMessages = iapKeys.splitMessages(AmazonBillingService.MAX_SKU_LIMIT)
val splitMessages = iapKeys.splitMessages(BillingService.MAX_SKU_LIMIT)

Assert.assertEquals(1, splitMessages.size)

Expand All @@ -42,7 +42,7 @@ class AmazonSkuLimitTest {
fun checkSkuListSize_ShouldReturn0() {
val iapKeys = mutableListOf<String>()

val splitMessages = iapKeys.splitMessages(AmazonBillingService.MAX_SKU_LIMIT)
val splitMessages = iapKeys.splitMessages(BillingService.MAX_SKU_LIMIT)

Assert.assertEquals(0, splitMessages.size)
}
Expand Down
Loading

0 comments on commit 3c7f0be

Please sign in to comment.