メインコンテンツまでスキップ

Android Billing SDK の統合

Aptoide Billing SDK を使用すると、AptoideBillingClient を通じて、Android アプリでアプリ内商品およびサブスクリプションを販売できます。このページでは、クライアントの接続、商品の照会、購入フローの開始、購入の処理と消費という統合の全体について説明します。

最初の目標は、AptoideBillingClient をインスタンス化して接続することです。接続後、この Billing クライアントを使用して、Aptoide Connect に登録された商品の取得、購入の開始、購入の処理を行うことができます。
したがって、実装は次の 4 つのステップで構成されます。

  1. Android Billing SDK との接続を設定する。
  2. アプリ内商品を照会する。
  3. 購入フローを開始する。
  4. 購入を処理し、アイテムをユーザーに付与し、購入を消費する。
📘
開発者向けツール

Android Studio プラグイン: ネイティブ Android Billing SDK の実装を支援するため、ステップごとに案内する Android Studio プラグインを開発しました。このプラグインはこちらからダウンロードでき、このプラグインの詳細についてはこちらをご覧ください。

実装例: 参考として使用できる実装例をこちらに用意しています。これは本番環境レベルではないことにご注意ください。

1. Android Billing SDK との接続を設定する

Aptoide Connect をインスタンス化して接続する前に、Android Billing SDK を使用できるように、アプリケーションに依存関係と権限を追加する必要があります。

依存関係と権限

プロジェクトの build.gradle に、次のリポジトリが含まれていることを確認してください。

allprojects {
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
}

アプリの build.gradle に、依存関係として Android Billing SDK を追加します。最新バージョンを取得するには、次のリンクを確認してください。android-aptoide-billing

dependencies {
implementation("com.aptoide:android-aptoide-billing:1.+") //check the latest version in mvnrepository
<...other dependencies..>
}

サービス接続の開始

依存関係をすべて追加したら、AptoideBillingClient のインスタンスを初期化する必要があります。これは、Android Billing SDK との通信に使用されるインスタンスです。常にアクティブなインスタンスは 1 つだけにし、アプリケーションの初期化時に行ってください。
Billing クライアントを初期化して接続を開始するには、クライアントの初期化に PurchasesUpdatedListener が必要であり、接続を開始するには AptoideBillingClientStateListener が必要です。このセクションでは、必要な 2 つのインスタンスを作成する方法と、AptoideBillingClient をインスタンス化して接続する方法について説明します。PurchasesUpdatedListener については、ステップ 4 で詳しく説明します。

Aptoide Wallet アプリがインストールされている場合、サービスはただちに開始され、Billing 状態リスナーが呼び出されます。それ以外の場合は、可能であれば WebView 経由で支払いが行われ、それも不可能な場合は、Aptoide Wallet のダウンロード、インストール、および新しいウォレットの設定を行うようユーザーに求めます。

class MyApplication : Application() {
...
val aptoideBillingClientStateListener: AptoideBillingClientStateListener =
object : AptoideBillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode != BillingResponseCode.OK) {
Log.d(TAG, "Problem setting up in-app billing: ${billingResult.responseCode}")
return
}

Log.d(TAG, "Setup successful. Querying inventory.")
// Query in-app product details
queryInapps()
// Query subscriptions product details
querySubs()
// Check for pending purchases of Consumables
checkPendingConsumables()
// Check for pending and active Subscriptions
checkSubscriptions()
}

override fun onBillingServiceDisconnected() {
Log.d("Message: ", "Disconnected")
}
}
...
}

AptoideBillingClient

以下に、AptoideBillingClientStateListenerPurchasesUpdatedListener、および公開鍵を引数として渡すことで、Aptoide IAB を構築して開始する方法の例を示します。
Aptoide Connect から公開鍵を取得するには、こちらをクリックしてください。

class MyApplication : Application() {
...
private lateinit var billingClient: AptoideBillingClient
private val purchasesUpdatedListener
get()= PurchasesUpdatedListener { billingResult: BillingResult, purchases: List<Purchase> ->
//Defined in step 4
}
...
override fun onCreate() {
...
val publicKey = MY_KEY // Key obtained in Aptoide Connect's console

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

billingClient.startConnection(aptoideBillingClientStateListener)
...
}
...
}

