如何在 Android 应用程序中实现应用内计费?

Posted

技术标签:

【中文标题】如何在 Android 应用程序中实现应用内计费?【英文标题】:How to implement In-App Billing in an Android application? 【发布时间】:2012-02-02 21:20:56 【问题描述】:

android 应用中实现应用内计费似乎相当复杂。我怎么能这样做? SDK 中的示例应用只有一个 Activity,这对于像我这样有多个 Activity 的应用来说过于简化了。

【问题讨论】:

+1 这个Android例子真的很复杂,有很多层,它涵盖了所有的功能,不管你是否应该从更小更简单的东西开始,但是没有任何文档。我确实有该示例的精简版本。请解释你在哪里卡住了,因为基础是你应该将活动绑定到计费服务并拨打电话。广播接收器将完成剩下的工作。 @sfratini 你能发布你的例子吗?谢谢! 工作很痛苦。如果不出意外,调试和正确处理需要几个小时。我有一个项目设置,就像一个工作的你好世界。试试看mcondev.wordpress.com/2011/06/26/… 我认为本教程 (youtu.be/El7q_1a_WVc) 最适合应用内结算。它教如何在 5 分钟内实现应用内计费功能! 这已经过时了。 【参考方案1】:

好吧,我会试着解释一下我的经历。我不认为自己是这方面的专家,但我几天都摔断了头。

对于初学者,我在尝试理解示例和应用程序的工作流程时遇到了非常糟糕的情况。我认为最好从一个简单的示例开始,但是很难将代码分成小块并且不知道您是否破坏了任何东西。我会告诉你我所拥有的以及我从示例中所做的更改以使其正常工作。

我有一个 Activity,我的所有购买都来自该 Activity。它被称为专业版。

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

好吧,我将我的 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,它处理市场发送的所有事件。这是它的精简版,但您应该明白这一点(通过代码查看我的 cmets):

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 简化样本并准确观察我所观察到的内容。但是……您不是在不严格遵循“官方”样本的情况下冒险吗?毕竟,那些错误检查和无数“可能的场景”是有原因的。没有? @BillTheApe 好吧,我只是让它变得更简单。当你试图理解这个例子时,所有这些额外的行只是为了记录一些东西,使代码看起来更难更长。毕竟,添加日志行很容易。 @Sfratini 谢谢,它很好,但是当我将应用程序演示作为你的步骤时,我得到了错误。不导入任何东西。你能告诉我我有什么问题吗? 没有 BillingService 或 BillingPurchaseObserver 这样的东西。为什么没有关于这个话题的好教程! 可能是因为我在第一个版本中使用了它。也许他们取消了 API。我不必再次使用它,对不起。【参考方案2】:

V3:这里是一个快速入门的教程。他正在使用来自 google 示例(Trivial Drive)的帮助类......就像第一个“Hello Billing”一样好......

http://www.techotopia.com/index.php/Integrating_Google_Play_In-app_Billing_into_an_Android_Application_%E2%80%93_A_Tutorial

【讨论】:

【参考方案3】:

这里有一个完整的 Android In-App Billing v3 示例,并附有屏幕截图。请查看教程: Android In-App Billing v3 using ServiceConnection Class

希望它会有所帮助。

要了解更多信息,请阅读本教程:Implementing In-app Billing in Version 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

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

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);
        
    

创建帮助程序包目录

创建一个新的包文件夹并将其命名为 helpers。在包中,新建一个 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();
    

测试应用内结算购买

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

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

您要求的商品无法购买

解决方案——According to AndreiBogdan in ***,

所有功劳都归于Inducesmile 他的tutorial

Android 开发者博客还推荐了有关销售应用内产品的培训课程。要查看完整的实现并了解如何测试应用程序,请查看本教程:Selling In-app Products

【讨论】:

第一个链接,你应该将大部分代码添加到答案中,以防链接失效 @LunarWatcher 我已经更新了答案。所有代码均已添加。请检查。 @SkyWalker 我在哪里可以获得customSharedPreference 课程? @SkyWalker 使用 google inapp purchase 有什么缺点?【参考方案4】:

好的,这是其中没有太多在线文档可用的内容之一,因此我将尽力逐步解释所有内容。摘自我的博客文章,这是一个更详细的版本(带有屏幕截图),here on The Millibit。废话不多说,

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

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

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

第二步: Play 管理中心 现在您需要将您的应用上传到 Google Play 控制台。我们还没有向公众发布我们的应用程序(别担心),我们只是将它上传到 BETA RELEASE 部分,这将允许我们测试应用程序内购买。我们需要这样做的原因是,Google 需要上传您的 APK 的某个版本,才能让计费流程真正发挥作用。

    转到https://play.google.com/apps/publish/

    创建应用程序

    按照步骤设置您的应用

    转到应用版本

    导航到测试版

    在 Android Studio 中为您的应用创建一个 APK,并将其上传到 Play 管理中心的 Beta 版

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

    点击神奇按钮(发布!)

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

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

IabBroadcastReceiver IabException IabHelper Iab结果 库存 购买 安全性 Sku详细信息

第四步:创建产品

    创建被管理产品

    点击保存并制作“定价模板”

在这里,您将选择该产品的价格。您可以选择不同国家的价格,或者如果您只选择您的价格下的所有国家,它会自动调整:

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

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

    获取您的 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();
    

就是这样!你现在可以开始赚钱了。真的就这么简单!

同样,如果您想要本教程的更详细版本,包括屏幕截图和图片,请访问original post here。如果您还有任何问题,请在 cmets 中告诉我。

【讨论】:

感谢您的出色工作!该代码有许多易于修复的小错误。但我也得到以下信息:error: flagEndAsync() is not public in IabHelper; cannot be accessed from outside package. 仅仅公开似乎并不正确。它不打算在包装外使用。 代码的第二个严重问题:Unable to destroy activity java.lang.IllegalArgumentException: Service not registered. 它发生在长按模拟器中的“切换应用程序”系统按钮时。 最糟糕的是,catch 子句没有帮助,应用程序崩溃 修复:if (serviceBound) mContext.unbindService(mServiceConn); (在 mContext.bindService() 附近添加 serviceBound = true)。【参考方案5】:

要更好地了解如何使用 google play 计费库进行应用内计费,请参阅以下流程图:

您可以按照我在本文中解释的逐步集成:

https://medium.com/@surabhichoudhary/in-app-purchasing-with-google-play-billing-library-6a72e289a78e

如果您需要演示,这是项目链接:https://github.com/surabhi6/InAppPurchaseDemo

【讨论】:

【参考方案6】:

如果您想使用一个简单的库在 Google Play 和 Amazon Appstore 上发布,您可以选择RoboBillingLibrary。它将两者的细节抽象到一个易于使用的库中。详细说明在 Github 页面上。

【讨论】:

【参考方案7】:

我开发了使用“com.android.billingclient:billing:2.1.0”的Android应用内计费库

这是它的属性:

    “INAPP”支持库 以后会支持订阅! 库为您的产品使用 Roomdb,您无需实施即可检查产品状态 库使用共享依赖。您的应用会更小,并且不需要 multidex 每次应用启动时,库都会检查您的产品状态。您可以获取状态(已购买或未购买)! 客户购买的每件产品都需要在 SUCCES 状态下得到“确认”。图书馆正在为您制作这个! 图书馆支持(立即购买、响应延迟购买成功、响应延迟购买拒绝、用户取消购买)

library source

【讨论】:

以上是关于如何在 Android 应用程序中实现应用内计费?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android Studio 中实现 razorpay 定期付款?

Android:在应用计费增值税

如何处理应用内计费库中的多个用户? (最佳实践)

在 Android 中实现应用内购买?

如何在 Flutter 中实现应用内购买订阅?

Android 应用内计费如何以及在何处指定付费功能费用