因為公司業(yè)務(wù)需求,需要使用google的登錄和支付。google支付分為訂閱和應(yīng)用內(nèi)購買兩種,筆者使用的是應(yīng)用內(nèi)購買這種方式,這里將整個google支付和支付驗證的流程記錄下來。
導(dǎo)入google結(jié)算庫
def billing_version = "4.0.0"
implementation "com.android.billingclient:billing-ktx:$billing_version"
接入支付
流程:
- 初始化鏈接到google支付服務(wù),如果不能鏈接到說明設(shè)備環(huán)境有問題,要么是沒有翻墻,要么是google套件(google paly 、server)沒有安裝完整,國內(nèi)手機都是閹割過的,所以需要重新安裝google套件
- 查詢上次未消費的商品,如果有未消費的商品通知服務(wù)器,然后消費掉。因為國外的支付環(huán)境和國內(nèi)不一樣,他們可以線上下單,然后到便利店去支付,所以有未消費的這種情況。
這時google支付的準(zhǔn)備工作已完成,下面就可以發(fā)起支付了
- 使用google后臺配置商品id進(jìn)行支付
- 支付完成后通知服務(wù)器驗證訂單合法性并發(fā)貨
- 客戶端消費商品
下面咋們上代碼
step1
初始化并連接到google服務(wù)
// init方法
public synchronized void init(Activity mActivity){
//創(chuàng)建BillingClient 對面,查詢 消費 支付都會使用這個對象
this.mBillingClient = BillingClient.newBuilder(mActivity)
.setListener(new PurchasesUpdatedListener() {//設(shè)置支付回調(diào),這里其實是商品狀態(tài)發(fā)生變化時就會回調(diào)
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
LogUtils.d("call onPurchasesUpdated");
if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {//支付成功
for (Purchase purchase : purchases) {
if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) continue;
OrderManager.getInstance().paySuccess(purchase);
//通知服務(wù)器支付成功,服務(wù)端驗證后,消費商品
}
//TODO客戶端同步回調(diào)支付成功
} else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {//支付取消
} else {//支付失敗
}
}
})
.enablePendingPurchases()
.build();
//鏈接到google play
this.connectBillPay();
}
private void connectBillPay(){
mBillingClient.startConnection(new BillConnectListener());
}
class BillConnectListener implements BillingClientStateListener {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//鏈接到google服務(wù)
payEnable = true;
queryPurchases();
}
}
@Override
public void onBillingServiceDisconnected() {
//未鏈接到google服務(wù)
payEnable = false;
connectBillPay();
}
}
setp2
查詢已支付的商品,并通知服務(wù)器后消費(google的支付里面,沒有消費的商品,不能再次購買)
private void queryPurchases(){
PurchasesResponseListener mPurchasesResponseListener = new PurchasesResponseListener() {
@Override
public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> purchasesResult) {
if(billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || purchasesResult == null) return;
for (Purchase purchase : purchasesResult) {
if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) continue;
OrderManager.getInstance().paySuccess(purchase);
//這里處理已經(jīng)支付過的訂單,通知服務(wù)器去驗證
}
}
};
mBillingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, mPurchasesResponseListener);
}
setp3
發(fā)起支付
/**
*
* @param cpOrder 你自己的訂單號或者用戶id,用于關(guān)聯(lián)到對應(yīng)的用戶,發(fā)放道具時使用
* @param productId google后臺配置產(chǎn)品ID
*/
public void pay(final String cpOrder, final String productId) {
if(mBillingClient == null || wrActivity.get() == null || !payEnable){
//TODO客戶端同步回調(diào)支付失敗,原因是為鏈接到google或者google的支付服務(wù)不能使用
return;
}
//查詢商品詳情
querySkuDetailsAsync(cpOrder, productId);
}
//查詢商品詳情
void querySkuDetailsAsync(final String cpOrder, final String productId){
List<String> skuList = new ArrayList<>();
skuList.add(productId);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
if (skuDetailsList != null && billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
for(SkuDetails skuDetails : skuDetailsList){
if(productId.equals(skuDetails.getSku())){
//發(fā)起支付
launchBillingFlow(cpOrder, skuDetails);
}
}
}
}
});
}
//吊起google支付頁面
void launchBillingFlow(String cpOrder, SkuDetails skuDetails){
mBillingClient.launchBillingFlow(
wrActivity.get(),
BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.setObfuscatedAccountId(cpOrder)//這里本來的意思存放用戶信息,類似于國內(nèi)的透傳參數(shù),我這里傳的我們的訂單號。老版本使用DeveloperPayload字段,最新版本中這個字段已不可用了
.build()
);
}
服務(wù)器支付驗證操作較為復(fù)雜,咋們在下面單獨提出來做一個小節(jié)
setp5
消費商品
public void consumePurchase(final Purchase purchase){
if(mBillingClient == null || purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) return;
LogUtils.d("消耗商品:\n商品id:" + purchase.getSkus() + "\n商品OrderId:" + purchase.getOrderId() + "\ntoken:" + purchase.getPurchaseToken());
LogUtils.d("消耗商品:" + purchase.getAccountIdentifiers().getObfuscatedAccountId());
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
//消費失敗將商品重新放入消費隊列
OrderManager.getInstance().consumeFinal(purchase);
return;
}
LogUtils.d("消費成功");
}
};
mBillingClient.consumeAsync(consumeParams, listener);
}
服務(wù)端驗證
做服務(wù)端驗證前,需要做一下準(zhǔn)備工作
- 創(chuàng)建api項目這個和登錄用的項目不是同一個
- 開啟Google Play Android Developer API
- 設(shè)置oauth同意屏幕(就是拉起開發(fā)者授權(quán)賬號登錄時的登錄頁面)
- 創(chuàng)建web應(yīng)用的oauth客戶端ID
- google play開發(fā)者后臺,API權(quán)限菜單中關(guān)聯(lián)剛剛創(chuàng)建的項目,一個google play賬號只需要也只能關(guān)聯(lián)一個api項目就行了,這個項目可以查詢關(guān)聯(lián)賬號中的所有應(yīng)用的訂單
- 拉起授權(quán)頁面,使用google開發(fā)者賬號給項目授權(quán),得到code
- 通過code,拿到refreshToken,這個token只有第一次才會返回需要永久儲存(這個refreshtoken很重要,需要保存下來),如果弄丟,只有重新創(chuàng)建一個oauth客戶端ID,然后重復(fù)步驟6,7,拿到新的refreshtoken
- 刷新refreshToken, 得到accessToken,通過accesstoken就可以去查詢訂單狀態(tài)了,這里的accessToken一般只有5分鐘左右,5分鐘后需要重新用refreshToken換取新的accessToken
下面咋們上操作截圖
setp1
創(chuàng)建api項目google api console