セットアップが正常に完了したら、ただちに消費型商品の保留中の購入と、アクティブまたは保留中のサブスクリプションを確認する必要があります。保留中の購入がある場合は、その正当性を検証し、アイテムをユーザーに付与して消費する必要があります。消費についてはステップ 4 で説明します。

消費型商品

以下の例は、消費型商品の保留中の購入を確認する方法を示しています。

void fun checkPendingConsumables() {
billingClient.queryPurchasesAsync(
QueryPurchasesParams
.newBuilder()
.setProductType(ProductType.INAPP)
.build()
) { billingResult, purchases ->
// Validate the BillingResult then validate the Purchase in a server-to-server request
// follow this page /docs/iap-validators-server-to-server-check-client

// After validating, deliver the product to the User

// Lastly consumeAsync should be called to notify Aptoide Services of the Successfull delivery and allow the User to Purchase once again the Item
}
}

サブスクリプション

アクティブ/保留中のサブスクリプションを検証するには、queryPurchasesAsync メソッドを使用します。結果は、(消費すべき)保留中のサブスクリプションとアクティブなサブスクリプションで構成されます。期限切れになったサブスクリプションをユーザーから正しく削除するには、受信した結果に含まれていないもののうち、現在ユーザーが利用できるものを照合する必要があります。

以下の例は、サブスクリプションを確認する方法を示しています。

void fun checkSubscriptions() {
billingClient.queryPurchasesAsync(
QueryPurchasesParams
.newBuilder()
.setProductType(ProductType.SUBS)
.build()
) { billingResult, purchases ->
// Validate the BillingResult then validate the Subscription in a server-to-server request
// follow this page /docs/iap-validators-server-to-server-check-client

// After validating, deliver the product to the User

// Lastly consumeAsync should be called to make the Subscription active, there will be no issue in consuming more than once
// Remove Subscriptions from the User when not present in this list
}
}

📘
注記

サブスクリプションのステータスに関する情報をリアルタイムでユーザーに提供するには、RTDN を使用します。

2. アプリ内商品を照会する

接続を開始したら、Aptoide Connect の正しい価格で購入可能な商品をユーザーに表示するために、Aptoide Connect に照会する必要があります。この照会には、商品のタイトルだけでなく、説明、価格なども含まれます。
商品を照会するには、queryProductDetailsAsync を使用できます。これには、Aptoide Connect のレスポンスを処理するための ProductDetailsResponseListener が必要です。

リスナーを作成したら、以下のようにパラメータとともに queryProductDetailsAsync に渡すことができます。

private fun queryInapps() {
val queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
immutableListOf(
Product.newBuilder()
.setProductId("your_product_id")
.setProductType(ProductType.INAPP)
.build()
)
)
.build()

billingClient.queryProductDetailsAsync(
queryProductDetailsParams
) { billingResult, productDetailsResult ->
// Validate the BillingResult and then Process the ProductDetails result
if (billingResult.responseCode == BillingResponseCode.OK) {
for (productDetails in productDetailsResult.productDetailsList) {
// Process here the successfully fetched product details
}

for (unfetchedProduct in productDetailsResult.unfetchedProductList) {
// Process here the unfetched products
}
}
}
}

private fun querySubs() {
val queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
immutableListOf(
Product.newBuilder()
.setProductId("your_product_id")
.setProductType(ProductType.SUBS)
.build()
)
)
.build()

billingClient.queryProductDetailsAsync(
queryProductDetailsParams
) { billingResult, productDetailsResult ->
// Validate the BillingResult and then Process the ProductDetails result
if (billingResult.responseCode == BillingResponseCode.OK) {
for (productDetails in productDetailsResult.productDetailsList) {
// Process here the successfully fetched product details
}

for (unfetchedProduct in productDetailsResult.unfetchedProductList) {
// Process here the unfetched products
}
}
}
}
private void queryInapps() {
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
List.of(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("your_product_id")
.setProductType(ProductType.INAPP)
.build())
)
.build();

billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
(billingResult, productDetailsList) -> {
// Validate the BillingResult and then Process the ProductDetails result
if (billingResult.getResponseCode() == AptoideBillingClient.BillingResponseCode.OK) {
for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
// Process here the successfully fetched product details
}

for (UnfetchedProduct unfetchedProduct : productDetailsResult.getUnfetchedProductList()) {
// Process here the unfetched products
}
}
}
);
}

private void querySubs() {
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
List.of(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("your_product_id")
.setProductType(ProductType.SUBS)
.build())
)
.build();

billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
(billingResult, productDetailsList) -> {
// Validate the BillingResult and then Process the ProductDetails result
if (billingResult.getResponseCode() == AptoideBillingClient.BillingResponseCode.OK) {
for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
// Process here the successfully fetched product details
}

for (UnfetchedProduct unfetchedProduct : productDetailsResult.getUnfetchedProductList()) {
// Process here the unfetched products
}
}
}
);
}

商品詳細結果の処理

queryProductDetailsAsync メソッドは、QueryProductDetailsResult オブジェクトを返します。この結果は、2 つの異なるリストに分かれています。

  • ProductDetails のリスト: 正常に取得された商品のメタデータ(価格、説明など)が含まれます。これを使用して UI に表示します。
  • UnfetchedProduct のリスト: 取得に失敗した商品が含まれます。これらのオブジェクトのメソッドを呼び出して、具体的な失敗ステータスコードを取得します。
❗️
価格表示の必須要件

アプリ審査に合格するには、ProductDetails オブジェクトで返されるローカライズされた価格メタデータを使用して UI に表示する必要があります。Aptoide では、アプリに表示される価格が実際のチェックアウト価格と正確に一致することを必須としています。

これが重要なのは、アプリの UI と Aptoide の購入画面との間に不一致があると、ユーザーの不信感を招き、取引の中断につながるためです。

API から提供された結果を使用しない場合、アプリのリジェクトにつながります。

3. 購入フローを開始する

購入フローを開始するには、launchBillingFlow 関数を使用します。これは、ProductDetails オブジェクトを含む BillingFlowParams のインスタンスを受け取ります。この ProductDetails オブジェクトは、queryProductDetailsAsync メソッドを呼び出すことで取得できます。次のスニペットは、「購入」ボタンに関連付けることができる関数の例を示しています。

fun startPayment(
context: Context,
productDetails: ProductDetails,
obfuscatedAccountId: String?,
developerPayload: String?
) {
// Only allow the user to make Purchases in case the billing service is already setup
if (!billingClient.isReady) {
Log.d(TAG, "Billing service is not ready yet to make purchases.")
return
}

// Verify if the purchase of Subscription type you are making is a Free Trial or not
val isFreeTrial = isFreeTrialSubscription(productDetails, obfuscatedAccountId)

val productDetailsParamsList = listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
// Use the ProductDetails obtained via queryProductDetailsAsync() method
.setProductDetails(productDetails)
.build()
)

val billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.apply {
obfuscatedAccountId?.let {
setObfuscatedAccountId(it)
}
developerPayload?.let {
setDeveloperPayload(it)
}
setFreeTrial(shouldStartFreeTrial)
}.build()

val thread = Thread {
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
runOnUiThread {
if (billingResult.responseCode != BillingResponseCode.OK) {
val builder =
AlertDialog.Builder(this)
builder.setMessage("Error purchasing with response code : ${billingResult.responseCode}")
builder.setNeutralButton("OK", null)
Log.d(TAG, "Error purchasing with response code : ${billingResult.responseCode}")
builder.create().show()
}
}
}
thread.start()
}
private void startPayment(
Context context,
ProductDetails productDetails,
String obfuscatedAccountId,
String developerPayload
) {
// Only allow the user to make Purchases in case the billing service is already setup
if (!billingClient.isReady()) {
Log.d(TAG, "Billing service is not ready yet to make purchases.");
return;
}

// Verify if the purchase of Subscription type you are making is a Free Trial or not
Boolean isFreeTrial = isFreeTrialSubscription(productDetails, obfuscatedAccountId);

List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = List.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
// Use the ProductDetails obtained via queryProductDetailsAsync() method
.setProductDetails(productDetails)
.build()
);

BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setObfuscatedAccountId(obfuscatedAccountId)
.setDeveloperPayload(developerPayload)
.setFreeTrial(isFreeTrial)
.build();

final Activity activity = this;
Thread thread = new Thread(() -> {
final BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
runOnUiThread(() -> {
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Error purchasing with response code : " + billingResult.getResponseCode());
builder.setNeutralButton("OK", null);
Log.d(TAG, "Error purchasing with response code : " + billingResult.getResponseCode());
builder.create().show();
}
});
});
thread.start();
}

