如何在Android应用程序中实现应用内结算?


73

在Android应用中实施应用内结算似乎非常复杂。我该怎么办?SDK中的示例应用程序只有一个Activity,对于像我这样的具有多个Activity的应用程序来说,这过于简化了。


14
+1 Android示例确实很复杂,有很多层,并且涵盖了所有功能,无论您是应该从更小,更简单的方式入手,还是没有任何文档。我确实有该示例的简化版本。请说明您的问题所在,因为基本情况是您应将活动绑定到计费服务并进行呼叫。其余的由广播接收机完成。
sfratini 2012年

@sfratini您能发表您的例子吗?谢谢!
SZH 2012年

上班很痛苦。如果没有其他问题,则需要花费数小时进行调试并获得正确的解决方案。我有一个项目设置,就像一个工作良好的世界。试试看mcondev.wordpress.com/2011/06/26/…–
Siddharth

1
我认为本教程(youtu.be/El7q_1a_WVc)最适合应用内结算。它教了如何在5分钟内实现应用内结算功能!
Zohab Ali

这个已经过期了。
Keith Loughnane

Answers:


42

好吧,我将尝试解释我的经历。我不认为自己是专家,但是几天我都伤透了脑筋。

对于初学者来说,我很难理解示例和应用程序的工作流程。我认为从一个简单的示例开始应该会更好,但是将代码分成小块非常困难,而且不知道您是否要破坏任何东西,这非常困难。我将告诉您我所拥有的以及为使示例工作而对示例进行的更改。

我有一个活动,所有购买都来自此活动。它叫做Pro。

首先,您应该使用公开市场开发人员密钥更新Security类中的变量base64EncodedPublicKey,否则您将看到一个不错的Exception。

好吧,我将Activity绑定到BillingService上是这样的:

      public class Pro extends TrackedActivity implements OnItemClickListener {

            private BillingService mBillingService;
            private BillingPurchaseObserver mBillingPurchaseObserver;
            private Handler mHandler;

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


                //Do my stuff

                mBillingService = new BillingService();
                mBillingService.setContext(getApplicationContext());

                mHandler = new Handler();
                mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler);

            }

        }



    @Override
    protected void onStart() {
       //Register the observer to the service
        super.onStart();
        ResponseHandler.register(mBillingPurchaseObserver);   
    }


    @Override
    protected void onStop() {
        //Unregister the observer since you dont need anymore
        super.onStop();
        ResponseHandler.unregister(mBillingPurchaseObserver);
    }

    @Override
    protected void onDestroy() {
       //Unbind the service
        super.onDestroy();
        mBillingService.unbind();
    }

这样,所有购买者都与该服务对话,然后,该服务会将JSON请求发送到市场。您可能会认为购买是在同一瞬间完成的,但没有。您发送请求后,购买可能会在几分钟或几小时后到来。我认为这主要是因为服务器超负荷和信用卡审批。

然后,我有了一个包含商品的ListView,然后在每个商品上打开一个AlertDialog,邀请他们购买商品。当他们单击某个项目时,我会这样做:

  private class BuyButton implements DialogInterface.OnClickListener {

       private BillingItem item = null;
       private String developerPayload;

       public BuyButton(BillingItem item, String developerPayload) {
        this.item = item;
        this.developerPayload = developerPayload;
        }

            @Override
            public void onClick(DialogInterface dialog, int which) {

                if (GeneralHelper.isOnline(getApplicationContext())){
                    //I track the buy here with GA SDK. 

        mBillingService.requestPurchase(this.item.getSku(), this.developerPayload);             
                } else {                
                    Toast.makeText(getApplicationContext(), R.string.msg_not_online, Toast.LENGTH_SHORT).show();
                }

            }

        }

好了,您应该看到市场打开,用户可以完成或取消购买。

然后重要的是我的PurChaseObserver,它处理市场发送的所有事件。这是一个剥离的版本,但您应该明白这一点(请参阅代码中的我的评论):

private class BillingPurchaseObserver extends PurchaseObserver {
        public BillingPurchaseObserver(Handler handler) {
            super(Pro.this, handler);
        }

