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

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

52708 次浏览

我会试着解释我经历了什么。我不认为自己是这方面的专家,但我几天前摔破了头。

对于初学者来说,我在试图理解示例和应用程序的工作流程时遇到了非常糟糕的情况。我认为最好从一个简单的示例开始,但是很难将代码分成小块,并且不知道是否破坏了什么。我将告诉你们我得到了什么,以及我从这个例子中改变了什么来使它工作。

我有一个单一的活动,我所有的购买来自。这是所谓的专业。

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

好的,我将我的活动绑定到我的账单服务,如下所示:

      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 请求发送到市场。你可能认为购买是在同一时间进行的,但事实并非如此。您发送请求,购买可能在几分钟或几小时后到来。我认为这主要是服务器超载和信用卡审批。

然后,我有一个列表视图与我的项目,我打开一个 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.ordering”中使用示例 SKU。到目前为止,我已经测试了这一点,它的工作,但我仍然需要涵盖一切,如退款状态。在这种情况下,我允许用户保留这些特性,但是我希望在修改它之前确保它能够完美工作。

我希望它能帮助你和其他人。

如果你想使用一个简单的库在 Google Play 和 Amazon Appstore 上发布,你可以使用 机器人比林图书馆。它将两者的细节抽象为一个易于使用的库。详细说明在 Github 页面上。

V3: 这里是一个快速入门的教程。.他正在使用 google 示例中的 helper-class (TrivialDrive) ... 和第一个“ Hello Billing”一样好。.

Http://www.techotopia.com/index.php/integrating_google_play_in-app_billing_into_an_android_application_%e2%80%93_a_tutorial

有一个完整的例子 Android In-App Billingv3一步一步地给出在这里与截图。请检查教程: 使用 ServiceConnection 类的 Android In-App Billingv3

希望能有所帮助。

要了解更多信息,请阅读本教程: 在版本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'
}

在 AppPurchaseActivity.java 和 activity _ in _ app _ buy. xml 中

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

在 AppPurchaseActivity.java 中

注意: 为了避免应用程序崩溃,应该在非 UI 线程中调用 getAllUserPurbuy ()和 itemPurchaseUtiability ()方法。

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。

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 所说,

所有功劳都归于 诱导微笑他的 教程

Android Developer Blog 还推荐了一个关于销售应用内产品的培训课程。要查看完整的实现并学习如何测试应用程序,请检查本教程: 销售应用程序内产品

好吧,这就是那些在网上没有很多文档可用的事情之一,所以我将尽我所能一步一步地解释每件事情。摘自我的博客文章,这是一个更详细的版本(截图) ,这里是 The Millibit。闲话少说,

第一步: 许可 这是最简单的步骤:

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

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

第二步: 播放控制台 现在你需要把你的应用上传到谷歌播放控制台。我们还没有向公众发布我们的应用程序(不要担心) ,我们只是将它上传到 BETA 释放部分,这将允许我们测试在应用程序购买。我们需要这样做的原因是,谷歌需要有一些版本的您的 APK 上传的帐单过程实际工作。

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

  2. 创建应用程序

  3. 按照以下步骤设置应用程序

  4. 转到应用程序发布版本

  5. 导航到测试版

  6. 在 Android 工作室中创建应用程序的 APK,并将其上传到 Play Console 中的 Beta 产品

(在发布之前,请确保您已经填写了商店列表、内容评级、定价和分配)

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

第三步: 安装项目 好了,现在你需要复制粘贴一堆文件。

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

  • IabBroadcastReceiver
  • 例外
  • IabHelper
  • 化验结果
  • 库存
  • 购买
  • 保安
  • SkuDetails

第四步: 创造产品

  1. 创建管理产品

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

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

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

最后,请注意产品的 ID。我们将在接下来的几个步骤中使用这个 ID。

  1. 获取 Base64EncodedString

转到“ Services & 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中,接下来我们将对 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();
}
}

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

同样,如果你想要本教程的更详细的版本,带有截图和图片,请访问 原帖在此。如果你还有其他问题,请在评论中告诉我。

我已经开发了 Android In 应用程序账单库,它使用了“ com.Android.bilingclient: billing: 2.1.0”

以下是它的特性:

  1. “ INAPP”支持库
  2. 稍后将支持订阅!
  3. 库为您的产品使用 Roomdb,您不需要实现来检查您的产品的状态
  4. 图书馆使用共享依赖。您的应用程序将减少大小和不需要多索引
  5. 每次应用程序启动时,库都会检查您的产品状态。您可以获得状态(购买或不购买) !
  6. 每一个客户购买的产品都需要在 SUCCES State 得到“认可”,图书馆正在为你制作这个!
  7. 图书馆支持(即时购买,响应延迟购买成功,响应延迟购买拒绝,用户取消购买)

图书馆资料来源

为了更好地理解如何在应用程序内计费工程使用谷歌播放计费库,请参考下面的流程图:

enter image description here

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

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

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

基本上,您需要在两个地方的购买代码

首先在你的主要活动地点 你的应用程序每次都会打开,所以检查那里的购买状态。需要实现购买更新的监听器。

其次当按钮被点击启动购买。所以你的按钮驻留其活动需要 实现采购更新侦听器。 如果您的按钮位于 MainActivity 之下,那么您只需要在一个地方(即 MainActivity)使用购买代码。

你可以点击这里查看我的工作指南:

Https://programtown.com/how-to-make-multiple-in-app-purchase-in-android-using-google-play-billing-library/