obfuscatedAccountId パラメータを使用すると、Billing システムの不正利用や悪用を防ぐのに役立ちます。この識別子により、アプリケーション内の特定のユーザーと Billing アクティビティを正確に関連付けることができ、セキュリティと信頼性が向上します。ユーザーアカウントのロジックがない場合、このパラメータは null にできます。これは Purchase の結果には返されません。

developerPayload は、購入を独自のアプリケーションの購入検証ロジックに関連付ける必要がある場合に、購入に関する情報を保存するために使用します。

無料トライアルの開始

ユーザーに対して無料トライアルを有効にするには、BillingFlowParams クラスの freeTrial パラメータを true に設定します。これにより、指定された obfuscatedAccountId に基づいて無料トライアルの支払いが開始されます。

注記: 無料トライアルの支払いを開始する際には、obfuscatedAccountId必須です。

ユーザーがすでにそのサブスクリプションの無料トライアルを消費している場合、それ以降のサブスクリプション試行は自動的に通常の支払いフローにフォールバックします。これにより、スムーズな支払い体験が保証され、エラーを引き起こすことなく、デフォルトで freeTrial を安全に true に設定できます。

private fun isFreeTrialSubscription(
productDetails: ProductDetails,
obfuscatedAccountId: String?
): Boolean {
// First verify if the Free Trial feature and Obfucasted Account Id parameter are available
if (billingClient.isFeatureSupported(FeatureType.FREE_TRIALS) != 0) {
return false
}

if (billingClient.isFeatureSupported(FeatureType.OBFUSCATED_ACCOUNT_ID) != 0) {
return false
}

// Verify if the Product Type is a Subscription
if (!productDetails.productType == ProductType.SUBS) {
return false
}

// Apply your internal App Logic to verify if the User should receive a Free Trial or not
/* Example:
return obfuscatedAccountId == "123" && productDetails.productId == "trial_dice"
*/

return false
}
private boolean isFreeTrialSubscription(
ProductDetails productDetails,
String obfuscatedAccountId
) {
// First verify if the features Free Trial and Obfucasted Account Id are available
if (billingClient.isFeatureSupported(FeatureType.FREE_TRIALS) != 0) {
return false;
}

if (billingClient.isFeatureSupported(FeatureType.OBFUSCATED_ACCOUNT_ID) != 0) {
return false;
}

// Verify if the Product Type is a Subscription
if (!productDetails.getProductType().equals(ProductType.SUBS)) {
return false;
}

// Apply your internal App Logic to verify if the User should receive a Free Trial or not
/* Example:
return obfuscatedAccountId.equals("123") && productDetails.getProductId().equals("trial_dice")
*/

return false;
}

4. 購入を処理し、アイテムをユーザーに付与し、購入を消費する

SDK が購入を処理して検証すると、PurchasesUpdatedListener を通じて購入データが通知されます。このリスナーはステップ 1 で登録したもので、購入が更新されたときのコールバックを含みます。このコールバック内で、購入の詳細を取得し、アイテムをユーザーに割り当てることができます。

以下は、PurchasesUpdatedListener 統合のサンプルスニペットです。

class MyApplication : Application() {
...
private var purchasesUpdatedListener =
PurchasesUpdatedListener { billingResult: BillingResult, purchases: List<Purchase> ->
if (billingResult.responseCode == BillingResponseCode.OK) {
for (purchase in purchases) {
val purchaseToken = purchase.purchaseToken

// Validate the Purchase in a server-to-server request
// follow this page /docs/iap-validators-server-to-server-check-client

// After validating, deliver the product to the User

// Lastly consumeAsync should be called to allow the user to purchase the
// item again and change the purchase's state.
// Also consume subscriptions to make them active, there will be no issue in consuming more than once
billingClient.consumeAsync(
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build(),
consumeResponseListener
)
}
} else {
Log.e(TAG, "Error on receiving the Purchase with response code : ${billingResult.responseCode}")
}
}
...
}
class MyApplication extends Application {
...
PurchasesUpdatedListener purchaseUpdatedListener = (billingResult, purchases) -> {
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
for (Purchase purchase : purchases) {
String purchaseToken = purchase.getPurchaseToken();

// Validate the Purchase in a server-to-server request
// follow this page /docs/iap-validators-server-to-server-check-client

// After validating, deliver the product to the User

// Lastly consumeAsync should be called to allow the user to purchase the
// item again and change the purchase's state.
// Also consume subscriptions to make them active, there will be no issue in consuming more than once

billingClient.consumeAsync(
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build(),
consumeResponseListener
);
}
} else {
Log.e(TAG, "Error on receiving the Purchase with response code :" + billingResult.getResponseCode());
}
};
...
}