        @Override
        public void onBillingSupported(boolean supported) {

            if (supported) {
                //Enable buy functions. Not required, but you can do stuff here. The market first checks if billing is supported. Maybe your country is not supported, for example. 
            } else {
                Toast.makeText(getApplicationContext(), R.string.billing_not_supported, Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
                int quantity, long purchaseTime, String developerPayload) {

//This is the method that is called when the buy is completed or refunded I believe. 
// Here you can do something with the developerPayload. Its basically a Tag you can use to follow your transactions. i dont use it. 

        BillingItem item = BillingItem.getBySku(getApplicationContext(), itemId);

        if (purchaseState == PurchaseState.PURCHASED) {
            if (item != null){
//This is my own implementation that sets the item purchased in my database. BillingHelper is a class with methods I use to check if the user bought an option and update the UI. You should also check for refunded. You can see the Consts class to find what you need to check for. 

                    boolean resu = item.makePurchased(getApplicationContext());
                    if (resu){                      
                        Toast.makeText(getApplicationContext(), R.string.billing_item_purchased, Toast.LENGTH_LONG).show();
                    }
                }
            }
        }

        private void trackPurchase(BillingItem item, long purchaseTime) {           
            //My code to track the purchase in GA
        }

        @Override
        public void onRequestPurchaseResponse(RequestPurchase request,
                ResponseCode responseCode) {

               //This is the callback that happens when you sent the request. It doesnt mean you bought something. Just that the Market received it. 

            if (responseCode == ResponseCode.RESULT_OK) {               

                Toast.makeText(getApplicationContext(), R.string.billing_item_request_sent, Toast.LENGTH_SHORT).show();

            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                //The user canceled the item. 
            } else {
            //If it got here, the Market had an unexpected problem. 
            }
        }

        @Override
        public void onRestoreTransactionsResponse(RestoreTransactions request,
                ResponseCode responseCode) {
            if (responseCode == ResponseCode.RESULT_OK) {
//Restore transactions should only be run once in the lifecycle of your application unless you reinstalled the app or wipe the data. 

                SharedPreferences.Editor edit = PreferencesHelper.getInstance().getDefaultSettings(getApplicationContext()).edit();
                edit.putBoolean(Consts.DB_INITIALIZED, true);
                edit.commit();

            } else {
    //Something went wrong
            }
        }
    }

而且我相信您不需要编辑其他任何内容。其余代码“有效”。您可以首先在自己的“ android.test.purchased”项目中尝试使用示例SKU。到目前为止,我已经对此进行了测试,并且可以正常工作,但是我仍然需要涵盖退款状态之类的所有内容。在这种情况下,我让用户保留这些功能,但是我想确保在修改之前能完美工作。

希望对您和其他人有帮助。


1
简化样本的Kudos +1,以及我观察到的确切结果。但是...您是不是通过不严格遵循“官方”样本来冒险?毕竟,这些错误检查和无数的“可能的情况”是有原因的。没有?
Bill The Ape

@BillTheApe好吧,我只是简化了它。当您尝试理解该示例时,所有这些额外的行仅用于记录内容,从而使代码显得越来越难。毕竟,添加一条日志行很容易。
sfratini,2012年

@Sfratini谢谢,非常好,但是,当我按步骤进行应用演示时出现错误。无法导入任何内容。您能告诉我我怎么了吗?
2012年

4
没有诸如BillingService或BillingPurchaseObserver之类的东西。为什么没有关于此主题的好教程!
Bisclavret

可能是因为我在第一个版本中使用了它。也许他们伪造了API。对不起,我不必再次使用它。
sfratini


7

此处提供了完整的Android应用内结算v3的完整示例,并提供了屏幕截图。请查看教程: 使用ServiceConnection类的Android应用内结算v3

希望它会有所帮助。

有关更多说明,请完成本教程:在第3版API中实现应用内结算

在我们的项目中集成应用内结算库的步骤

更新您的AndroidManifest.xml文件。

创建一个ServiceConnection并将其绑定到IInAppBillingService。

将应用内结算请求从您的应用发送到IInAppBillingService。

处理来自Google Play的应用内结算响应。

更新AndroidManifest.xml

<uses-permission android:name="com.android.vending.BILLING" />

在Manifest.xml文件中添加权限

将AIDL文件添加到您的项目

构建您的应用程序。您应该在项目的/ gen目录中看到一个名为IInAppBillingService.java的生成文件。

更新build.gradle文件中的依赖项

apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.inducesmile.androidinapppurchase"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 2
        versionName "1.1"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.1.1'
    compile 'com.intuit.sdp:sdp-android:1.0.3'
    compile 'com.android.support:support-annotations:24.1.1'
    compile 'org.jetbrains:annotations-java5:15.0'
}

InAppPurchaseActivity.java和activity_in_app_purchase.xml

在这里,我们的应用程序用户将有机会进行应用程序内购买。在布局文件中,我们将为用户提供以不同面额购买的机会。

InAppPurchaseActivity.java

注意:应该在非UI线程中调用getAllUserPurchase()和itemPurchaseAvailability()方法,以避免应用崩溃。

public class InAppPurchaseActivity extends AppCompatActivity {
    private static final String TAG = InAppPurchaseActivity.class.getSimpleName();
    private IInAppBillingService mService;
    private CustomSharedPreference customSharedPreference;
    String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID};
    private ImageView buyOneButton, buyTwoButton, buyThreeButton;
    private static final char[] symbols = new char[36];
    static {
        for (int idx = 0; idx < 10; ++idx)
            symbols[idx] = (char) ('0' + idx);
        for (int idx = 10; idx < 36; ++idx)
            symbols[idx] = (char) ('a' + idx - 10);
    }
    private String appPackageName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_app_purchase);
        appPackageName = this.getPackageName();
        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
        serviceIntent.setPackage("com.android.vending");
        bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
        customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this);
        buyOneButton = (ImageView)findViewById(R.id.buy_one);
        buyOneButton.setVisibility(View.GONE);
        assert buyOneButton != null;
        buyOneButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_ONE_ID);
            }
        });
        buyTwoButton = (ImageView)findViewById(R.id.buy_two);
        buyTwoButton.setVisibility(View.GONE);
        assert buyTwoButton != null;
        buyTwoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_TWO_ID);
            }
        });
        buyThreeButton = (ImageView)findViewById(R.id.buy_three);
        buyThreeButton.setVisibility(View.GONE);
        assert buyThreeButton != null;
        buyThreeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_THREE_ID);
            }
        });
    }
    ServiceConnection mServiceConn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = IInAppBillingService.Stub.asInterface(service);
            AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName);
            mAsyncTask.execute();
        }
    };
    private void purchaseItem(String sku){
        String generatedPayload = getPayLoad();
        customSharedPreference.setDeveloperPayLoad(generatedPayload);
        try {
            Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload);
            PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
            try {
                startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == Helper.RESPONSE_CODE) {
            int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
            String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
            String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
            if (resultCode == RESULT_OK) {
                try {
                    JSONObject purchaseJsonObject = new JSONObject(purchaseData);
                    String sku = purchaseJsonObject.getString("productId");
                    String developerPayload = purchaseJsonObject.getString("developerPayload");
                    String purchaseToken = purchaseJsonObject.getString("purchaseToken");
                    //the developerPayload value is better stored in remote database but in this tutorial
                    //we will use a shared preference
                    for(int i = 0; i < productIds.length; i++){
                        if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){
                            customSharedPreference.setPurchaseToken(purchaseToken);
                            //access to private content
                            Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class);
                            startActivity(contentIntent);
                        }
                    }
                }
                catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private String getPayLoad(){
        RandomString randomString = new RandomString(36);
        String payload = randomString.nextString();
        return payload;
    }
    public class RandomString {
        private final Random random = new Random();
        private final char[] buf;
        public RandomString(int length) {
            if (length < 1)
                throw new IllegalArgumentException("length < 1: " + length);
            buf = new char[length];
        }
        public String nextString() {
            for (int idx = 0; idx < buf.length; ++idx)
                buf[idx] = symbols[random.nextInt(symbols.length)];
            return new String(buf);
        }
    }
    public final class SessionIdentifierGenerator {
        private SecureRandom random = new SecureRandom();
        public String nextSessionId() {
            return new BigInteger(130, random).toString(32);
        }
    }
    private class AvailablePurchaseAsyncTask extends AsyncTask<Void, Void, Bundle> {
        String packageName;
        public AvailablePurchaseAsyncTask(String packageName){
            this.packageName = packageName;
        }
        @Override
        protected Bundle doInBackground(Void... voids) {
            ArrayList<String> skuList = new ArrayList<String>();
            skuList.add(Helper.ITEM_ONE_ID);
            skuList.add(Helper.ITEM_TWO_ID);
            skuList.add(Helper.ITEM_THREE_ID);
            Bundle query = new Bundle();
            query.putStringArrayList(Helper.ITEM_ID_LIST, skuList);
            Bundle skuDetails = null;
            try {
                skuDetails = mService.getSkuDetails(3, packageName, "inapp", query);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            return skuDetails;
        }
        @Override
        protected void onPostExecute(Bundle skuDetails) {
            List<AvailablePurchase> canPurchase = new ArrayList<AvailablePurchase>();
            int response = skuDetails.getInt("RESPONSE_CODE");
            if (response == 0) {
                ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
                if(responseList != null){
                    for (String thisResponse : responseList) {
                        JSONObject object = null;
                        try {
                            object = new JSONObject(thisResponse);
                            String sku = object.getString("productId");
                            String price = object.getString("price");
                            canPurchase.add(new AvailablePurchase(sku, price));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){
                buyOneButton.setVisibility(View.VISIBLE);
            }else{
                buyOneButton.setVisibility(View.GONE);
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){
                buyTwoButton.setVisibility(View.VISIBLE);
            }else{
                buyTwoButton.setVisibility(View.GONE);
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){
                buyThreeButton.setVisibility(View.VISIBLE);
            }else{
                buyThreeButton.setVisibility(View.GONE);
            }
        }
    }
    @org.jetbrains.annotations.Contract("null, _ -> false")
    private boolean checkIfPurchaseIsAvailable(List<AvailablePurchase> all, String productId){
        if(all == null){ return false;}
        for(int i = 0; i < all.size(); i++){
            if(all.get(i).getSku().equals(productId)){
                return true;
            }
        }
        return false;
    }
    public boolean isBillingSupported(){
        int response = 1;
        try {
            response = mService.isBillingSupported(3, getPackageName(), "inapp");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        if(response > 0){
            return false;
        }
        return true;
    }
    public void consumePurchaseItem(String purchaseToken){
        try {
            int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
            if(response != 0){
                return;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    public Bundle getAllUserPurchase(){
        Bundle ownedItems = null;
        try {
            ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return ownedItems;
    }
    public List<UserPurchaseItems> extractAllUserPurchase(Bundle ownedItems){
        List<UserPurchaseItems> mUserItems = new ArrayList<UserPurchaseItems>();
        int response = ownedItems.getInt("RESPONSE_CODE");
        if (response == 0) {
            ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
            ArrayList<String>  purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
            ArrayList<String>  signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
            String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");
            if(purchaseDataList != null){
                for (int i = 0; i < purchaseDataList.size(); ++i) {
                    String purchaseData = purchaseDataList.get(i);
                    assert signatureList != null;
                    String signature = signatureList.get(i);
                    assert ownedSkus != null;
                    String sku = ownedSkus.get(i);
                    UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature);
                    mUserItems.add(allItems);
                }
            }
        }
        return mUserItems;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mService != null) {
            unbindService(mServiceConn);
        }
    }
}

创建助手包目录

创建一个新的程序包文件夹并将其命名为助手。在包内,创建一个新的Java文件Helper.java。

Helper.java

public class Helper {
    public static final String ITEM_ID_LIST = "ITEM_ID_LIST";
    public static final String ITEM_ONE_ID = "productone";
    public static final String ITEM_TWO_ID = "producttwo";
    public static final String ITEM_THREE_ID = "productthree";
    public static final int RESPONSE_CODE = 1001;
    public static final String SHARED_PREF = "shared_pref";
    public static final String DEVELOPER_PAYLOAD = "developer_payload";
    public static final String PURCHASE_TOKEN = "purchase_token";
    public static void displayMessage(Context context, String message){
        Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();
    }
}

测试应用内结算购买

  1. 创建一个Google+帐户(不要使用主帐户)
  2. 添加将在您的组或社区中测试应用程序的用户。

应用内购买测试期间可能会遇到的错误

您要求的商品无法购买

解决方案–根据Stackoverflow中的AndreiBogdan的说法

所有的功劳归因Inducesmile教程

Android开发者博客还建议提供有关销售应用内商品的培训课程。要查看完整的实现并了解如何测试应用程序,请检查此教程:销售应用内商品


第一个链接,您应该将大多数代码添加到答案中,以防万一该链接失效
Zoe

@LunarWatcher我已经更新了答案。所有代码已添加。请检查。
天行者'16

2
@SkyWalker我在哪里 customSharedPreference上课?
TheQ


5

好的,这是其中没有太多在线文档的事情之一,因此,我将尽我所能逐步解释一切。取自我的博客文章,该文章的详细版本(包括屏幕截图)在Millibit上。无需再费周折,

第一步:权限这是最简单的步骤。导航到manifest.xml文件,并在标签下添加以下行:

<uses-permission android:name="com.android.vending.BILLING" />

这将使您的应用有权访问应用内结算。如果目标是API 22以上的版本,则需要确保在运行时授予此权限。

第二步: Play控制台现在,您需要将您的应用上传到Google Play控制台。我们尚未将应用发布给公众(请放心),我们只是将其上传到“测试版发布”部分,这将使我们能够测试应用内购买。我们需要这样做的原因是,Google需要上传您的APK的某个版本,才能使结算流程真正起作用。

  1. 前往https://play.google.com/apps/publish/

  2. 创建应用程序

  3. 请按照以下步骤设置您的应用

  4. 转到应用发布

  5. 导航到Beta

  6. 在Android Studio中为您的应用创建一个APK,然后将其上传到Play控制台中的Beta版

(发布之前,请确保您已经填写了商品详情,内容分级以及定价和分发)

  1. 按下魔术按钮(发布!)

第三步:设置项目好的,这是您必须复制和粘贴一堆文件的部分。

首先,抓取文件,下载并放在下面。src/main它应该将自身构建到一个文件夹中。接下来,抓取整个util文件夹并将其粘贴到其中,src/java folder.然后重新构建项目以解决错误。Util文件夹包含以下类:

  • IabBroadcastReceiver
  • IabException
  • IabHelper
  • IabResult
  • 库存
  • 采购
  • 安全
  • SkuDetails

第四步:创建产品

  1. 创建被管理产品

  2. 点击保存,制作一个“定价模板”

在这里,您将选择此产品的价格。您可以选择不同国家/地区的价格,或者只要选择价格下的所有国家/地区,即可自动调整价格:

  1. 确保上一次激活应用程序内产品并与控制台中的正确应用程序链接。

最后,记下产品的ID。我们将在接下来的几个步骤中使用此ID。

  1. 获取您的Base64EncodedString

转至“服务和API”并获取您的Base64EncodedString。将其复制并粘贴到记事本中的某个位置,以便您可以访问它。不要与任何人共享此内容,他们将能够使用它进行恶意操作。

第五步:最后!我们可以开始编码:我们将首先绑定到应用内计费库,并查询用户购买/未购买的商品。然后,我们将购买我们之前设置的产品。

首先,导入我们之前设置的所有内容:

import util.*;

现在,我们将使用一个名为mHelper的IabHelper对象,并使用此方法进行所有操作。

base64EncodedPublicKey = ""; //PUT YOUR BASE64KEY HERE

mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.enableDebugLogging(false); //set to false in real app


mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
    public void onIabSetupFinished(IabResult result) {
        if (!result.isSuccess()) {
            // Oh no, there was a problem.

            if (result.getResponse() == 3) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("In app billing")
                        .setMessage("This device is not compatible with In App Billing, so" +
                                " you may not be able to buy the premium version on your phone. ")
                        .setPositiveButton("Okay", null)
                        .show();
            }

            Log.v(TAG, "Problem setting up In-app Billing: " + result);
        } else {
            Log.v(TAG, "YAY, in app billing set up! " + result);
            try {
                mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener
            } catch (IabHelper.IabAsyncInProgressException e) {
                e.printStackTrace();
            }
        }
    }
});

好吧,让我分解一下这里发生的事情。基本上,我们调用“ startSetup”来初始化“ IabHelper”。如果设置成功,我们将查询用户已经购买了什么并将响应存储在中mGotInventoryListener,我们将在下面进行编码:

IabHelper.QueryInventoryFinishedListener mGotInventoryListener
        = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result,
                                         Inventory inventory) {

        i = inventory;

        if (result.isFailure()) {
            // handle error here

            Log.v(TAG, "failure in checking if user has purchases");
        } else {
            // does the user have the premium upgrade?
            if (inventory.hasPurchase("premium_version")) {

                premiumEditor.putBoolean("hasPremium", true);
                premiumEditor.commit(); 

                Log.v(TAG, "Has purchase, saving in storage");

            } else {

                premiumEditor.putBoolean("hasPremium", false);
                premiumEditor.commit();

                Log.v(TAG, "Doesn't have purchase, saving in storage");

            }
        }
    }
};

上面的代码很不言自明。基本上,它只是检查用户已经购买了什么。现在我们知道用户是否已经购买了我们的产品,我们知道是否要他们购买我们的产品!如果他们以前从未购买过我们的产品,让我们开始购买请求:

public void buyPremium() {
    try {

     mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
     mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener
    } catch (Exception e) {
        e.printStackTrace();

mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually




        new AlertDialog.Builder(MainActivity.this)
                .setTitle("Error")
                .setMessage("An error occurred in buying the premium version. Please try again.")
                .setPositiveButton("Okay", null)
                .show();
    }
}


    @Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

    // Pass on the activity result to the helper for handling

    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {

    }

    else 
        Log.d(TAG, "onActivityResult handled by IABUtil.");
    }

}

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
        = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {

        Log.v(TAG, "purchase finished");

        if (purchase != null) {

            if (purchase.getSku().equals("premium_version")) {

                Toast.makeText(MainActivity.this, "Purchase successful!", Toast.LENGTH_SHORT).show();

                premiumEditor.putBoolean("hasPremium", true);
                premiumEditor.commit();
            }
        } else {
            return;
        }
        if (result.isFailure()) {
            return;
        }
    }
};

