文档地址:google play 的结算服务
首先说明这不是google pay,而是google play自己的结算服务,上架google play后可直接调起支付;第一次上海外,刚开始也准备接入google pay,不接害怕google play审核不通过,但流程太过麻烦,还需要注册第三方的账号,偶然发现google play自带了结算服务,接入也比较简单,也不需要注册其他平台的账号,就选择了这个;还有一点要注意:以下每一步都很重要,全部都要做。都是我走过不少坑总结出来的。
前提准备:
- 需要手机配置google服务
- 把应用上传到google play平台,未上架的可以传到测试渠道
- 设置定价模板,为应用内商品使用
4. 选中应用,创建应用内商品
5. 设置付款资料,其中的商家ID后续在代码中会用到
客户端开发流程:
- 添加库 当前最新的为4.0.0
//google play 结算
implementation "com.android.billingclient:billing:${project.ext.billingVersion}"
- 初始化
/**
* 初始化
*/
public void init() {
mBillingClient = BillingClient.newBuilder(mActivityRef.get())
.setListener(mPurchasesUpdatedListener)
.enablePendingPurchases()
.build();
if (!mBillingClient.isReady()) {
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult != null) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
getOrderID();//从后台获取订单id
} else {
ToastUtil.showToast(ReaderApplication.getInstance()
.getResources().getString(R.string.recharge_no_service));
}
}
}
@Override
public void onBillingServiceDisconnected() {
ToastUtil.showToast(ReaderApplication.getInstance()
.getResources().getString(R.string.recharge_no_service));
}
});
} else {
getOrderID();
}
}
- 获取订单id
/**
* 获取订单ID
*/
private void getOrderID() {
HashMap<String, Object> params = new HashMap<>(16);
params.put("productId", productId);
new NetModel().doPost(NetApi.ANDROID_URL_PAY_ORDER_GOOGLE, params, new INetCallBack<JSONObject>() {
@Override
public void onSuccess(JSONObject result) {
boolean success = JsonUtil.getBoolean(result, "success");
String message = JsonUtil.getString(result, "message");
JSONObject data = JsonUtil.getJSONObject(result, "data");
if (success) {
mOrderID = JsonUtil.getString(data, "topUpId");
recharge();
} else {
mListener.onState(mActivityRef.get(),
RechargeResult.failOf(message));
}
}
@Override
public void onFail(String msg) {
ToastUtil.showToast(msg);
}
});
}
- 购买
/**
* 购买 mUserID:就是上面提到的商家id
*/
private void recharge() {
if (mBillingClient.isReady()) {
List<String> skuList = new ArrayList<>();
skuList.add(mSku);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),
(billingResult, skuDetailsList) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& skuDetailsList != null) {
for (SkuDetails skuDetails : skuDetailsList) {
String sku = skuDetails.getSku();
if (mSku.equals(sku)) {
BillingFlowParams purchaseParams =
BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.setObfuscatedAccountId(mUserID)
.setObfuscatedProfileId(mOrderID)
.build();
mBillingClient.launchBillingFlow(mActivityRef.get(), purchaseParams);
}
}
} else {
ToastUtil.showToast(ReaderApplication.getInstance()
.getResources().getString(R.string.recharge_no_service));
}
});
}
}
/**
* 购买回调
*/
private PurchasesUpdatedListener mPurchasesUpdatedListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> list) {
String debugMessage = billingResult.getDebugMessage();
Log.e(TAG, debugMessage);
if (list != null && list.size() > 0) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
for (Purchase purchase : list) {
mConsume = "2";
uploadToServer(purchase.getPurchaseToken(), mOrderID);
}
} else {
ToastUtil.showToast(billingResult.getDebugMessage());
}
} else {
switch (billingResult.getResponseCode()) {
case BillingClient.BillingResponseCode.SERVICE_TIMEOUT: {//服务连接超时"-3"
// mListener.onState(mActivityRef.get(), RechargeResult.failOf("-3"));
mListener.onState(mActivityRef.get(), "服务连接超时");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED: {//-2
// mListener.onState(mActivityRef.get(), RechargeResult.failOf("-2"));
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED: {//服务未连接-1
mListener.onState(mActivityRef.get(), "服务未连接");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.USER_CANCELED: {//取消1
mListener.onState(mActivityRef.get(), "支付取消");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE: {//服务不可用2
mListener.onState(mActivityRef.get(), "服务不可用");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: {//购买不可用3
mListener.onState(mActivityRef.get(), "购买不可用");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: {//商品不存在4
mListener.onState(mActivityRef.get(), "商品不存在");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.DEVELOPER_ERROR: {//提供给 API 的无效参数5
mListener.onState(mActivityRef.get(), "提供给 API 的无效参数");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.ERROR: {//错误6
mListener.onState(mActivityRef.get(), "错误");
mActivityRef.get().finish();
break;
}
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: {//未消耗掉
mConsume = "1";
queryHistory();
break;
}
case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: {//不可购买8
mListener.onState(mActivityRef.get(), "不可购买");
mActivityRef.get().finish();
break;
}
default:
break;
}
}
}
};
- 上传到服务器验证
/**
* 上传到服务器验证接口
*/
private void uploadToServer(final String purchaseToken, String mOrderID) {
HashMap<String, Object> params = new HashMap<>(16);
Log.i(TAG, "uploadToServer: purchaseToken=" + purchaseToken + " topUpId=" + mOrderID);
params.put("purchaseToken", purchaseToken);
params.put("topUpId", mOrderID);
new NetModel().doPost(NetApi.ANDROID_URL_PAY_CALL_GOOGLE, params, new INetCallBack<JSONObject>() {
@Override
public void onSuccess(JSONObject result) {
boolean success = JsonUtil.getBoolean(result, "success");
String message = JsonUtil.getString(result, "message");
if (success) {
ToastUtil.showToast("充值成功");
AppsFlyerPoint.purchaseSuccess(amount, bookId, chapterId);
consume(purchaseToken);
} else {
mListener.onState(mActivityRef.get(),
RechargeResult.failOf(message));
}
}
@Override
public void onFail(String msg) {
ToastUtil.showToast(msg);
}
});
}
/**
* 消耗
*/
private void consume(String purchaseToken) {
if (mBillingClient.isReady()) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
mBillingClient.consumeAsync(consumeParams, (billingResult, s) -> {
});
} else {
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult != null) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
mBillingClient.consumeAsync(consumeParams, (billingResult1, s) -> {
});
} else {
ToastUtil.showToast(ReaderApplication.getInstance()
.getResources().getString(R.string.recharge_no_service));
}
}
}
@Override
public void onBillingServiceDisconnected() {
}
});
}
}
测试
还有流程和国内差别最大的一点,测试
- 必须设置 许可测试 ,在google play 的应用管理中心,设置-许可测试,添加测试人员,并把许可相应设为:LICENSED;没设置之前调起一直显示商品无法购买;
- 选中应用,添加测试用户,此处需要复制链接后,让测试人员登录google浏览器,打开此链接,接受邀请,测试账号设置过许可测试后,不需要非要从该链接下载应用测试,直接运行到手机的debug版也可以测试支付功能,而且不会真实扣费,也不需要绑定信用卡等;