#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 проблемы
- Вы включили кнопку обновления по умолчанию и не имеете нулевой проверки в методе upgradeAction
- Вы сравниваете строки с помощью == вместо .equals в java
- Вы настраиваете обновление 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 на тестируемом устройстве