質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

AIDL から Google Play Billing Libraryへの移行でQuerySkuDetailsAsyncの応答がない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 191

sunmo

score 9

AndroidでXamarin.Androidでアプリ内課金を行っているのですが、
AIDL から Google Play Billing Libraryへの移行が必要とのことで、
対応中なのですが、最初から壁にぶち当たり前に進めていません。

開発環境は、下記の通りです。

開発ソフト:Microsoft Visual Studio Community 2019
開発言語:Xamarin.Android
NuGet:Xamarin.Android.Google.Billing.Clinetを利用
(以前は、Plugin.InAppBillingを使っていました。)

困っていることは、

「BuyActivity」と「BillingSupport」というクラスを作り、
「BuyActivity」の「Resume」の中で、「BillingSupport」のインスタンスを作成しています。

この後、「BuyActivity」の中の「BuyInApp」関数を呼び出して、「BillingSupport.LoadPurchases_InApp」関数で
商品の読み込みを行おうとしているのですが、「QuerySkuDetailsAsync」関数が
応答がないようで、「OnSkuDetailsResponse」が呼び出されませんでした。
そのため、「SkuDetails」がnullのままとなり、処理が継続できません。

Googleの開発サイトなどを参照しているのですが、Xamarin.Androidの事例が
あまり見つからず、解決方法が分かっておりません。

試したこと、確認したことは下記の通りです。

1)「OnBillingSetupFinished」はOKがかえってきている。
2)QuerySkuDetailsAsyncをWait()すると、応答がないままフリーズする。
3)予約アイテム「android.test.purchased」を使っても応答はないまま。

以上です。

どなたか、お分かりになる方いらっしゃいましたら、
ご教授よろしくお願いいたします。

