发布应用(正式版或者内测版)并创建好应用内商品。
发布应用和创建商品步骤较多, 这里详细说下。 我们最终的目的是能够将应用发布到测试版或者是正式版。只有当要发布的应用的所有信息填写完毕,所有对勾都变绿以后。才能发布版本。填写没有顺序,比如填写
定价和分发范围
的时候,要先上传一个apk才能继续填写, 那就先在
应用版本
传一个apk再去继续填写,耐心点按照提示来就可以了。 创建商品在
应用内商品
里面添加。 内购型商品在
受管理商品
创建,订阅型产品在
订阅
里面创建。 所以信息填写好,就可以到
应用版本
->管理->查看->发布到XX版来发布。
3.2 集成谷歌结算库
主要参考
官方文档
,如果可以科学上网尽量多看官方文档,耐心看下去会少踩很多坑。 集成很简单, 直接依赖就好了:
implementation 'com.android.billingclient:billing:2.0.3'
复制代码
3.3 使用
整个支付过程在安卓客户端这里分为5步:
支付结果回调
整个过程走下来就是, 先与Google Play 建立连接, 通过商品id来查询本应用是否存在该商品,存在则去支付,如果支付成功了,就消费此次商品。 消费成功后客户端需要去和服务端校验此次支付。
支付工具类封装如下:
public class GooglePayUtils implements PurchasesUpdatedListener {
String TAG = ".GooglePayUtils";
private BillingClient mBillingClient = null;
private boolean isClientInit = false;
private MyListener mListener = null;
private Context mContext = null;
private String orderId = "";
private String purchaseId = "";
private String purchaseType = BillingClient.SkuType.INAPP;
public GooglePayUtils(Context context, MyListener mListener, String orderId, String purchaseId, String purchaseType) {
this.mContext = context;
this.mListener = mListener;
this.orderId = orderId;
this.purchaseId = purchaseId;
this.purchaseType = purchaseType;
public GooglePayUtils pay() {
mBillingClient = BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Log.e(TAG, "onBillingSetupFinished code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
isClientInit = true;
queryAndPayPurchases(purchaseId);
@Override
public void onBillingServiceDisconnected() {
isClientInit = false;
return this;
private void queryAndPayPurchases(@NonNull final String purchaseId) {
if (!isClientInit) {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.not_connect));
return;
List<String> skuList = new ArrayList<>();
skuList.add(purchaseId);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(purchaseType);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
Log.e(TAG, "onSkuDetailsResponse code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage() + " , skuDetailsList = " + skuDetailsList);
if (billingResult.getResponseCode()!= BillingClient.BillingResponseCode.OK) {
onFail(mContext.getResources().getString(R.string.no_goods));
return;
if (skuDetailsList == null || skuDetailsList.isEmpty()) {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.no_goods));
return;
SkuDetails skuDetails = null;
for (SkuDetails details : skuDetailsList) {
Log.e(TAG, "onSkuDetailsResponse skuDetails = " + details.toString());
if (purchaseId.equals(details.getSku())) {
skuDetails = details;
if (skuDetails != null) {
pay(skuDetails);
} else {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.no_goods));
private void pay(SkuDetails skuDetails) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
int code = mBillingClient.launchBillingFlow((Activity) mContext, flowParams).getResponseCode();
if (BillingClient.BillingResponseCode.OK!=code) {
onFail(mContext.getResources().getString(R.string.dump_pay_faile));
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
Log.e(TAG, "onPurchasesUpdated code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.pay_cancle));
} else {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.pay_faile));
private void handlePurchase(Purchase purchase) {
if (!purchase.isAcknowledged()) {
if(BillingClient.SkuType.INAPP.equals(purchaseType)){
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(orderId)
.build();
mBillingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
Log.e(TAG, "onConsumeResponse code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage() + " , purchaseToken = " + purchase.getPurchaseToken());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (mListener != null) {
mListener.onSuccess(purchase.getPurchaseToken());
} else {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
}else {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken()).setDeveloperPayload(orderId)
.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
Log.e(TAG, "onConsumeResponse code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage() + " , purchaseToken = " + purchase.getPurchaseToken());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (mListener != null) {
mListener.onSuccess(purchase.getPurchaseToken());
} else {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
} else {
if (mListener != null) {
mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
public interface MyListener {
void onSuccess(String purchaseToken);
void onError(String msg);
private void onFail(String str){
if (mListener != null) {
mListener.onError(str);
复制代码
3.4 测试
代码编写完了,要做的就是测试。 测试购买分两种, 一种是消耗性的也就是购买类型是
内购
。一种是非消耗性型,购买类型是
订阅
。
要测试内购,直接传结算库里面几个预设好拿来测试的商品id就行了,在
官方文档
中查看。如果是订阅, 则需要
应用发布到正式版
才能够测试。第一次发布后,审核的时间会比较久,3-4天左右。 页面上显示正在处理更新就是还在审核中。
添加测试账号
只有在控制台添加谷歌测试账号才能够用这个账号来测试支付。在这里添加测试账号: 控制台->开发者账号->账号详情->可用于测试的 Gmail帐号
这些都准备好了, 就可以传商品id和商品类型到上面的支付工具类中测试支付了。
测试过程中有哪些坑:
没有科学上网, 导致查询不到商品。
手机登录的不是刚刚添加的测试账号,导致调起支付框,但提示无法支付之类的信息。在手机->设置->账号/同步 -> Google账号 里查看。
版本号verionCode 、应用标识applicationId、签名等要保持和控制台上传的一致。
订阅后是自动续订的, 要通过谷歌发送的续订邮件中找到退订入口, 退订后才能继续测试订阅支付。 不然提示,系统正在处理你的订单,稍后再试。
在Google Play 控制台创建应用、应用内商品。
发布应用到内测版或者正式版(测试订阅需要)。
参照官方文档集成结算库、封装好支付工具类。
在Google Play 控制台添加测试账号,用这个账号来测试支付。
5. 历史文章目录
juejin.cn/post/684490…