Migration from Legacy AppCoins SDK to new Aptoide SDK

This page helps the developers to migrate from the legacy AppCoins Billing SDK to the newest Aptoide Billing SDK

Overview

This document provides a complete, step-by-step guide to migrate your application from the legacy AppCoins Billing SDK (io.catappult:android-appcoins-billing) to the latest Aptoide Billing SDK (com.aptoide:android-aptoide-billing). The new SDK introduces modernized APIs, cleaner structure, improved compatibility with Android features, and better alignment to the Billing standards in the industry.

Migration Steps Summary

The main steps to have a successful migration are the following:

  1. Update SDK Dependency
    Replace the AppCoins SDK dependency with the new Aptoide SDK in your Gradle files.

  2. Refactor Billing Client Initialization
    Replace AppcoinsBillingClient with AptoideBillingClient using the new builder pattern.

  3. Update Purchase Listener
    Update the PurchasesUpdatedListener to handle BillingResult instead of integer for the ResponseCode.

  4. Update Purchase Querying Logic
    Replace queryPurchases(SkuType) with queryPurchasesAsync(QueryPurchasesParams).

  5. Migrate Product Querying Logic
    Replace querySkuDetailsAsync with queryProductDetailsAsync, using QueryProductDetailsParams to define the type of Products to be searched and receive ProductDetails instead of SkuDetails.

  6. Update Purchase Flow
    Replace direct SKU-based purchases with BillingFlowParams using ProductDetailsParams.

  7. Refactor Consumption Logic
    Replace the old consumeAsync(token) method with a new method that uses ConsumeParams.


1. Update SDK Dependency

AppCoins SDK:

implementation("io.catappult:android-appcoins-billing:0.9.+")

Aptoide SDK:

implementation("com.aptoide:android-aptoide-billing:1.+")

Update your build.gradle and sync the project.
Once the project is synced, update all the imports from the Legacy AppCoins Billing SDK to the new Aptoide Billing SDK by replacing appcoins with aptoide, as per example:

Legacy Imports:

import com.appcoins.sdk.billing.Purchase

New Imports:

import com.aptoide.sdk.billing.Purchase

2. Refactor Billing Client Initialization

Legacy Initialization:

val cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(context, publicKey, listener)  
cab.startConnection(appCoinsBillingStateListener)
CatappultAppcoinsBilling cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(context, publicKey, listener)  
cab.startConnection(appCoinsBillingStateListener)

New Initialization:

val billingClient = AptoideBillingClient.newBuilder(context)  
  .setListener(purchasesUpdatedListener)  
  .setPublicKey(publicKey)  
  .build()

billingClient.startConnection(aptoideBillingClientStateListener)
AptoideBillingClient billingClient = AptoideBillingClient.newBuilder(context)
  .setListener(purchasesUpdatedListener)
  .setPublicKey(publicKey)
  .build();

billingClient.startConnection(aptoideBillingClientStateListener);

Changes Required:

  • Replace AppcoinsBillingClient with AptoideBillingClient
  • Replace AppCoinsBillingStateListener with AptoideBillingClientStateListener
  • Use BillingResult instead of raw integers for response codes

3. Update Purchase Listener

Legacy:

val purchasesUpdatedListener = PurchasesUpdatedListener { responseCode, purchases ->
    if (responseCode == ResponseCode.OK.value) {
        for (purchase in purchases) {
            // Apply here your Purchase result logic
        }
    }
}
PurchasesUpdatedListener purchasesUpdatedListener = (responseCode, purchases) -> {
    if (responseCode == ResponseCode.OK.getValue()) {
        for (Purchase purchase : purchases) {
            // Apply here your Purchase result logic
        }
    }
};

New:

val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
    if (billingResult.responseCode == BillingResponseCode.OK) {
        for (purchase in purchases) {
            // Apply here your Purchase result logic
        }
    }
}
PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
    if (billingResult.getResponseCode() == BillingResponseCode.OK) {
        for (Purchase purchase : purchases) {
            // Apply here your Purchase result logic
        }
    }
};

Highlights:

  • Response codes now come from billingResult.responseCode
  • Maintain same logic to extract token and handle delivery/consumption

4. Update Purchase Querying Logic

Legacy:

val purchasesResult = cab.queryPurchases(SkuType.inapp)
PurchasesResult purchasesResult = cab.queryPurchases(SkuType.inapp)

New:

val params = QueryPurchasesParams.newBuilder()
    .setProductType(ProductType.INAPP)
    .build()

billingClient.queryPurchasesAsync(params) { billingResult, purchases ->
    // Handle results
}
QueryPurchasesParams params = QueryPurchasesParams.newBuilder()
    .setProductType(ProductType.INAPP)
    .build();

billingClient.queryPurchasesAsync(params, (billingResult, purchases) -> {
    // Handle results
});

5. Migrate Product Query Logic

Legacy:

cab.querySkuDetailsAsync(SkuDetailsParams(...), listener)
cab.querySkuDetailsAsync(new SkuDetailsParams(...), listener)

New:

val params = QueryProductDetailsParams.newBuilder()
    .setProductList(
        listOf(
            Product.newBuilder()
                .setProductId("your_product_id")
                .setProductType(ProductType.INAPP)
                .build()
        )
    ).build()

billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsResult ->
    if (billingResult.responseCode == BillingResponseCode.OK) {
        for (product in productDetailsResult.productDetailsList) {
            // Use product details here
        }
    }
}
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
    .setProductList(
        List.of(
            Product.newBuilder()
                .setProductId("your_product_id")
                .setProductType(ProductType.INAPP)
                .build()
        )
    ).build();

billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsResult) -> {
    if (billingResult.getResponseCode() == BillingResponseCode.OK) {
        for (ProductDetails product : productDetailsResult.getProductDetailsList()) {
            // Use product details here
        }
    }
});

Notes:

  • Replace SkuDetailsParams with QueryProductDetailsParams
  • Replace SkuType with ProductType

6. Update Purchase Flow

Legacy:

val billingFlowParams = BillingFlowParams(...)  
cab.launchBillingFlow(activity, billingFlowParams)
BillingFlowParams billingFlowParams = new BillingFlowParams(...);
cab.launchBillingFlow(activity, billingFlowParams);

New:

val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
    .setProductDetails(productDetails)
    .build()

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(listOf(productDetailsParams))
    .setObfuscatedAccountId(userId)
    .setFreeTrial(true)
    .build()

billingClient.launchBillingFlow(activity, billingFlowParams)
BillingFlowParams.ProductDetailsParams productParams =
    BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .build();

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(List.of(productParams))
    .setObfuscatedAccountId(userId)
    .setFreeTrial(true) // Use this to initiate Free Trials
    .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Key Changes:

  • Uses ProductDetails instead of raw SKU strings
  • Adds setObfuscatedAccountId() for user-level tracking
  • setFreeTrial() handles trial logic

7. Refactor Consumption Logic

Legacy:

cab.consumeAsync(purchase.token, consumeResponseListener)
cab.consumeAsync(purchase.getToken(), consumeResponseListener);

New:

val consumeParams = ConsumeParams.newBuilder()
    .setPurchaseToken(purchase.purchaseToken)
    .build()

billingClient.consumeAsync(consumeParams, consumeResponseListener)
ConsumeParams consumeParams = ConsumeParams.newBuilder()
    .setPurchaseToken(purchase.getPurchaseToken())
    .build();

billingClient.consumeAsync(consumeParams, consumeResponseListener);

Important:

  • Always consume purchases within 48 hours
  • Subscriptions should also be consumed or acknowledged (via Backend API)

Changes Comparison Table

FeatureLegacy AppCoins SDKNew Aptoide SDK
Billint Client ClassAppcoinsBillingClientAptoideBillingClient
Querying ProductsquerySkuDetailsAsyncqueryProductDetailsAsync
Purchase Flow ParamsBillingFlowParams with SKUBillingFlowParams with ProductDetails
Billing action resultInt values as ResponseCodeUses BillingResult which contains BillingResponseCode and Debug Message
ConsumptionconsumeAsync(token)consumeAsync(ConsumeParams)

FAQ

Where can I find the entire Aptoide Billing SDK Integration documentation?

Our entire documentation for the new Aptoide Billing SDK Integration can be found in here. It contains the most important steps to have a complete and successful integration of our Billing system.


Where can I find the legacy AppCoins Billing SDK Integration documentation?

Our documentation for the legacy Aptoide Billing SDK Integration can be found in here.