前端
这里要接入的是google应用内支付
1.在app模块下的build.gradle模块下引入最新版本google billing 7.1.1版本,顺便引入google服务,如下
implementation 'com.google.android.gms:play-services-wallet:19.0.0'
implementation 'com.android.billingclient:billing:7.1.1'
2.在app模块下的AndroidManifest.xml加入谷歌商店应用内购买结算需要的权限
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR" />
com.android.vending.BILLING权限在我们上传包到google后台的时候需要用到
流程
下面是一次性购买或订阅的典型购买流程。
1.向用户展示他们可以购买什么。
2.启动购买流程,以便用户接受购买交易。
3.在您的服务器上验证购买交易。
4.向用户提供内容。
5.确认内容已传送给用户。对于消耗型商品,用户要先消耗掉已购商品,才能再次购买。
6.订阅会自动续订,直到被取消。订阅可处于下面这几种状态:
有效:用户信誉良好,可享用订阅内容。
已取消:用户已取消订阅,但在到期前仍可享用订阅内容。
处于宽限期:用户遇到了付款问题,但仍可享用订阅内容,同时 Google 会重新尝试通过相应的付款方式扣款。
暂时保留:用户遇到了付款问题,不能再享用订阅内容,同时 Google 会重新尝试通过相应的付款方式扣款。
已暂停:用户暂停了其订阅,在恢复之前不能享用订阅内容。
已到期:用户已取消且不能再享用订阅内容。用户在订阅到期时会被视为流失。
前端核心代码
1.初始化BillingClient
注意:如果购买的是一次性商品 ,则需要调用enableOneTimeProducts启用一次性购买
public BillingClient mBillingclient;
PendingPurchasesParams var10001 = PendingPurchasesParams.newBuilder().enableOneTimeProducts().build();
mBillingclient=BillingClient.newBuilder(context).enablePendingPurchases(var10001).setListener(mPurchasesUpdatedListener).build();
2.添加监听
private PurchasesUpdatedListener mPurchasesUpdatedListener=new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
String debugMessage = billingResult.getDebugMessage();
if (list != null && list.size() > 0) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
for (Purchase purchase : list) {
if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) {
continue;
}
//通知服务器支付成功,服务端验证后,消费商品
VerifyProduct(purchase);
//TODO客户端同步回调支付成功
// acknowledged(purchase); //非消耗性商品 确认交易
consumePurchase(purchase); //消费商品 消费商品后才能进行下一次购买
}
}
} else {
switch (billingResult.getResponseCode()) {
case BillingClient.BillingResponseCode.NETWORK_ERROR:
break;
case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED:
break;
case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED: //服务未连接
break;
case BillingClient.BillingResponseCode.USER_CANCELED: //取消
break;
case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE: //服务不可用
break;
case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: //购买不可用
break;
case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: //商品不存在
break;
case BillingClient.BillingResponseCode.DEVELOPER_ERROR: //提供给 API 的无效参数
break;
case BillingClient.BillingResponseCode.ERROR: //错误
break;
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: //未消耗掉
queryPurchases();
break;
case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: //不可购买
break;
}
}
}
};
3.连接google服务 连接失败可以进行重新连接
productId:这是在google后台填的商品id
public void pay() {
if(mBillingclient == null || !mBillingclient.isReady()){
//TODO客户端同步回调支付失败,原因是为链接到google或者google的支付服务不能使用
mBillingclient.startConnection(new BillingClientStateListener() { //重新连接
@Override
public void onBillingServiceDisconnected() {
//尝试重新启动连接的下一个请求
//谷歌通过调用startConnection()方法进行播放。
retryBillingServiceConnection(productId);
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//BillingClient已经准备好。 你可以在这里查询购买情况。
querySkuDetailsAsync(productId);
}
}
});
return;
}
//查询商品详情
querySkuDetailsAsync(productId);
}
/**
* 重试启动连接
*/
void retryBillingServiceConnection(final String productId){
if(retryCount>=3)return;
try {
retryCount++;
mBillingclient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingServiceDisconnected() {
retryBillingServiceConnection(productId);
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
retryCount=0;
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//BillingClient已经准备好。 你可以在这里查询购买情况。
querySkuDetailsAsync(productId);
}
}
});
}catch (Exception e){
}
}
4.查询商品详情 商品列表里有购买的商品后在拉取支付页
/**
* 查询商品详情
* @param productId
*/
void querySkuDetailsAsync(final String productId){
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
ImmutableList.of(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build()))
.build();
mBillingclient.queryProductDetailsAsync(queryProductDetailsParams, new ProductDetailsResponseListener() {
@Override
public void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List<ProductDetails> skuDetailsList) {
if (skuDetailsList != null && billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
for(ProductDetails skuDetails : skuDetailsList){
if(productId.equals(skuDetails.getProductId())){
//发起支付
launchBillingFlow(skuDetails);
}
}
}
}
});
}
5.拉取支付页面
mch_order_no:透传参数,这里用的是我们服务端生成的订单号,可不传,查询google订单的时候,会从google返回回来
/**
* 拉取google支付页面
* @param cpOrder 你自己的订单号或者用户id,用于关联到对应的用户,发放道具时使用
* @param skuDetails
*/
void launchBillingFlow(ProductDetails productDetails){
ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
ImmutableList.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
// retrieve a value for "productDetails" by calling queryProductDetailsAsync()
.setProductDetails(productDetails)
// For one-time products, "setOfferToken" method shouldn't be called.
// For subscriptions, to get an offer token, call
// ProductDetails.subscriptionOfferDetails() for a list of offers
// that are available to the user.
.build()
);
BillingResult billingResult= mBillingclient.launchBillingFlow(
(Activity) context,
BillingFlowParams
.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setObfuscatedAccountId(mch_order_no)
.build()
);
}
6.消耗商品
public void consumePurchase(final Purchase purchase){
if(mBillingclient == null || purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) return;
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
//消费失败将商品重新放入消费队列
return;
}
Log.i("TAG", "消费成功: ");
}
};
mBillingclient.consumeAsync(consumeParams, listener);
}
7.非消耗品 确认交易
/**
* 非消耗品 确认购买
*/
public void acknowledged(Purchase purchase){
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingclient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {
}
});
}
}
}
以上大致就是客户端核心流程,接下来就是后端查询订单验证的过程了
服务端
1.Gooale Play Console 后台创建应用 传送门:https://play.google.com/console
2.Google Cloud Platform创建api项目 传送门:https://console.cloud.google.com/
3.Google Play Console后台的API权限那里进入并关联到这个api项目
4.Google Play Android Developer API启用
5.设置oauth同意屏幕
6.创建web应用的oauth2.0客户端ID
7.拉起授权页面,使用google开发者账号给项目授权,得到code
8.通过code,拿到refreshToken,这个token只有第一次才会返回需要永久储存(这个refreshtoken很重要,需要保存下来),如果弄丢,只有重新创建一个oauth客户端ID,然后重复步骤6,7,拿到新的refreshtoken
9.刷新refreshToken, 得到accessToken,通过accesstoken就可以去查询订单状态了,这里的accessToken一般只有5分钟左右,5分钟后需要重新用refreshToken换取新的accessToken
具体参考这篇博客:https://www.cnblogs.com/xiaogou/p/15568393.html
查询
1.获取code
地址:https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri={填写的重定向地址}&client_id={创建的clientId}
将上面的{XX}替换成创建api项目时填写的重定向地址,和clientId,然后将连接放到浏览器中打开,就会吊起授权界面,使用你的开发者账号授权登录
请求方式:浏览器中打开
请求完重定向地址上有两个参数code和scope,我们只需要code就行了
2.获取refreshToken
使用code换取refreshToken
地址:https://accounts.google.com/o/oauth2/token
请求方式:post
参数:grant_type=authorization_code
code=获取到的code(需要看看code中是否有%号,如果有需要urldecode)
client_id=创建api项目是的clientId(客户端ID)
client_secret=创建api项目时的clientSecret(客户端密钥)
redirect_uri=创建api项目时的重定向地址
3.查询订单状态
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token={access_token}
packageName:app包名,必须是创建登录api项目时,创建android客户端Id使用包名
productId:对应购买商品的商品ID
token:购买成功后Purchase对象的getPurchaseToken()
access_token:上面咋们获取到的accessToken
请求方式:get
注意
1.接好sdk后打个app bundle上传到Gooale Play Console后台的内部测试,添加测试人员,发送邀请和测试链接,即可使用沙盒测试。
2.支付无法拉起请检查下面:
- 连接的网络不能使用国内的,可以使用港澳台漫游网络,vpn有时候也不顶用
- 使用的海外手机,并且安装有google服务套件(google play ,google sever)
- 登录的google账号得是海外账号(港澳台的也行),必须海外注册的账号,不然不可用
- 可以登录google支付商店查看付费项能否正常显示
常见报错(FAQ)
1.In-app Billing API version is less than 3
检查google play 是否最新,使用的账号是否是海外账号,连接网络不能使用国内网络,可以使用港澳台漫游
2.查询订单报错:The current user has insufficient permissions to perform the requested operation
这是因为上面获得refreshToken的账号没有被授予Sevvice Account权限,进入Google Cloud Platform创建服务账号,并授予权限,用获得refreshToken的账号添加角色,并授予Sevvice Account权限即可查询订单
3.An internal error occurred
可能是网络问题,检查网络是否能上网,然后看其他付费应用能否拉取google支付
官方文档:https://developer.android.google.cn/google/play/billing/integrate