ソース抜粋

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Android.App;
using Android.BillingClient.Api;
using Android.Content;

    class BillingSupport : Java.Lang.Object, IPurchasesUpdatedListener, IBillingClientStateListener, ISkuDetailsResponseListener, IConsumeResponseListener
    {
        Context Context;
        private BillingClient billingClient;
        public IList<SkuDetails> SkuDetails;

        #region 購入開始
        public BillingSupport(Activity activity)
        {
            Context = activity.ApplicationContext;
            billingClient = BillingClient.NewBuilder(Context).EnablePendingPurchases().SetListener(this).Build();
            billingClient.StartConnection(this);
        }

        public void OnBillingSetupFinished(BillingResult p0)
        {
            if (p0.ResponseCode == BillingResponseCode.Ok)
            {
                Console.WriteLine("BILLING | startConnection | RESULT OK");
                // The BillingClient is ready. You can query purchases here.
            }
            else
            {
                Console.WriteLine("BILLING | startConnection | RESULT: $billingResponseCode");
            }
        }

        public void OnBillingServiceDisconnected()
        {
            Console.WriteLine("BILLING | onBillingServiceDisconnected | DISCONNECTED");
            // Try to restart the connection on the next request to
            // Google Play by calling the startConnection() method.
        }

        #endregion

        #region 商品の読み込み
        //public void LoadPurchases_InApp()
        //{

        //    if (billingClient.IsReady)
        //    {
        //        List<string> skuList = new List<string>();
        //        skuList.Add("android.test.purchased");
        //        skuList.Add("android.test.canceled");
        //        skuList.Add("android.test.item_unavailable");
        //        SkuDetailsParams.Builder paramse = SkuDetailsParams.NewBuilder();
        //        paramse.SetSkusList(skuList).SetType(BillingClient.SkuType.Inapp);
        //        billingClient.QuerySkuDetailsAsync(paramse.Build());(★応答がない)
        //    }
        //}
        //変更 REV001
        public async Task<QuerySkuDetailsResult> LoadPurchases_InApp()
        {
            if (BillingClient == null || !BillingClient.IsReady)
            {
                await ConnectAsync();
            }

            IList<string> skuList = new JavaList<string>
            {
                "android.test.purchased",
                "android.test.canceled",
                "android.test.item_unavailable"
            };

            SkuDetailsParams paramse = SkuDetailsParams.NewBuilder().SetSkusList(skuList).SetType(BillingClient.SkuType.Inapp).Build();

            var querySkuDetailsResult = await BillingClient.QuerySkuDetailsAsync(paramse);
            return querySkuDetailsResult;
            }
        }
        //<=== 変更 REV001

        public void OnSkuDetailsResponse(BillingResult p0, IList<SkuDetails> p1)
        {
            if (p0.ResponseCode == BillingResponseCode.Ok)
            {
                Console.WriteLine("querySkuDetailsAsync, responseCode: $responseCode");
                InitProductAdapter(p1);
            }
            else
            {
                Console.WriteLine("Can't querySkuDetailsAsync, responseCode: $responseCode");
            }
        }
        public void InitProductAdapter(IList<SkuDetails> skuDetails)
        {
            this.SkuDetails = skuDetails;
        }
        #endregion


        #region 商品の購入
        public void PurchaseInApp(SkuDetails skuDetails)
        {
            BillingFlowParams billingFlowParams = 
                BillingFlowParams.NewBuilder()
                .SetSkuDetails(skuDetails)
                .Build();

            billingClient.LaunchBillingFlow(this.Context as Activity, billingFlowParams);
        }

        //Response code 7 in OnPurchasesUpdated. It means: Item Already Owned.
        public void OnPurchasesUpdated(BillingResult p0, IList<Purchase> p1)
        {
            if (p0.ResponseCode == BillingResponseCode.Ok && p1 != null)
            {
                foreach (Purchase purchase in p1)
                {
                    HandlePurchase(purchase);
                }
            }
            else if (p0.ResponseCode == BillingResponseCode.UserCancelled)
            {
                // ユーザーが購入フローをキャンセルした場合に発生したエラーを処理します。
            }
            else
            {
                // 他のエラー コードを処理します。
            }
        }

        public void HandlePurchase(Purchase purchase)
        {
            if (purchase.PurchaseState == PurchaseState.Purchased)
            {
                // ユーザーに権利を付与します。

                // 購入がまだ承認されていない場合は、購入を承認します。
                if (!purchase.IsAcknowledged)
                {
                    AcknowledgePurchaseParams acknowledgePurchaseParams =
                        AcknowledgePurchaseParams.NewBuilder()
                            .SetPurchaseToken(purchase.PurchaseToken)
                            .Build();
                    billingClient.AcknowledgePurchase(acknowledgePurchaseParams, (IAcknowledgePurchaseResponseListener)this);
                }

            }
            else if (purchase.PurchaseState == PurchaseState.Pending)
            {
                // Here you can confirm to the user that they've started the pending
                // purchase, and to complete it, they should follow instructions that
                // are given to them. You can also choose to remind the user in the
                // future to complete the purchase if you detect that it is still
                // pending.
            }
        }
        #endregion

        #region アイテム消費
        public void ConsumePurchases(string purchaseToken)
        {
            ConsumeParams consumeParams = 
                ConsumeParams.NewBuilder()
                .SetPurchaseToken(purchaseToken)
                .Build();
            billingClient.ConsumeAsync(consumeParams);
        }

        public void OnConsumeResponse(BillingResult p0, string p1)
        {            
            if (p0.ResponseCode == BillingResponseCode.Ok && p1 != null)
            {
                Console.WriteLine("onPurchases Updated consumeAsync, purchases token removed: $purchaseToken");
            }
            else
            {
                Console.WriteLine("onPurchases some troubles happened: $responseCode");
            }
        }

        #endregion
    }
  public class BuyActivity : Activity
    {   
        BillingSupport billingSupport;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            //省略
        }

        protected override void OnResume()
        {
            base.OnResume();

            #region Google Play 請求サービス起動
            billingSupport = new BillingSupport(this);
            #endregion
        }

     //private void BuyInApp()
        //{
        //    //商品の詳細情報を取得
        //    billingSupport.LoadPurchases_InApp();

        //    //商品を購入
        //    billingSupport.PurchaseInApp(billingSupport.SkuDetails[0]);//★ここでSkuDetailsがnullになる
        //}
        //変更 REV001 ===>
        private async Task BuyInApp()
        {
            //商品の詳細情報を取得
            var response = await billingSupport.LoadPurchases_InApp();

            //商品を購入/消費
            billingSupport.PurchaseInApp(billingSupport.SkuDetails[0]);
        }
        //<=== 変更 REV001
  }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

課金の実装はしたことがないのでわかりませんが、
QuerySkuDetailsAsyncConsumeAsyncを呼び出している箇所で、非同期(async/await)の対応を行ってみてはいかがでしょうか。

C#のTaskと非同期処理についてまとめる

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/16 22:34

    ご助言いただきありがとうございます。
    ソースに「REV001」のリビジョンを付けて修正したのですが、結果は同じでした。
    「QuerySkuDetailsAsync」を呼び出すと、「OnSkuDetailsResponse」が呼び出されると思っているのですが、これが呼ばれず、商品情報が取得できずNULLになっています。

    キャンセル

  • 2020/08/16 23:18

    先ほどの件をヒントに調べていたら、「OnSkuDetailsResponse」は呼ばれないのですが、「QuerySkuDetailsAsync」の戻り値にSkuDetailsの情報が入っておりました!
    こちらの内容で、次のステップに進めれそうです。AndroidとXamarinで使い方が違うんですね・・・
    ありがとうございました。

    キャンセル

  • 2020/08/16 23:22

    解決のヒントになったようでなによりです。

    キャンセル

check解決した方法

0

「QuerySkuDetailsAsync」の戻り値が、OK、NGの結果だけでなく、
「SkuDetails」の情報も入っておりました。下記で目的の情報を取得できました。
AndroidとXamarinで作りが若干違うことに気が付いた瞬間でした。

        private async Task BuyInApp()
        {
            //商品の詳細情報を取得
            QuerySkuDetailsResult response = await billingSupport.LoadPurchases_InApp();

            //商品を購入/消費
            billingSupport.PurchaseInApp(response.SkuDetails[0]);
        }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る