Skip to content

Commit

Permalink
Merge pull request #5 from hansemannn/feature/android-billing-v6
Browse files Browse the repository at this point in the history
feat: use In App Billing v7
  • Loading branch information
hansemannn committed Jul 10, 2024
2 parents 63f11c3 + f7b038f commit b2cbb32
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 44 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Titanium In-App-Purchasing
# Titanium In-App-Purchases

Support for native cross-platform in-app-purchasing API's in Titanium.
Support for native cross-platform in-app-purchase API's in Titanium.
This repository represents a modern alternative to ti.storekit (iOS) and ti.inappbilling (Android).

## Versions

- Android: In App Billing v6 (6.0.1)
- iOS: SwiftyStoreKit 0.16.4

## Requirements

- [x] iOS 11+
- [x] Android 5+
- [x] Titanium SDK 9.2.0+

## Example
Expand Down
8 changes: 0 additions & 8 deletions android/Resources/README.md

This file was deleted.

3 changes: 1 addition & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
apply plugin: "kotlin-android"
apply plugin: "kotlin-android-extensions"

dependencies {
implementation "androidx.core:core-ktx:1.3.0"

def inapp_version = "5.0.0"
def inapp_version = "7.0.0"
implementation "com.android.billingclient:billing:${inapp_version}"
implementation "com.android.billingclient:billing-ktx:${inapp_version}"
}
Expand Down
2 changes: 1 addition & 1 deletion android/manifest
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# this is your module manifest and used by Titanium
# during compilation, packaging, distribution, etc.
#
version: 3.0.2
version: 4.0.0
apiversion: 4
architectures: arm64-v8a armeabi-v7a x86 x86_64
description: titanium-in-app-purchase
Expand Down
61 changes: 37 additions & 24 deletions android/src/ti/iap/TitaniumInAppPurchaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@

package ti.iap

import android.util.Log
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode.*
import com.android.billingclient.api.BillingClient.FeatureType.*
import com.android.billingclient.api.BillingClient.SkuType.INAPP
import com.android.billingclient.api.BillingClient.SkuType.SUBS
import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams
import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams.ReplacementMode
import com.android.billingclient.api.Purchase.PurchaseState.*
import org.appcelerator.kroll.KrollDict
import org.appcelerator.kroll.KrollFunction
import org.appcelerator.kroll.common.Log
import org.appcelerator.kroll.KrollModule
import org.appcelerator.kroll.KrollProxy
import org.appcelerator.kroll.annotations.Kroll
Expand All @@ -28,10 +30,7 @@ import ti.iap.handlers.ProductsHandler
import ti.iap.handlers.PurchaseHandler
import ti.iap.helper.QueryHandler
import ti.iap.models.PurchaseModel
import java.lang.Error
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine


@Kroll.module(name = "TitaniumInAppPurchase", id = "ti.iap")
class TitaniumInAppPurchaseModule : KrollModule() {
Expand Down Expand Up @@ -62,13 +61,20 @@ class TitaniumInAppPurchaseModule : KrollModule() {
@Kroll.constant const val CODE_ITEM_NOT_OWNED = ITEM_NOT_OWNED // "Failure to consume since item is not owned."
@Kroll.constant const val CODE_ITEM_UNAVAILABLE = ITEM_UNAVAILABLE // "Requested product is not available for purchase."
@Kroll.constant const val CODE_SERVICE_DISCONNECTED = SERVICE_DISCONNECTED // "Play Store service is not connected now - potentially transient state."
@Kroll.constant const val CODE_SERVICE_TIMEOUT = SERVICE_TIMEOUT // "The request has reached the maximum timeout before Google Play responds."
@Kroll.constant const val CODE_SERVICE_UNAVAILABLE = SERVICE_UNAVAILABLE // "Network connection is down."
@Kroll.constant const val CODE_NETWORK_ERROR = NETWORK_ERROR // "Network connection is down."
@Kroll.constant const val CODE_USER_CANCELED = USER_CANCELED // "User pressed back or canceled dialog."
@Kroll.constant const val CODE_ERROR = ERROR // "Fatal error during the API action."
@Kroll.constant const val CODE_OK = OK
@Kroll.constant const val CODE_BILLING_NOT_READY = 100 // "Billing library not ready"
@Kroll.constant const val CODE_SKU_NOT_AVAILABLE = 101 // "SKU details not available for making purchase"

@Kroll.constant const val REPLACEMENT_MODE_CHARGE_FULL_PRICE = ReplacementMode.CHARGE_FULL_PRICE
@Kroll.constant const val REPLACEMENT_MODE_UNKNOWN_REPLACEMENT_MODE = ReplacementMode.UNKNOWN_REPLACEMENT_MODE
@Kroll.constant const val REPLACEMENT_MODE_CHARGE_PRORATED_PRICE = ReplacementMode.CHARGE_PRORATED_PRICE
@Kroll.constant const val REPLACEMENT_MODE_WITHOUT_PRORATION = ReplacementMode.WITHOUT_PRORATION
@Kroll.constant const val REPLACEMENT_MODE_WITH_TIME_PRORATION = ReplacementMode.WITH_TIME_PRORATION
@Kroll.constant const val REPLACEMENT_MODE_DEFERRED = ReplacementMode.DEFERRED
}

@Kroll.method
Expand Down Expand Up @@ -116,20 +122,7 @@ class TitaniumInAppPurchaseModule : KrollModule() {

@Kroll.method
fun launchPriceChangeConfirmationFlow(args: KrollDict) {
val callback = args[IAPConstants.Properties.CALLBACK] as KrollFunction?
val productId = args[IAPConstants.PurchaseModelKeys.PRODUCT_ID] as String
val skuDetails = ProductsHandler.getSkuDetails(productId) ?: return

org.appcelerator.kroll.common.Log.w("Ti.IAP", "The \"launchPriceChangeConfirmationFlow\" method has been deprecated by Google and may be removed in the future.")

val params = PriceChangeFlowParams.newBuilder().setSkuDetails(skuDetails).build()
billingClient?.launchPriceChangeConfirmationFlow(TiApplication.getInstance().rootOrCurrentActivity, params) { billingResult ->
val event = KrollDict()
event[IAPConstants.Properties.SUCCESS] = billingResult.responseCode == OK
event[IAPConstants.Properties.CODE] = billingResult.responseCode

callback?.callAsync(getKrollObject(), event)
}
Log.e("Ti.IAP", "The \"launchPriceChangeConfirmationFlow\" method was removed in In App Billing v6. Use the properties \"oldPurchaseToken\", \"subscriptionReplacementMode\" and \"originalExternalTransactionId\" in \"purchase()\" instead!")
}

private fun isBillingLibraryReady(args: KrollDict? = null): Boolean {
Expand Down Expand Up @@ -179,15 +172,35 @@ class TitaniumInAppPurchaseModule : KrollModule() {
}

@Kroll.method
fun purchase(productId: String): Int {
fun purchase(params: KrollDict): Int {
if (!isBillingLibraryReady()) {
return CODE_BILLING_NOT_READY
}

val productId = params.getString("identifier")
val oldPurchaseToken = params.optString("oldPurchaseToken", null)
val subscriptionReplacementMode = params.optInt("subscriptionReplacementMode", -1)
val originalExternalTransactionId = params.optString("originalExternalTransactionId", null)

val skuDetails = ProductsHandler.getSkuDetails(productId) ?: return CODE_SKU_NOT_AVAILABLE
var flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails)

// Handle subscription updates
if (oldPurchaseToken != null) {
var params = SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken)

if (subscriptionReplacementMode != -1) {
params = params.setSubscriptionReplacementMode(subscriptionReplacementMode);
}

if (originalExternalTransactionId != null) {
params = params.setOriginalExternalTransactionId(originalExternalTransactionId);
}

flowParams = flowParams.setSubscriptionUpdateParams(params.build())
}

val flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build()
val launchBillingResult = billingClient!!.launchBillingFlow(TiApplication.getAppCurrentActivity(), flowParams)
val launchBillingResult = billingClient!!.launchBillingFlow(TiApplication.getAppCurrentActivity(), flowParams.build())

return launchBillingResult.responseCode
}
Expand Down Expand Up @@ -240,7 +253,7 @@ class TitaniumInAppPurchaseModule : KrollModule() {

@Kroll.method
fun queryPurchasesAsync(args: KrollDict) {
org.appcelerator.kroll.common.Log.e("Ti.IAP", "The \"queryPurchasesAsync\" API has been removed, use \"queryPurchases\" instead!")
Log.e("Ti.IAP", "The \"queryPurchasesAsync\" API has been removed. Use \"queryPurchases\" instead!")
}

@Kroll.method
Expand Down
4 changes: 4 additions & 0 deletions android/src/ti/iap/handlers/ProductsHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.android.billingclient.api.SkuDetailsResponseListener
import org.appcelerator.kroll.KrollDict
import org.appcelerator.kroll.KrollFunction
import org.appcelerator.kroll.KrollObject
import org.appcelerator.kroll.common.Log
import ti.iap.IAPConstants
import ti.iap.models.SkuModel

Expand All @@ -17,6 +18,9 @@ class ProductsHandler(private val callback: KrollFunction?, private val krollObj
@JvmStatic fun getSkuDetails(productId: String): SkuDetails? {
var skuDetails: SkuDetails? = null

Log.w("IAP", productId);
Log.w("IAP", skuList.toString());

for (skuModel in skuList) {
if (skuModel.skuDetails.sku == productId) {
skuDetails = skuModel.skuDetails
Expand Down
13 changes: 8 additions & 5 deletions example/iap-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const InAppPurchaseProduct = {

let purchaseResolver;
let purchaseRejecter;
let tripId;
let selectedProduct;

let isBillingInitialized = false;
Expand Down Expand Up @@ -88,9 +87,7 @@ export default class InAppPurchaseManager {
}
}

static async purchase(product, _tripId) {
tripId = _tripId;

static async purchase(product) {
Ti.API.warn(`** Purchasing in app product (${product.identifier}) **`);

return new Promise((resolve, reject) => {
Expand All @@ -105,7 +102,13 @@ export default class InAppPurchaseManager {

selectedProduct = product;

IAP.purchase(product.identifier); // TODO: Use a callback here as well?
IAP.purchase({
identifier: product.identifier,
// Optional: pass subscription update parameters
// oldPurchaseToken: '',
// subscriptionReplacementMode: IAP.REPLACEMENT_MODE_CHARGE_FULL_PRICE,
// originalExternalTransactionId: ''
});
} else {
IAP.purchase({
identifier: product.identifier,
Expand Down

0 comments on commit b2cbb32

Please sign in to comment.