この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaでの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)
2024年9月以降のアプリ内課金の実装は、Billing Library 6以降の使用が必須となります。
ここでは、Billing Library 5への移行について説明します。
2024年5月現在、Google Play Billing Library 7が公開されています。
Billing Library 5へ移行したソースのままで、動作しています。
非推奨となった querySkuDetailsAsync の対応
◎ポイント
Google Play Billing Library 5( com.android.billingclient:billing:5 ) では、querySkuDetailsAsync、SkuDetails などが非推奨になり、替わりに queryPurchasesAsync、ProductDetails を使用した実装に変更する必要があります。
対応前
querySkuDetailsAsync を呼び出して、SkuDetails のリストを取得します。
SkuDetails のリストを引数に BillingFlowParams を生成して、launchBillingFlow を呼び出す実装を行います。
Java 対応前コーディング(参考)
◎アプリ内課金クラス(InAppBilling)
購入可能リスト取得では、querySkuDetailsAsync を使用して、SkuDetails を取得します。
SkuDetails を引数として、購入フローを起動します。
public class InAppBilling {
private BillingClient billingClient;
private final ArrayList<String> errorMessage = new ArrayList<>();
private List<Purchase> arrayListPurchase = new ArrayList<>();
private List<SkuDetails> arraySkuDetails = new ArrayList<>();
private final int responseCode = Integer.MAX_VALUE;
// インタフェース
public interface OnBillingClientStateListener {
void onBillingClientState(int responseCode);
}
public interface OnConsumeAsyncListener {
void consumeAsync(int consume);
}
public interface OnPurchasesUpdatedListener {
void purchasesUpdated(int purchase);
}
public interface OnPurchasesResponseListener {
void queryPurchasesResponse(int responseCode);
}
public interface OnSkuDetailsResponseListener {
void skuDetailsResponse(int responseCode);
}
// コンストラクタ
public InAppBilling(Context context,
OnBillingClientStateListener onBillingClientStateListener,
OnPurchasesUpdatedListener onPurchasesUpdatedListener,
OnConsumeAsyncListener onConsumeAsyncListener) {
billingClient = BillingClient.newBuilder(context).setListener((billingResult, list) -> {
switch (billingResult.getResponseCode()) {
case BillingClient.BillingResponseCode.OK:
if (null != list) {
for (Purchase purchase : list) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
});
}
}
}
break;
case BillingClient.BillingResponseCode.USER_CANCELED:
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
break;
case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
default:
errorMessage.add(String.format("BillingResult : %s", billingResult.getDebugMessage()));
break;
}
onPurchasesUpdatedListener.purchasesUpdated(billingResult.getResponseCode());
}).enablePendingPurchases().build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingServiceDisconnected() {
onBillingClientStateListener.onBillingClientState(responseCode);
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
onBillingClientStateListener.onBillingClientState(billingResult.getResponseCode());
}
});
}
// エラーメッセージ取得
public ArrayList<String> getErrorMessage() {
return errorMessage;
}
public int getConnectionState() {
return billingClient.getConnectionState();
}
// BillingClient切断
public void endConnection() {
if (getConnectionState() == BillingClient.ConnectionState.CONNECTED)
billingClient.endConnection();
}
// 購入済みリスト取得
public void queryPurchasesAsync(OnPurchasesResponseListener onPurchasesResponseListener) {
arrayListPurchase = new ArrayList<>();
billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, (result, list) -> {
arrayListPurchase.addAll(list);
onPurchasesResponseListener.queryPurchasesResponse(result.getResponseCode());
});
}
public List<Purchase> getArrayListPurchase() {
return arrayListPurchase;
}
// 購入可能リスト取得
public void querySkuDetailsAsync(String sku, OnSkuDetailsResponseListener onSkuDetailsResponseListener) {
List<String> skuList = new ArrayList<>();
arraySkuDetails = new ArrayList<>();
skuList.add(sku);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(), (billingResult, list) -> {
if (list != null) arraySkuDetails.addAll(list);
onSkuDetailsResponseListener.skuDetailsResponse(billingResult.getResponseCode());
});
}
public List<SkuDetails> getArraySkuDetails() {
return arraySkuDetails;
}
// 購入フロー起動
public int launchBillingFlow(Activity activity, SkuDetails skuDetails) {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
return billingClient.launchBillingFlow(activity, billingFlowParams).getResponseCode();
}
// 消費
public void consumeAsync(Purchase purchase, OnConsumeAsyncListener onConsumeAsyncListener) {
billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
});
}
}
◎アプリ内課金の実装部
アプリ内課金クラス(InAppBilling)の購入可能リスト取得では、戻り値として SkuDetails を受け取り、launchBillingFlow を起動しています。
// android.test.purchased 正常に購入出来るテスト用プロダクトID
// android.test.canceled 購入のキャンセルをテストできるプロダクトID
// android.test.refunded 払い戻しが行われた時のレスポンスをテストできるプロダクトID
// android.test.item_unavailable 購入希望商品が存在しなかった時をシミュレートできるプロダクトID
private static final String code = "android.test.purchased";
private InAppBilling inAppBilling;
:
inAppBilling = new InAppBilling(context,
responseCode -> {
if (responseCode == BillingClient.BillingResponseCode.OK) {
// 購入済みリスト取得
inAppBilling.queryPurchasesAsync(responseCode1 -> {
if (responseCode1 == BillingClient.BillingResponseCode.OK) {
List<Purchase> listPurchase = inAppBilling.getArrayListPurchase();
if (listPurchase.size() > 0) {
for (Purchase purchase : listPurchase) {
// 消費リクエスト
inAppBilling.consumeAsync(purchase, consume -> {
:
});
}
} else {
// 購入可能リスト取得
inAppBilling.querySkuDetailsAsync(code, responseCode2 -> {
if (responseCode2 == BillingClient.BillingResponseCode.OK) {
List<SkuDetails> listSkuDetails = inAppBilling.getArraySkuDetails();
if (listSkuDetails.size() > 0) {
for (SkuDetails skuDetails : listSkuDetails) {
if (inAppBilling.launchBillingFlow(this, skuDetails) != 0) {
for (String errorMessage : inAppBilling.getErrorMessage()) {
Log.d(TAG, errorMessage);
}
}
}
} else {
:
}
}
});
}
}
});
} else {
for (String errorMessage : inAppBilling.getErrorMessage()) {
Log.d(TAG, errorMessage);
}
:
}
},
purchase -> {
if (purchase != BillingClient.BillingResponseCode.OK) {
for (String errorMessage : inAppBilling.getErrorMessage()) {
Log.d(TAG, errorMessage);
}
:
}
},
consume -> {
// 消費リクエスト
if (consume == BillingClient.BillingResponseCode.OK) {
:
} else {
:
}
}
);
}
テスト用プロダクトID(android.test.purchase)によるテストができなくなりました。
未署名でGoogle Playにアップロードされていないアプリはブロックされます。
ライセンス テスターとPlay Billing Labを活用したテストを推奨しています。
対応後
queryPurchasesAsync を呼び出して、ProductDetails のリストを取得します。
ProductDetails のリストを引数に BillingFlowParams を生成して、launchBillingFlow を呼び出す実装を行います。