Сбой Java — приложения при использовании in app purchase

#java #android #in-app-purchase #in-app-billing

#java #Android #покупка в приложении #биллинг в приложении

Вопрос:

Итак, у меня возникла некоторая проблема с покупкой в приложении в моем приложении.

Я начал работать над старым (8-месячным) проектом, который у меня был ранее, но у меня возникли некоторые проблемы с покупкой приложения. Приложение уже доступно в Play Store, поэтому покупка в приложении активна.

В build.gradle (:app) Я изменился с:

 dependencies {
  implementation 'com.android.billingclient:billing:2.2.1'
 

к этому:

 dependencies {
  def billing_version = "3.0.0" // In App Purchase
      implementation "com.android.billingclient:billing:$billing_version"
 

Это полный код моего UpgradeActivity.java:

 package com.pinloop.testproj;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResu<
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private List skuList = new ArrayList();

    private String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);
        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                upgradeAction();
            }
        });

        // Check In App Purchase

        upgradeButton.setEnabled(true);
        skuList.add(sku);


        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }

    private void upgradeAction() {
        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query"   billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            amp;amp; !skuDetailsList.isEmpty())
                    {
                        for (Object skuDetailsObject : skuDetailsList) {
                            final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
                            Toast.makeText(UpgradeActivity.this, ""   skuDetails.getSku(), Toast.LENGTH_SHORT).show();

                            if (skuDetails.getSku() == sku)
                                mSkuDetails = skuDetails;
                            upgradeButton.setEnabled(true);

                            upgradeButton.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    BillingFlowParams billingFlowParams = BillingFlowParams
                                            .newBuilder()
                                            .setSkuDetails(skuDetails)
                                            .build();
                                    billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);

                                }
                            });
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                amp;amp; purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled"   responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code"   responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}
 

В основном, при нажатии происходит upgradeButton сбой приложения. Я не могу понять, что я делаю не так. Я использую правильный артикул, и приложение доступно в Play Store уже более года.

Это журнал ошибок, который я получаю при нажатии upgradeButton :

 I/zygote: Do partial code cache collection, code=124KB, data=72KB
    After code cache collection, code=124KB, data=72KB
    Increasing code cache capacity to 512KB
D/EGL_emulation: eglMakeCurrent: 0xdb928540: ver 3 0 (tinfo 0xdb92bbd0)
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pinloop.testproj, PID: 13352
    java.lang.IllegalArgumentException: SKU cannot be null.
        at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:billing@@3.0.0:23)
        at com.pinloop.testproj.UpgradeActivity.upgradeAction(UpgradeActivity.java:130)
        at com.pinloop.testproj.UpgradeActivity.access$000(UpgradeActivity.java:31)
        at com.pinloop.testproj.UpgradeActivity$1.onClick(UpgradeActivity.java:56)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
 

Не уверен, на что здесь смотреть, но я это вижу java.lang.IllegalArgumentException: SKU cannot be null. , но артикул уже объявлен : private String sku = "com.pinloop.testproj.pro"; . Есть идеи?

Ответ №1:

Вот код обновления

 import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResu<
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.aumhum.aumhum.R;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private final List<String> skuList = new ArrayList();

    private final String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);

        upgradeButton.setEnabled(false);

        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                upgradeAction();

            }
        });

        // Check In App Purchase

        skuList.add(sku);

        setButtonStatus();

    }

    private void setButtonStatus(){

        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }


    private void upgradeAction() {

        if (mSkuDetails==null){
            Toast.makeText(this,"Please wait while we get details",Toast.LENGTH_SHORT).show();
            return;
        }

        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query"   billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            amp;amp; !skuDetailsList.isEmpty())
                    {
                        for (SkuDetails skuDetails : skuDetailsList) {
                            Toast.makeText(UpgradeActivity.this, ""   skuDetails.getSku(), Toast.LENGTH_SHORT).show();
                            if (skuDetails.getSku().equals(sku)) {
                                mSkuDetails = skuDetails;
                                upgradeButton.setEnabled(true);
                            }
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                amp;amp; purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled"   responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code"   responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}
 

3 проблемы

  1. Вы включили кнопку обновления по умолчанию и не имеете нулевой проверки в методе upgradeAction
  2. Вы сравниваете строки с помощью == вместо .equals в java
  3. Вы настраиваете обновление onclick listener 2 раза

Комментарии:

1. Итак, я попробовал ваш код, и при нажатии кнопки обновления ничего не происходит. Если я переключусь upgradeButton.setEnabled(false); на true , а затем нажму кнопку обновления, он просто скажет: Please wait while we get details каждый раз, когда я нажимаю кнопку. Что я упускаю из виду? Я дважды проверил в Play Console, что идентификатор продукта In-app products совпадает с артикулом и имеет статус active.

2. Существует некоторая проблема с получением сведений из Play Store, вам необходимо отладить и проверить, в чем ошибка

3. Хм, хорошо.. Я работаю на симуляторе, имеет ли это значение? Или это должно сработать?

4. Может быть, у меня были проблемы с несколькими эмуляторами. попробуйте запустить на реальном устройстве.

5. Попробовал на устройстве сейчас, включив кнопку настройки true , и это единственное сообщение, которое я получаю в журнале запуска при нажатии кнопки обновления: pastebin.com/mJG3ErDA . Поэтому я не могу найти ни одного сообщения об ошибке.

Ответ №2:

атрибут ‘mSkuDetails’?

  if (skuDetails.getSku() == sku){
         mSkuDetails = skuDetails;
         upgradeButton.setEnabled(true);
         upgradeButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
           BillingFlowParams billingFlowParams = BillingFlowParams
                                            .newBuilder()
                                            .setSkuDetails(mSkuDetails)
                                            .build();
                                    billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);

                                }
                            });
}
  
 

Ответ №3:

a. Убедитесь, что артикул совпадает с кодом продукта, установленным в Google Play в разделе выставления счетов за приложения.

б. Выполните отладку кода, чтобы убедиться, что null не передается как sku в вызове для запуска потока выставления счетов.

c. Обновите кэш Play Store на тестируемом устройстве