在这里,我们购买具有以下内容的商品(具有我们先前在播放控制台中生成的ID):

 mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener

注意,我们传入mPurchaseFinishedListener了参数。这意味着购买结果将返回给此侦听器。然后,我们仅检查购买的商品是否为空,否则,向用户奖励他们购买的任何功能。

不要让听众泄漏!当应用程序销毁时,我们必须销毁它们。

@Override
public void onDestroy() {
    super.onDestroy();
    if (mHelper != null)
        try {
            mHelper.dispose();
            mHelper = null;

        } catch (IabHelper.IabAsyncInProgressException e) {
            e.printStackTrace();
        }
}

最后,如果您想消费购买的商品,然后再次购买,则可以轻松完成。例如,如果用户购买了一辆虚拟汽车的汽油,并且用完了。他们需要再次购买相同的产品,您可以通过消费以下商品来再次购买:

public void consume(){

    //MAKING A QUERY TO GET AN ACCURATE INVENTORY
    try {
        mHelper.flagEndAsync(); //If any async is going, make sure we have it stop eventually

        mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener

        if(i.getPurchase("gas")==null){
            Toast.makeText(this, "Already consumed!", Toast.LENGTH_SHORT).show();
        }
    } catch (IabHelper.IabAsyncInProgressException e) {
        e.printStackTrace();

        Toast.makeText(this, "Error, try again", Toast.LENGTH_SHORT).show();
        mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
    }

    //ACTUALLY CONSUMING
    try {
        mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually

        this.mHelper.consumeAsync(this.i.getPurchase("gas"), new IabHelper.OnConsumeFinishedListener() {
            public void onConsumeFinished(Purchase paramAnonymousPurchase, IabResult paramAnonymousIabResult) {
//resell the gas to them
            }
        });

        return;
    } catch (IabHelper.IabAsyncInProgressException localIabAsyncInProgressException) {
        localIabAsyncInProgressException.printStackTrace();
        Toast.makeText(this, "ASYNC IN PROGRESS ALREADY!!!!" +localIabAsyncInProgressException, Toast.LENGTH_LONG).show();
        Log.v("myTag", "ASYNC IN PROGRESS ALREADY!!!");

        mHelper.flagEndAsync();
    }
}

