Android 集成Google支付,目前有两种方式,一种是使用Google Play结算库,另一种是使用AIDL进行应用内购买结算。今天我们来说一下如何使用Google play结算库结算,另一种请见:Android集成Google支付,以及遇到的坑、坑
使用Google Play结算库比使用AIDL相对简单很多,但是Google废弃了一个关键字段developerPayload,下面会说到。
Google支付官方给的demo GitHub地址,可以去拉下来瞅瞅。https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive_v2
本文只介绍Google结算库的接入,至于Google后台的配置及BASE_64_ENCODED_PUBLIC_KEY获取,可以去Google后台看看。
一:官方API接入方式:Google支付官方API
我们先按Google API接一遍,先了解一下原理,后面有使用demo中的方法接入的Google支付,使用很简单,如果不想看官方API可以直接看第二步,代码是一样的,只不过封装的好一点,demo中把重连什么的都封装好了。
1.首先在app的build.gradle中添加依赖:
//Google 支付
compile 'com.android.billingclient:billing:1.2'
2.Connect to Google Play
在每次支付之前,都需要链接到Google Play:
1):调用 newBuilder() 方法创建BillingClient类的实例,也就是Play Billing Library client。还必须调用setListener()方法,并将引用传递给PurchasesUpdatedListener的实例,以便接收应用程序启动的购买以及谷歌Play Store启动的购买的更新。
2):建立与Google Play的连接。设置过程是异步的,你必须实现一个BillingClientStateListener,以便在客户端发出进一步请求后接收回调。
3):重写onBillingServiceDisconnected()回调方法,并实现自己的重试策略,以便在客户端丢失连接时处理到谷歌Play的连接丢失。如果要是与Google链接失败,或者在支付过程中断开了,可以在这个方法中重连。其实Demo中已经封装好了,使用很方便。
下面是代码:
// create new Person
private BillingClient billingClient;
...
billingClient = BillingClient.newBuilder(activity).setListener(this).build();
billingClient.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.
}
});
3.查询应用程序内的所有产品信息
去查询应用程序内的所有产品信息,也就是查一下你要购买的产品是否在Google后台配置了。我的产品ID是在应用内写死的,Google后台也配置过了,所以没管这个查询操作。
skuList中添加的是你的产品ID。SkuType是产品类型。SkuType.INAPP(用于一次性产品或奖励产品)或SkuType.SUBS(订阅)。
List<String> skuList = new ArrayList<> ();
skuList.add("premium_upgrade");
skuList.add("gas");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
// Process the result.
}
});
下面是对查询结果的处理:如果请求成功,则响应代码为BillingResponse.OK。
if (responseCode == BillingResponse.OK
&& skuDetailsList != null) {
for (SkuDetails skuDetails : skuDetailsList) {
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if ("premium_upgrade".equals(sku)) {
premiumUpgradePrice = price;
} else if ("gas".equals(sku)) {
gasPrice = price;
}
}
}
查询到的结果:
4:开始购买应用内产品
(注:有些老手机可能不支持你的产品,比如订阅,你调用isFeatureSupported()方法检查设备是否支持要销售的产品。)
要从应用程序启动购买请求,请从UI线程调用launchBillingFlow()方法:
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
int responseCode = billingClient.launchBillingFlow(flowParams);
当您调用launchBillingFlow()方法时,系统将显示Google 购买一次性产品截图:
下面是购买订阅的截图:
5:接受购买成功的回调:
购买完成之后可以拿到Google返回的订单信息,自己做一些处理。还要做消耗操作,如果不消耗,下次不能购买好像。
@Override
void onPurchasesUpdated(@BillingResponse int responseCode, List<Purchase> 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.
}
}
purchase中就是订单信息。可以从中获取到订单的ID和产品ID等,发给服务器验证。
二:使用官方的Demo
1:还是先导入依赖:
//Google 支付
compile 'com.android.billingclient:billing:1.2'
2:把Demo中shared-module的billing文件夹中的代码拷到项目中:
https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive_v2
3:在支付的Activity中:
首先Activity 要 implements BillingProvider
然后在Activity中添加一下方法:
/**
* Google支付内容**************************************
*/
//Google支付
private BillingManager mBillingManager;
private BillingProvider mBillingProvider;
@Override
public BillingManager getBillingManager() {
return mBillingManager;
}
@Override
public boolean isPremiumPurchased() {
return false;
}
/**
* 购买事件
* purchaseId:产品ID
*/
public void googlePay(String purchaseId) {
mBillingProvider.getBillingManager().initiatePurchaseFlow(purchaseId,"", BillingClient.SkuType.INAPP);
}
/**
* Handler to billing updates
*/
private class UpdateListener implements BillingManager.BillingUpdatesListener {
@Override
public void onBillingClientSetupFinished() {
//通过商品ID,去查询Google后台是否有该ID的商品
final List skuList = new ArrayList<>();
skuList.add(purchaseId);
mBillingProvider.getBillingManager().querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuList, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (responseCode != BillingClient.BillingResponse.OK) {
} else if (skuDetailsList != null && skuDetailsList.size() > 0) {
for (SkuDetails details : skuDetailsList) {
//获取到所查商品信息
}
} else {
//没有要消耗的产品
// Toast.makeText(PayActivity.this, "没有要查询的产品", Toast.LENGTH_LONG).show();
}
}
});
}
@Override
public void onConsumeFinished(String token, @BillingClient.BillingResponse int result) {
// Toast.makeText(PayActivity.this, "消耗完成", Toast.LENGTH_LONG).show();
// Note: We know this is the SKU_GAS, because it's the only one we consume, so we don't
// check if token corresponding to the expected sku was consumed.
// If you have more than one sku, you probably need to validate that the token matches
// the SKU you expect.
// It could be done by maintaining a map (updating it every time you call consumeAsync)
// of all tokens into SKUs which were scheduled to be consumed and then looking through
// it here to check which SKU corresponds to a consumed token.
if (result == BillingClient.BillingResponse.OK) {
// 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
//消耗成功
// Toast.makeText(PayActivity.this, "消耗成功", Toast.LENGTH_LONG).show();
} else {
// Toast.makeText(PayActivity.this, "消耗失败", Toast.LENGTH_LONG).show();
}
}
@Override
public void onPurchasesUpdated(List<Purchase> purchaseList) {
for (Purchase purchase : purchaseList) {
//拿到订单信息,做自己的处理,发生到服务端验证订单信息,然后去消耗
//购买成功,拿着令牌 去消耗
// Toast.makeText(PayActivity.this, "购买成功:" + purchase.getPurchaseToken(), Toast.LENGTH_LONG).show();
// We should consume the purchase and fill up the tank once it was consumed
mBillingProvider.getBillingManager().consumeAsync(purchase.getPurchaseToken());
}
}
}
/**
* Google支付内容**************************************
*/
要购买直接调用googlePay()方法,把产品ID传进去,就可以调起Google支付了。
注:Google的BASE_64_ENCODED_PUBLIC_KEY,是在BillingManager.java中添加的,BASE_64_ENCODED_PUBLIC_KEY的获取,是从Google后台获取的。
OK了,就这么多,代码不多。
三:下面我们来说说developerPayload吧
使用AIDL进行应用内购买结算,客户端可以传一个developerPayload作为额外参数传给Google,在用户支付成功之后的订单信息里,也会有这个字段,服务端可以根据这个额外参数来判断是哪个用户购买的,从而进行处理。但是使用Google play结算库结算,不能添加这个字段了,我们可以在Google依赖库中的com/android/billingclient/api/BillingClientImpl.java看到这个字段已经被弃用:第80行和第88行
@Override
public int launchBillingFlow(Activity activity, BillingFlowParams params) {
if (!isReady()) {
return broadcastFailureAndReturnBillingResponse(BillingResponse.SERVICE_DISCONNECTED);
}
@SkuType String skuType = params.getSkuType();
String newSku = params.getSku();
SkuDetails skuDetails = params.getSkuDetails();
boolean rewardedSku = skuDetails != null && skuDetails.isRewarded();
// Checking for mandatory params fields
if (newSku == null) {
BillingHelper.logWarn(TAG, "Please fix the input params. SKU can't be null.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.DEVELOPER_ERROR);
}
if (skuType == null) {
BillingHelper.logWarn(TAG, "Please fix the input params. SkuType can't be null.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.DEVELOPER_ERROR);
}
// Checking for requested features support
if (skuType.equals(SkuType.SUBS) && !mSubscriptionsSupported) {
BillingHelper.logWarn(TAG, "Current client doesn't support subscriptions.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.FEATURE_NOT_SUPPORTED);
}
boolean isSubscriptionUpdate = (params.getOldSku() != null);
if (isSubscriptionUpdate && !mSubscriptionUpdateSupported) {
BillingHelper.logWarn(TAG, "Current client doesn't support subscriptions update.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.FEATURE_NOT_SUPPORTED);
}
if (params.hasExtraParams() && !mIABv6Supported) {
BillingHelper.logWarn(TAG, "Current client doesn't support extra params for buy intent.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.FEATURE_NOT_SUPPORTED);
}
if (rewardedSku && !mIABv6Supported) {
BillingHelper.logWarn(TAG, "Current client doesn't support extra params for buy intent.");
return broadcastFailureAndReturnBillingResponse(BillingResponse.FEATURE_NOT_SUPPORTED);
}
try {
BillingHelper.logVerbose(
TAG, "Constructing buy intent for " + newSku + ", item type: " + skuType);
Bundle buyIntentBundle;
// If IAB v6 is supported, we always try to use buyIntentExtraParams and report the version
if (mIABv6Supported) {
Bundle extraParams = constructExtraParams(params);
extraParams.putString(BillingHelper.LIBRARY_VERSION_KEY, LIBRARY_VERSION);
if (rewardedSku) {
extraParams.putString(BillingFlowParams.EXTRA_PARAM_KEY_RSKU, skuDetails.rewardToken());
if (mChildDirected == ChildDirected.CHILD_DIRECTED
|| mChildDirected == ChildDirected.NOT_CHILD_DIRECTED) {
extraParams.putInt(BillingFlowParams.EXTRA_PARAM_CHILD_DIRECTED, mChildDirected);
}
}
int apiVersion = (params.getVrPurchaseFlow()) ? 7 : 6;
buyIntentBundle =
mService.getBuyIntentExtraParams(
apiVersion,
mApplicationContext.getPackageName(),
newSku,
skuType,
null,
extraParams);
} else if (isSubscriptionUpdate) {
// For subscriptions update we are calling corresponding service method
buyIntentBundle =
mService.getBuyIntentToReplaceSkus(
/* apiVersion */ 5,
mApplicationContext.getPackageName(),
Arrays.asList(params.getOldSku()),
newSku,
SkuType.SUBS,
/* developerPayload */ null);
} else {
buyIntentBundle =
mService.getBuyIntent(
/* apiVersion */ 3,
mApplicationContext.getPackageName(),
newSku,
skuType,
/* developerPayload */ null);
}
int responseCode = BillingHelper.getResponseCodeFromBundle(buyIntentBundle, TAG);
if (responseCode != BillingResponse.OK) {
BillingHelper.logWarn(TAG, "Unable to buy item, Error response code: " + responseCode);
return broadcastFailureAndReturnBillingResponse(responseCode);
}
// Launching an invisible activity that will handle the purchase result
Intent intent = new Intent(activity, ProxyBillingActivity.class);
intent.putExtra(ProxyBillingActivity.KEY_RESULT_RECEIVER, onPurchaseFinishedReceiver);
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT_KEY);
intent.putExtra(RESPONSE_BUY_INTENT_KEY, pendingIntent);
// We need an activity reference here to avoid using FLAG_ACTIVITY_NEW_TASK.
// But we don't want to keep a reference to it inside the field to avoid memory leaks.
// Plus all the other methods need just a Context reference, so could be used from the
// Service or Application.
activity.startActivity(intent);
} catch (RemoteException e) {
String msg =
"RemoteException while launching launching replace subscriptions flow: "
+ "; for sku: "
+ newSku
+ "; try to reconnect";
BillingHelper.logWarn(TAG, msg);
return broadcastFailureAndReturnBillingResponse(BillingResponse.SERVICE_DISCONNECTED);
}
return BillingResponse.OK;
}
developerPayload已经写死为null了,很气人,不能用了。并且Google开发人员也明确说出以后也不会开通这个字段。请看https://issuetracker.google.com/issues/63381481
所以,在集成的时候,要特别注意这一点。
当然也有人自己开发了对Payload支持的Demo:https://github.com/DimaDake/billing
他就是把Google Play结算库中的代码全部拉出来,然后把developerPayload的重新添加上。这个demo的其他代码没细看,我是没使用这个demo,毕竟支付是和钱有关的,还是用Google Play的官方SDK比较好。
ok,集成好,就可以购买测试了。测试过程这里不细说了,可以看另一篇文章Android集成Google支付,以及遇到的坑、坑