setp2
開啟Google Play Android Developer API


搜索“Google Play Android Developer API”

開啟“Google Play Android Developer API”

setp3
開啟同意屏幕

這里填上必填項就行了,這個授權(quán)同意屏幕,請求code時拉起來給咋們開發(fā)人員開的,填啥都無所謂
setp4
創(chuàng)建oauth2客戶端id

創(chuàng)建頁面和創(chuàng)建成功后的修改頁面可以獲取到clientId和clientSecret

到這里api項目就已經(jīng)創(chuàng)建好了
setp5
google play后臺關(guān)聯(lián)api項目

setp6
獲取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={創(chuàng)建的clientId}
將上面的{XX}替換成創(chuàng)建api項目時填寫的重定向地址,和clientId,然后將連接放到瀏覽器中打開,就會吊起授權(quán)界面,使用你的開發(fā)者賬號授權(quán)登錄
請求方式:瀏覽器中打開


這里可以看到,重定向地址上有兩個參數(shù)code和scope,我們只需要code就行了,這里的code是urlencode后的,使用時需要decode
setp7
使用code換取refreshToken
地址:https://accounts.google.com/o/oauth2/token
請求方式:post
參數(shù):grant_type=authorization_code
code=獲取到的code(需要看看code中是否有%號,如果有需要urldecode)
client_id=創(chuàng)建api項目是的clientId(客戶端ID)
client_secret=創(chuàng)建api項目時的clientSecret(客戶端密鑰)
redirect_uri=創(chuàng)建api項目時的重定向地址