購入を検証する

購入の正当性を確保し、不正を防止するため、アプリケーションは常にサーバー間リクエストで購入を検証する必要があります。この検証を行うには、アプリ内購入の検証ページのガイドラインに従ってください。購入の検証に成功した場合にのみ、商品をユーザーに付与する手続きを進めてください。

購入を消費する

購入が正常に検証され、アイテムも正常にユーザーに付与された後、購入を消費する必要があります。購入を消費するには、Consume API を使用します。サブスクリプションの場合は、Acknowledge API を使用します。これは、バックエンド/サーバー側にアイテム付与ロジックを持つアプリケーションに役立ちます。

購入を 48 時間以内に消費しない場合、自動的に返金されることにご注意ください。

アプリケーションがクライアント側のロジックのみに基づいている場合は、Aptoide Billing SDK の consumeAsync 関数を使用してください。この関数は PurchasesUpdatedListener のスニペットに示されており、Aptoide Connect の消費レスポンスを処理するための ConsumeResponseListener が必要です。

以下に、ConsumeResponseListener の実装例を示します。

class MyApplication : Application() {
...
val consumeResponseListener = ConsumeResponseListener {billingResult, purchaseToken ->
Log.d(TAG, "Consumption finished. Purchase: $purchaseToken, result: ${billingResult.responseCode}")
if (billingResult.responseCode == BillingResponseCode.OK) {
Log.d(TAG, "Consumption successful. Provisioning.")
} else {
Log.e(TAG, "Error while consuming token: $purchaseToken")
}
Log.d(TAG, "End consumption flow.")
}
...
}
class MyApplication extends Application {
...
ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() {
@Override public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
Log.d(TAG, "Consumption finished. Purchase: " + purchaseToken + ", result: " + billingResult.getResponseCode());

if (billingResult.getResponseCode() == BillingResponseCode.OK) {
Log.d(TAG, "Consumption successful. Provisioning.");
} else {
Log.e(TAG, "Error while consuming token: " + purchaseToken);
}
Log.d(TAG, "End consumption flow.");
}
};
...
}

FAQ

サポートされているターゲット SDK レベルは何ですか?

現在、ネイティブ Android SDK のターゲットレベルは 33 です。Android は API レベル 33 の後方互換性を維持しているため、開発者は問題なくより高い API レベルを使用できます。


サポートされている最小 SDK レベルは何ですか?

現在、ネイティブ Android SDK のサポートされている最小レベルは 21 です。


OSP から SDK 統合へどのように移行できますか?

OSP から SDK 統合へ移行するには、このガイドに従って、主な相違点必要な対応を理解してください。


従来の AppCoins Billing SDK から、この最新の Aptoide Billing SDK へどのように移行できますか?

従来の AppCoins Billing SDK から新しい Aptoide Billing SDK へ移行するには、このガイドに従って、主な相違点必要なコード変更を理解してください。


SDK の実装を支援するヘルパーはありますか?
はい、ステップごとに案内する Android Studio プラグインがあります。このプラグインはこちらからダウンロードできます。


ユーザーを購入にどのようにリンクしますか?
購入をユーザーにリンクする必要がある場合は、obfuscatedAccountId パラメータに userId を渡すことで実現できます。次の例は、購入関数に渡される UserId の一例を示しています。

startPurchase(productDetails, "user12345")
startPurchase(sku, "user12345");

この値は Purchase オブジェクトから取得できます。次のサンプルは、PurchasesUpdatedListener でペイロードを抽出し、条件付き処理を行う方法を示しています。

private val purchasesUpdatedListener
get()= PurchasesUpdatedListener { billingResult: BillingResult, purchases: List<Purchase> ->
if (billingResult.responseCode == BillingResponseCode.OK) {
for (purchase in purchases) {
val obfuscatedAccountId = purchase.accountIdentifiers?.obfuscatedAccountId
if (obfuscatedAccountId != null && obfuscatedAccountId == "user12345") {
...
}
...
}
} else {
...
}
}
PurchasesUpdatedListener purchaseUpdatedListener = (billingResult, purchases) -> {
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
for (Purchase purchase : purchases) {
String obfuscatedAccountId = purchase.getAccountIdentifiers().getObfuscatedAccountId();
if (obfuscatedAccountId != null && obfuscatedAccountId.equals("user12345")) {
...
}
...
}
} else {
...
}
};

