官方文档
使用google在购买某个商品后,如果没有对该商品进行消费,是不可以再次购买的,需要对已购商品进行消费后才能再次购买
提供永久性效果的产品,不能重新购买,不应表明为已消费
提供临时利益,可以回购的产品
Version 3 API 中的典型购买流程如下所示:
1.您的应用向 Google Play 发送 isBillingSupported 请求,以确定您当前使用的 In-app Billing API 目标版本是否受支持。
2.当您的应用启动或用户登录时,最好向 Google Play 进行查询,确定该用户拥有哪些商品。 要查询用户的应用内购买,请发送 getPurchases 请求。 如果请求成功,Google Play 会返回一个 Bundle,其中包含所购商品的商品 ID 列表、各项购买详情的列表以及购买签名的列表。
3.通常情况下,您需要将可供购买的商品通知用户。 要查询您在 Google Play 中定义的应用内商品的详细信息,应用可以发送 getSkuDetails 请求。 您必须在查询请求中指定商品 ID 列表。如果该请求成功,Google Play 会返回一个包含产品详情(包括商品的价格、标题、说明和购买类型)的 Bundle。
4.如果用户还未拥有某种应用内商品,您可以提示购买。 为了发起购买请求,您的应用会发送 getBuyIntent 请求,指定要购买商品的商品 ID 以及其他参数。 当您在 Developer Console 中创建新的应用内商品时,应记录其商品 ID。
a.Google Play 返回的 Bundle 中包含 PendingIntent,您的应用可用它来启动购买结账 UI。
b.您的应用通过调用 startIntentSenderForResult 方法启动待定 Intent。
c.结账流程结束后(即用户成功购买商品或取消购买),Google Play 会向您的 onActivityResult 方法发送响应 Intent。 onActivityResult 的结果代码中有一个代码将用于表明购买是成功还是已取消。 响应 Intent 中包含所购商品的相关信息,包括 Google Play 为了对此次购买交易进行唯一标识而生成的 purchaseToken 字符串。 Intent 中还包含使用您的开发者私钥签署的购买签名。
dependencies {
...
implementation 'com.android.billingclient:billing:1.2'
}
创建BillingClient实例并实现监听,开始与google play的连接
// create new Person
private BillingClient mBillingClient;
...
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@BillingResponse int billingResponseCode) {
if (billingResponseCode == BillingResponse.OK) {
// The billing client is ready. You can query purchases here.
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
同过商品的id来查询对应的商品信息
List skuList = new ArrayList<> ();
skuList.add("xxxxx"); //商品id
skuList.add("xxxxx"); //商品id
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(int responseCode, List skuDetailsList) {
// Process the result.
}
});
在发起支付页面之前,需要通过isFeatureSupported方法检查设备是否支持该商品的销售,可以通过BillingClient.FeatureType查询商品类型
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku(skuId)
.setType(SkuType.INAPP) // SkuType.SUB for subscription
.build();
int responseCode = mBillingClient.launchBillingFlow(flowParams);
在建立连接那一步中,通过setListener(this),使当前类实现了回调监听的接口
@Override
void onPurchasesUpdated(@BillingResponse int responseCode, List purchases) {
if (responseCode == BillingResponse.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (responseCode == BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
} else {
// Handle any other error codes.
}
}
当用户购买商品完成后,可以获取到已购的商品列表
mBillingClient.queryPurchaseHistoryAsync(SkuType.INAPP,
new PurchaseHistoryResponseListener() {
@Override
public void onPurchaseHistoryResponse(@BillingResponse int responseCode,
List purchasesList) {
if (responseCode == BillingResponse.OK
&& purchasesList != null) {
for (Purchase purchase : purchasesList) {
// Process the result.
}
}
}
});
在购买完成之后,可以获取到已购商品的purchaseToken,consumeAsync消费已购商品
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(@BillingResponse int responseCode, String outToken) {
if (responseCode == BillingResponse.OK) {
// Handle the success of the consume operation.
// For example, increase the number of coins inside the user's basket.
}
};
mBillingClient.consumeAsync(purchaseToken, listener);
上面知道,我们可以通过依赖google pay库实现,通过SDK实现支付。除此之外还有另外一种实现方式,也就是通过定义AIDL接口,实现应用间的跨进程通信,从而实现支付
AIDL,Android接口定义语言,是为了两个不同的应用间通过Serivce进行通信。也就是说google play对外提供远程Service,其他应用可以并发地访问该Service
一般来说,AIDL文件放在main/com.xxx.xx/下面,然后然后定义对外提供的方法,但注意,它有自己的语法
例如 IServerSerivce.aidl
interface IServerSerivce {
String getName(int id);
}
重新构建一下project会在builde-resource-gen…下面生成一个IServerSerivce.java文件
这个文件里面有一个内部静态抽象类Stub,接下来我们需要实现它
例如ServerSerivce.java
import com.xxx.xx.IServerSerivce;
public class ServerSerivce extends Service {
/**
* 返回ServerSerivce代理对象IBinder给客户端使用
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IServerSerivce.Stub mBinder = new IServerSerivce.Stub() {
@Override
public String getName(int id) thorws RemoteException {
return new String("同学"+id);
}
}
}
在AndroidManifest.xml中注册该Serivice。其中还要action属性,用于隐式启动该服务
<service
android:name=".service.ServerService"
android:exported="true">
<intent-filter>
<action android:name="com.xxx.xxx"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
service>
在第三方应用中同样需要AIDL文件,而且和服务端的AIDL一模一样。
第一步 建立服务连接
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 这里的IBinder对象service是代理对象,所以必须调用下面的方法转换成AIDL接口对象
mServerService = IRemoteService.Stub.asInterface(service);
System.out.println("bind success! " + mServerService.toString());
}
@Override
public void onServiceDisconnected(ComponentName name) {
mServerService = null;
System.out.println(mServerService.toString() +" disconnected! ");
}
};
第二步 通过隐式意图绑定远程服务
// 连接绑定远程服务
Intent intent = new Intent();
// action值为远程服务的action,即上面我们在服务端应用清单文件的action
intent.setAction("com.xx.xxx"); //指定的action
intent.setPackage("com.xxx.xx"); //服务端应用的包名
isConnSuccess = bindService(intent, conn, Context.BIND_AUTO_CREATE); //
第三步 在onDestroy解除服务
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
接下来就可以通过mServerService来访问数据。
首先在下载google给出的示例代码
把项目里面的AIDL文件拷贝到自己的项目中,文件路径也要一致
把util的类都拷贝到自己的项目中。service的连接,访问都被封装到了IabHelper类中
第一步 查询记录回调
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
complain("Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
// Do we have the premium upgrade?
Purchase premiumPurchase = inventory.getPurchase(purchaseId);
mPurchase = premiumPurchase;
mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
alert("User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
updateUi();
setWaitScreen(false);
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
在调用查询方法后回调,需要根据自己的情况对查询结果进行处理
第二步 购买成功回调
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
complain("Error purchasing: " + result);
setWaitScreen(false);
return;
}
if (!verifyDeveloperPayload(purchase)) {
complain("Error purchasing. Authenticity verification failed.");
setWaitScreen(false);
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(purchaseId)) {
// bought the premium upgrade!
Log.d(TAG, "Purchase is premium upgrade. Congratulating user.");
alert("Thank you for upgrading to premium!");
mIsPremium = true;
updateUi();
setWaitScreen(false);
}
}
};
第三步 消费商品回调
// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
// We know this is the "gas" sku because it's the only one we consume,
// so we don't check which sku was consumed. If you have more than one
// sku, you probably should check...
if (result.isSuccess()) {
// successfully consumed, so we apply the effects of the item in our
// game world's logic, which in our case means filling the gas tank a bit
Log.d(TAG, "Consumption successful. Provisioning.");
// mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;
saveData();
alert("Consumption successful");
}
else {
complain("Error while consuming: " + result);
}
updateUi();
setWaitScreen(false);
Log.d(TAG, "End consumption flow.");
}
};
第四步 初始化和解除
public void init() {
mHelper = new IabHelper(mContext, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
complain("Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Important: Dynamically register for broadcast messages about updated purchases.
// We register the receiver here instead of as a in the Manifest
// because we always call getPurchases() at startup, so therefore we can ignore
// any broadcasts sent while the app isn't running.
// Note: registering this listener in an Activity is a bad idea, but is done here
// because this is a SAMPLE. Regardless, the receiver must be registered after
// IabHelper is setup, but before first call to getPurchases().
// mBroadcastReceiver = new IabBroadcastReceiver(MainActivity.this);
IntentFilter broadcastFilter = new IntentFilter(IabBroadcastReceiver.ACTION);
// registerReceiver(mBroadcastReceiver, broadcastFilter);
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
try {
mHelper.queryInventoryAsync(mGotInventoryListener);
} catch (IabHelper.IabAsyncInProgressException e) {
complain("Error querying inventory. Another async operation in progress.");
}
}
});
}
@Override
public void onDestroy() {
// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) {
mHelper.disposeWhenFinished();
mHelper = null;
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
第四步 调用支付、查询、消费等方法
方法名 | 类型 | 所需参数 | 含义 |
---|---|---|---|
launchPurchaseFlow | void | Activity act, String sku, int requestCode,OnIabPurchaseFinishedListener,String extraData | 唤起支付页面 |
queryInventoryAsync | void | QueryInventoryFinishedListener listener | 查询订单记录 |
consumeAsync | void | Purchase purchase, OnConsumeFinishedListener listener | 消费指定商品 |
关于IabHelper
在IabHelper中,它已经帮我们把与远程服务通信的连接,等操作给给封装起来,可以让我们使用者只需要关注实现方法本身,而不用处理服务的连接等,还有一些逻辑的处理等,接下来看看它封装了哪些方法吧
startSetup
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can't do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed) return;
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
try {
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren't supported, neither are subscriptions
mSubscriptionsSupported = false;
mSubscriptionUpdateSupported = false;
return;
} else {
logDebug("In-app billing version 3 supported for " + packageName);
}
// Check for v5 subscriptions support. This is needed for
// getBuyIntentToReplaceSku which allows for subscription update
response = mService.isBillingSupported(5, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscription re-signup AVAILABLE.");
mSubscriptionUpdateSupported = true;
} else {
logDebug("Subscription re-signup not available.");
mSubscriptionUpdateSupported = false;
}
if (mSubscriptionUpdateSupported) {
mSubscriptionsSupported = true;
} else {
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported = true;
} else {
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
mSubscriptionsSupported = false;
mSubscriptionUpdateSupported = false;
}
}
mSetupDone = true;
}
catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
}
e.printStackTrace();
return;
}
if (listener != null) {
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
}
}
};
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> intentServices = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}
else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}
在startSetup中,它判断了应用是否支持google pay,然后实现了Service的连接
launchPurchaseFlow
public void launchPurchaseFlow(Activity act, String sku, String itemType, List<String> oldSkus,
int requestCode, OnIabPurchaseFinishedListener listener, String extraData)
throws IabAsyncInProgressException {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle;
if (oldSkus == null || oldSkus.isEmpty()) {
// Purchasing a new item or subscription re-signup
buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType,
extraData);
} else {
// Subscription upgrade/downgrade
if (!mSubscriptionUpdateSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE,
"Subscription updates are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
buyIntentBundle = mService.getBuyIntentToReplaceSkus(5, mContext.getPackageName(),
oldSkus, sku, itemType, extraData);
}
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
由上,在我们的程序调用了launchPurchaseFlow方法后,它会先对参数进行判断,判断商品的类型,然后调用service中的购买商品的方法,接收回调,对回调进行分析处理
接下来的方法就不再介绍了,基本是大同小异。相信总结完这么多也基本可以理解使用了。