這里就獲取到refreshToken了,重點重點重點,refreshToken保存下來,它只會在第一次請求中返回,后續(xù)用在發(fā)一樣的請求不會返回refreshtoken,如果不慎弄丟了,需要去重新創(chuàng)建一個WebClientId
setp8
使用refreshToken獲取accessToken
地址:https://accounts.google.com/o/oauth2/token
請求方式:post
參數(shù):grant_type=refresh_token
refresh_token=剛剛獲取到的refreshToken
client_id=創(chuàng)建api項目是的clientId(客戶端ID)
client_secret=創(chuàng)建api項目時的clientSecret(客戶端密鑰)

setp9
查詢訂單狀態(tài)
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token={access_token}
packageName:app包名,必須是創(chuàng)建登錄api項目時,創(chuàng)建android客戶端Id使用包名
productId:對應(yīng)購買商品的商品ID
token:購買成功后Purchase對象的getPurchaseToken()
access_token:上面咋們獲取到的accessToken
請求方式:get
返回值:
{
"purchaseTimeMillis": "1623980699933",//購買產(chǎn)品的時間,自紀(jì)元(1970 年 1 月 1 日)以來的毫秒數(shù)。
"purchaseState": 0,//訂單的購買狀態(tài)??赡艿闹禐椋?. 已購買 1. 已取消 2. 待定
"consumptionState": 0,//產(chǎn)品的消費狀態(tài)??赡艿闹禐椋?0. 尚未消耗 1. 已消耗
"developerPayload": "",
"orderId": "GPA.3398-6726-1036-80298",//google訂單號
"purchaseType": 0,
"acknowledgementState": 0,
"kind": "androidpublisher#productPurchase",
"obfuscatedExternalAccountId": "SDK2106180944530041",//上面客戶支付時的透傳字段,google指導(dǎo)是用來存放用戶信息的,不能過長,否則客戶端不能支付
"obfuscatedExternalProfileId": "",
"regionCode": "HK"
}
到這里整個支付驗證流程就已經(jīng)走完了,這里總結(jié)哈筆者這次試用過程中走過的一些坑:
- google應(yīng)用必須要在封閉測試狀態(tài)下,并審核通過的應(yīng)用才能支付,文檔說的是內(nèi)部測試就可以了,筆者每次都弄到封閉測試狀態(tài)下才可以支付。
- 在firebase中創(chuàng)建了項目,會自動同步到google api后臺,不用再去單獨創(chuàng)建登錄使用的項目
- 登錄使用的api項目和查詢支付使用的api項目是兩個不同的項目相互不干擾,查詢支付的api項目一個google play賬號對應(yīng)一個項目,這個google play賬號中所有的應(yīng)用,都可以通過這個查詢支付的api項目去查詢
- 獲取code授權(quán)api項目時,要使用google play后臺的開發(fā)者賬號授權(quán)
關(guān)于RefreshToken過期問題
- api項目-同意屏幕,發(fā)布狀態(tài)為測試(有效期7天)
- RefreshToken 6個月都未使用,這個要維護(hù)accessToken的有效性,應(yīng)該可以不必考慮
- 授權(quán)賬號改密碼了(筆者未測試,修改開發(fā)者賬號密碼是否會導(dǎo)致過期)
- 授權(quán)超過50個刷新令牌,最先的刷新令牌就會失效(這里50個應(yīng)該夠用了,除了測試時,可能會授權(quán)多個)
- 取消了授權(quán)
- 屬于具有有效會話控制策略的 Google Cloud Platform 組織