アプリの所有権を設定せずに購入フローをテストするにはどうすればよいですか?
テスト目的で、次のデータを使用してアプリケーションの Billing をテストできます。

applicationId:

com.appcoins.sample

IAB_KEY:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyEt94j9rt0UvpkZ2jPMZZ16yUrBOtjpIQCWi/
F3HN0+iwSAeEJyDw7xIKfNTEc0msm+m6ud1kJpLK3oCsK61syZ8bYQlNZkUxTaWNof1nMnbw3Xu5nuY
MuowmzDqNMWg5jNooy6oxwIgVcdvbyGi5RIlxqbo2vSAwpbAAZE2HbUrysKhLME7IOrdRR8MQbSbKE
y/9MtfKz0uZCJGi9h+dQb0b69H7Yo+/BN/ayBSJzOPlaqmiHK5lZsnZhK+ixpB883fr+PgSczU7qGoktqoe
6Fs+nhk9bLElljCs5ZIl9/NmOSteipkbplhqLY7KwapDmhrtBgrTetmnW9PU/eCWQIDAQAB

また、当社の Billing 実装を組み込んだ Google の Trivial Drive のバージョンをこちらから入手し、すでに動作するサンプルを試すこともできます。


AAR または JAR を使用して Android Billing SDK を実装するにはどうすればよいですか?

AAR または JAR を使用して SDK を実装するには、AAR および JAR ライブラリに関する Android デベロッパーガイドに必ず従ってください。

ファイルは、公式の mavenRepository から取得できます。

ファイルを Gradle に追加する際には、Android Billing SDK が使用するすべての依存関係も忘れずに追加し、それらの依存関係についても同じ手順に従ってください。依存関係を取得するには、Compile Dependencies セクションを参照してください(実装しているバージョンのものを使用することを忘れないでください)。


Google Play Billing SDK から Aptoide Billing SDK へ移行しようとしていますが、このプロセスに役立つ追加のドキュメントはありますか?

はい!Google Play Billing SDK から当社の Aptoide Billing SDK へ移行しようとしている場合、完全で成功する移行のための最も重要な手順を記載したこのドキュメントページがあります。


まだ従来版の AppCoins Billing SDK を使用していますが、従来版のドキュメントはどこで見つけられますか?

従来版の AppCoins Billing SDK をまだ使用している場合は、機能とセキュリティパッチを最新の状態に保つため、新しい Aptoide Billing SDK へ移行することを強くお勧めします。それでも、従来版のドキュメントはこちらで引き続き利用可能です。


SDK を呼び出す際に 1429 TOO_MANY_REQUESTS レスポンスコードが返されるのはなぜですか?

このエラーは、SDK のレート制限に達したために発生します。システムの安定性を確保するため、短期間に実行できるリクエスト数を制限する場合があります。アプリが、偶発的なループや高頻度のポーリングなどにより、あまりにも多くの呼び出しをあまりにも速く送信すると、システムは過負荷を防ぐために以降のリクエストを一時的にブロックします。

これを解決するには、SDK の呼び出しが必要以上の頻度で発生していないかコードを確認してください。失敗したリクエストを再試行する必要がある場合は、指数バックオフを使用することをお勧めします。これは、後続の各再試行の間隔を少しずつ長くすることを意味します。これにより、システムが制限をリセットする時間が確保され、アプリが正常な状態に戻ります。


よくある問題

当社で認識しているよくある問題の多くは、次の点を確実にすることで回避できます。

  • Android Billing SDK を、Activity ではなくプロジェクトの Application クラスで初期化してください。
    • これは重要です。Activity が破棄されると、そのコンテキストの使用に問題が生じるためです。
  • アイテムが消費されていない場合、新規購入で利用できません。
    • 購入を消費することが重要です。そうしないと、取引が確定されず、アイテムを新規購入で利用できません。
  • queryProductDetailsAsync() の結果を使用して、商品の価格を表示してください。
    • これは、アプリケーションと Wallet アプリの両方で、ユーザーの所在地に対応する価格を一致させるために重要です。
  • Main スレッドでの呼び出しは避けるようにしてください。
    • これは、Wallet アプリへのリクエストやバックエンドのコールバックによって Main スレッドがブロックされないようにするために、必ず守るべきことです。