而已!您现在可以开始赚钱了。真的就是这么简单!

同样,如果您想要本教程的更详细版本,包括屏幕截图和图片,请访问此处原始文章。如果您还有其他问题,请在评论中让我知道。


感谢您的杰出工作!该代码具有许多易于修复的小错误。但我也得到以下信息:error: flagEndAsync() is not public in IabHelper; cannot be accessed from outside package
德米特里(Dmitry)

仅公开发布似乎并不正确。并非打算从包装外部使用它。
德米特里(Dmitry)'18

代码的第二个严重问题是:Unable to destroy activity java.lang.IllegalArgumentException: Service not registered.长按模拟器中的“切换应用程序”系统按钮会发生这种情况。
德米特里(Dmitry)

最糟糕的是catch子句没有帮助,应用程序崩溃了
德米特里(Dmitry)

通过以下方式修复:if(serviceBound){mContext.unbindService(mServiceConn); }(在mContext.bindService()附近添加serviceBound = true)。
德米特里(Dmitry)'18


1

我已经开发了使用“ com.android.billingclient:billing:2.1.0”的Android In app计费库。

这是它的属性:

  1. “ INAPP”支持库
  2. 以后将支持订阅!
  3. 库将Roomdb用于您的产品,您无需实施即可检查产品状态
  4. 库使用共享依赖项。您的应用程序将减小尺寸,并且不需要多义
  5. 库会在每次启动应用程序时检查您的产品状态。您可以获得状态(已购买或未购买)!
  6. 客户购买的每个产品都必须在“成功状态”中“确认”。图书馆正在为您做这件事!
  7. 图书馆支持(立即购买,响应迟到购买解决方案,响应延迟购买拒绝,用户取消购买)

图书馆资料

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.