谷歌支付
1.流程
2.谷歌后臺(tái)API配置
啟用API
網(wǎng)址:https://play.google.com/console/u/0/developers/6637358084117674478/api-access
進(jìn)入谷歌管理后臺(tái)胸竞,當(dāng)未啟動(dòng)時(shí)需要啟動(dòng)對(duì)應(yīng)API舍肠,隨后會(huì)提示去創(chuàng)建服務(wù)賬號(hào)
創(chuàng)建服務(wù)賬號(hào)
網(wǎng)址:https://console.developers.google.com/iam-admin/serviceaccounts/create?project=wewatch-98a41
選中應(yīng)用(例如選擇wewatch)>> 輸入服務(wù)賬號(hào)名稱 >> 輸入服務(wù)賬號(hào)ID(這時(shí)候會(huì)有提示 應(yīng)用名-應(yīng)用ID.@appspot.gserviceaccount.com蜂筹,注意核實(shí)應(yīng)用是否一致) >>當(dāng)創(chuàng)建完畢之后會(huì)得到一個(gè)p12文件廉涕,需要提供給后臺(tái),同時(shí)會(huì)需要記錄下對(duì)應(yīng)p12文件的密碼
查看對(duì)應(yīng)用戶以及權(quán)限
網(wǎng)址:https://console.cloud.google.com/iam-admin/iam?project=wewatch-98a41
當(dāng)創(chuàng)建成功時(shí)捅暴,可以到IAM中查看對(duì)應(yīng)的賬號(hào)權(quán)限,為Editor權(quán)限
2.開啟消息通知(Cloud Pub/Sub)
定義:Cloud Pub/Sub是一種托管式的消息服務(wù),當(dāng)應(yīng)用購買對(duì)于商品成功時(shí)奄侠,就會(huì)去找到對(duì)應(yīng)的谷歌服務(wù)用戶,去通知其狀態(tài)的改變载矿。
開啟Cloud Pub/Sub網(wǎng)址:https://cloud.google.com/pubsub/
點(diǎn)擊轉(zhuǎn)到控制臺(tái)垄潮,會(huì)到達(dá)該網(wǎng)址:https://console.cloud.google.com/cloudpubsub/topic/list?folder=&organizationId=&project=wewatch-98a41
創(chuàng)建主題
當(dāng)點(diǎn)擊創(chuàng)建主題時(shí),會(huì)要求輸入對(duì)應(yīng)的主題名稱
創(chuàng)建成功后闷盔,會(huì)出現(xiàn)對(duì)應(yīng)的主題ID和主題名稱弯洗,名稱是用于測試的,選擇最右邊的編輯按鈕逢勾,或者直接點(diǎn)擊左側(cè)的訂閱專欄牡整,都能為主題創(chuàng)建訂閱
創(chuàng)建訂閱
進(jìn)入之后輸入對(duì)應(yīng)的訂閱名稱,選擇傳送類型為拉取溺拱,用于測試主題消息的連通性逃贝,其余選項(xiàng)選擇默認(rèn),當(dāng)創(chuàng)建成功時(shí)會(huì)進(jìn)入訂閱界面迫摔,這時(shí)候復(fù)制對(duì)應(yīng)的主題名稱到測試頁面沐扳。正式訂閱時(shí),需要改為推送句占,這里拉取只是測試連通性沪摄。
權(quán)限授予和校驗(yàn)流程
原理圖:
首先進(jìn)入Google play console >> 選擇應(yīng)用 >> 選擇獲利設(shè)置
當(dāng)進(jìn)入該頁面以后,輸入剛剛復(fù)制的主題名稱
當(dāng)權(quán)限失敗時(shí)就會(huì)出現(xiàn)該情況纱烘,這時(shí)候需要檢查主題和訂閱的權(quán)限杨拐。
1.給谷歌云消息賬號(hào)添加推送的權(quán)限,例如:簽名是應(yīng)用的ID凹炸,后面格式一致91804594755@cloudservices.gserviceaccount.com
點(diǎn)擊添加成員后戏阅,輸入賬號(hào),并給予推送權(quán)限
2.給訂閱者添加權(quán)限
與主題添加權(quán)限一致啤它,這里的賬號(hào)添加的是谷歌API后臺(tái)賬號(hào)奕筐,即一開始我們創(chuàng)建谷歌后臺(tái)賬號(hào),到此之后变骡,便能確保當(dāng)谷歌支付有狀態(tài)改變時(shí)离赫,谷歌能及時(shí)通知到后臺(tái),去對(duì)訂單做對(duì)應(yīng)的處理塌碌。
3.谷歌應(yīng)用測試人員配置
谷歌控制臺(tái)的測試人員配置
在選項(xiàng)中輸入對(duì)頂?shù)膅mail郵箱
進(jìn)入應(yīng)用配置測試人員
點(diǎn)擊應(yīng)用>> 測試 >> 內(nèi)部測試
點(diǎn)擊圖上的右鍵頭渊胸,輸入對(duì)應(yīng)的郵箱地址,隨后將底部的復(fù)制鏈接台妆,發(fā)送給對(duì)應(yīng)的谷歌賬號(hào)翎猛,同意授權(quán)之后胖翰,需要等待一段時(shí)間,一般在24小時(shí)之內(nèi)切厘,該賬號(hào)就有權(quán)限萨咳。校驗(yàn)測試購買的權(quán)限之一,就是進(jìn)入到Play store商店中疫稿,查找對(duì)應(yīng)的APP名字培他,如果能查到就有對(duì)應(yīng)權(quán)限
測試補(bǔ)充說明
1.手機(jī)或平板需要有對(duì)應(yīng)的谷歌服務(wù)、谷歌商店遗座、穩(wěn)定的vpn舀凛,才能順利購買,通惩窘可以使用Google Go三方軟件進(jìn)行配置檢測猛遍,華為magic系統(tǒng)新手機(jī)除外
2.對(duì)于測試前提,需要上架對(duì)應(yīng)的應(yīng)用碎绎,當(dāng)前上架應(yīng)用需要審核螃壤,審核時(shí)間為7天,只有應(yīng)用審核通過之后筋帖,才能進(jìn)行以下測試
3.對(duì)于測試周期奸晴,訂閱的周期測試為5分鐘,即自動(dòng)續(xù)訂是每5分鐘一次日麸,會(huì)續(xù)訂6次寄啼,對(duì)于消費(fèi)包,沒有限制代箭。對(duì)于訂閱的升降級(jí)墩划,谷歌內(nèi)部有提供多種結(jié)算方式:
BillingFlowParams.IMMEDIATE_WITH_TIME_PRORATION(立即生效,購買的新套餐首月價(jià)格會(huì)減去生效包剩余價(jià)格)
BillingFlowParams.DEFERRED(下一個(gè)結(jié)算周期生效)
其余結(jié)算方式請(qǐng)查看該網(wǎng)址: https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode
4.訂閱或消費(fèi)商品的創(chuàng)建
點(diǎn)擊應(yīng)用>> 商品 >> 應(yīng)用內(nèi)商品
消耗商品配置
該頁面為消費(fèi)商品嗡综,即一次性商品乙帮,通過最右側(cè)的創(chuàng)建商品,可以創(chuàng)建對(duì)應(yīng)的商品信息
輸入產(chǎn)品ID和對(duì)應(yīng)名稱极景,點(diǎn)擊最底下的創(chuàng)建模板
網(wǎng)址: https://play.google.com/console/u/0/developers/6637358084117674478/pricing-templates
該模板可以選擇不同國家察净,并且根據(jù)實(shí)際匯率進(jìn)行價(jià)格轉(zhuǎn)換
選擇完價(jià)格之后,對(duì)商品進(jìn)行保存
訂閱商品配置
點(diǎn)擊創(chuàng)建訂閱內(nèi)容
訂閱相比消耗盼樟,多了結(jié)算周期氢卡,和首訂等功能添加,最后需要點(diǎn)擊右下角的保存按鈕,便可以生效。
5.安卓終端支付對(duì)接
a.邏輯圖
訂閱流程:
1.判斷是否為預(yù)付費(fèi)用戶
2.判斷是否為訂閱裤纹,默認(rèn)未訂閱
3.判斷當(dāng)前用戶是否存在訂閱套餐逢捺,若不是Google方式購買則不允許購買筑悴,若是則繼續(xù)
4.判斷已購買的套餐们拙,對(duì)應(yīng)的谷歌訂單中的用戶是否為同一用戶,如果不是則不允許購買阁吝,若是則繼續(xù)
5.每次購買前會(huì)查詢谷歌訂單睛竣,用于補(bǔ)單處理
6.訪問后臺(tái)API創(chuàng)建對(duì)應(yīng)的訂單
7.調(diào)用谷歌的訂閱購買
8.調(diào)用谷歌的消費(fèi)接口
9.將訂單信息保存到本地中,并進(jìn)行加密處理求摇,防止漏單情況
10.將對(duì)應(yīng)訂單信息回調(diào)后臺(tái),通知后臺(tái)去開通訂單殊者,當(dāng)返回成功則与境,刪除訂單信息,若失敗或存儲(chǔ)猖吴,在應(yīng)用啟動(dòng)時(shí)進(jìn)行補(bǔ)單操作
11.當(dāng)升級(jí)訂閱時(shí)摔刁,會(huì)立馬提示刷新訂閱界面,當(dāng)降級(jí)時(shí)海蔽,會(huì)有通知彈窗告知用戶下個(gè)結(jié)算周期生效共屈,最終完成訂單處理
消費(fèi)流程:
1.判斷是否為預(yù)付費(fèi)用戶
2.判斷是否為消費(fèi)包
3.提示支付方式的彈窗
4.支付彈窗選擇谷歌支付
5.訪問后臺(tái)API創(chuàng)建對(duì)應(yīng)的訂單
6.調(diào)用谷歌消費(fèi)包購買
7.調(diào)用谷歌的消費(fèi)接口
8.將訂單信息保存到本地中,并進(jìn)行加密處理党窜,防止漏單情況
9.將對(duì)應(yīng)訂單信息回調(diào)后臺(tái)拗引,通知后臺(tái)去開通訂單,當(dāng)返回成功則幌衣,刪除訂單信息矾削,若失敗或存儲(chǔ),在應(yīng)用啟動(dòng)時(shí)進(jìn)行補(bǔ)單操作
10.當(dāng)購買成功時(shí)豁护,會(huì)立馬提示刷新界面哼凯,并將消費(fèi)包按鈕置與不可點(diǎn)擊狀態(tài)
b.谷歌API對(duì)應(yīng)的提示碼
c.代碼
官方文檔網(wǎng)址:https://developer.android.google.cn/google/play/billing/billing_library_overview
1.GoogleUtils.java和PayUtils.kt
public class GooglePayUtils implements PurchasesUpdatedListener {
//持弱引用仿內(nèi)存泄露
private WeakReference<Context> weakContext;
private BillingClient billingClient;
private static final String TAG = "GooglePayUtils";
public GooglePayUtils(Context context) {
weakContext = new WeakReference<>(context);
}
public void init() {
//為Utils添加產(chǎn)品包更新回調(diào)監(jiān)聽 PurchasesUpdatedListener
billingClient = BillingClient.newBuilder(getContext()).setListener(this).enablePendingPurchases().build();
//開始連接谷歌服務(wù)
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
LogUtil.e(TAG, "init Success");
if (payStateListener != null) {
payStateListener.onConnectionSuccess();
}
} else {
XMToastUtil.showShortToastCenter(billingResult.getDebugMessage(), getContext());
LogUtil.e(TAG, "init fail");
if (payStateListener != null && getContext() instanceof Activity) {
Intent intent = new Intent();
intent.putExtra("data", "fail");
((Activity) getContext()).setResult(Activity.RESULT_CANCELED, intent);
((Activity) getContext()).finish();
}
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchaseList) {
if (payStateListener != null)
payStateListener.onPurchasesUpdated(billingResult, purchaseList);
}
/**
* 查詢已購買的產(chǎn)品
*/
public void queryHavePurchased() {
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
if (areSubscriptionsSupported()) {
Purchase.PurchasesResult subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
if (subscriptionResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
purchasesResult.getPurchasesList().addAll(subscriptionResult.getPurchasesList());
} else {
LogUtil.e(TAG, "Got an error response trying to query subscription purchases");
}
}
LogUtil.e(TAG, "queryHavePurchased: " + purchasesResult.getPurchasesList().toString());
//payStateListener.onQueryPurchasedSuccess(purchasesResult);
}
/**
* @return 是否有可訂閱的商品
*/
private boolean areSubscriptionsSupported() {
BillingResult result = billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS);
int responseCode = result.getResponseCode();
if (responseCode != BillingClient.BillingResponseCode.OK) {
LogUtil.e(TAG, "areSubscriptionsSupported() got an error response: " + responseCode);
}
return responseCode == BillingClient.BillingResponseCode.OK;
}
/**
* 下單購買消耗性商品的sku
*
* @param sku 產(chǎn)品的sku數(shù)組
*/
public void purchase(String sku) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getContext()) != 0) {
XMToastUtil.showShortToastCenter("your phone can't support google service", getContext());
return;
}
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
List<String> skuList = new ArrayList<>();
skuList.add(sku);
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> list) {
if (list == null || list.size() == 0) {
Toast.makeText(getContext(), "not match" + billingResult.getDebugMessage() + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
if (payStateListener != null)
payStateListener.onPurchasesFail(sku, billingResult.getResponseCode());
return;
}
if (list == null) return;
for (int i = 0; i < list.size(); i++) {
SkuDetails skuDetails = list.get(i);
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
int code = billingClient.launchBillingFlow(getActivity(getContext()), flowParams).getResponseCode();
if (code != BillingClient.BillingResponseCode.OK && payStateListener != null) {
Toast.makeText(getContext(), "purchase fail:" + code, Toast.LENGTH_SHORT).show();
payStateListener.onPurchasesFail(skuDetails.getTitle(), code);
}
/*if (code == BillingClient.BillingResponseCode.OK && payStateListener != null) {
payStateListener.onSubscribeSuccess(ProductType.NORMAL);
}*/
}
}
});
}
/**
* @description 消耗所有可查詢的包
* @param
*/
public void consumePurchase() {
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
List<Purchase> purchasesList = purchasesResult.getPurchasesList();
if (purchasesList == null) return;
for (int i = 0; i < purchasesList.size(); i++) {
Purchase purchase = purchasesList.get(i);
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(purchase.getDeveloperPayload())
.build();
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
LogUtil.e(">>>>onConsumeResponse:", "message:" + billingResult.getDebugMessage() + billingResult.getResponseCode());
payStateListener.onConsumeFinished(purchase);
}
});
}
}
private Purchase currentSusProduct;
/**
* @description 訂閱包的購買
* @param newSku 對(duì)應(yīng)的商品id
* @param productType 具體升降級(jí)類型
*/
public void subscribe(String newSku, ProductType productType) {
LogUtil.e(TAG, "newSku:" + newSku);
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getContext()) != 0) {
XMToastUtil.showShortToastCenter("your phone can't support google service", getContext());
return;
}
ArrayList<String> skuList = new ArrayList<>();
skuList.add(newSku);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> list) {
Purchase.PurchasesResult subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
//LogUtil.e(TAG, "當(dāng)前訂閱的套餐:" + subscriptionResult.getPurchasesList().toString());
if (subscriptionResult.getPurchasesList() != null && !subscriptionResult.getPurchasesList().isEmpty()) {
currentSusProduct = subscriptionResult.getPurchasesList().get(0);
}
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
Toast.makeText(getContext(), "please check you network", Toast.LENGTH_SHORT).show();
return;
}
if (list == null || list.size() == 0) {
Toast.makeText(getContext(), "not match", Toast.LENGTH_SHORT).show();
return;
}
for (int i = 0; i < list.size(); i++) {
SkuDetails skuDetails = list.get(i);
BillingFlowParams.Builder builder;
//沒有訂閱記錄
if (currentSusProduct == null) {
//沒有有效期內(nèi)的訂閱記錄
builder = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails);
} else {
//有訂閱記錄,根據(jù)type判斷是升級(jí)還是降級(jí)
builder = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.setReplaceSkusProrationMode(productType == ProductType.UPGRADE ? IMMEDIATE_WITH_TIME_PRORATION : DEFERRED)
.setOldSku(currentSusProduct.getSku(), currentSusProduct.getPurchaseToken());
}
int code = billingClient.launchBillingFlow(getActivity(getContext()), builder.build()).getResponseCode();
if (code != BillingClient.BillingResponseCode.OK && payStateListener != null) {
payStateListener.onPurchasesFail(skuDetails.getTitle(), code);
}
/*if (code == BillingClient.BillingResponseCode.OK && payStateListener != null) {
payStateListener.onSubscribeSuccess(productType);
}*/
}
}
});
}
private Context getContext() {
return weakContext.get();
}
private static Activity getActivity(Context context) {
return context == null ? null : (context instanceof Activity ? (Activity) context : (context instanceof ContextWrapper ? getActivity(((ContextWrapper) context).getBaseContext()) : null));
}
/**
* 消耗購買的內(nèi)購產(chǎn)品
*
* @param purchase 要消費(fèi)掉的產(chǎn)品類
* @param purchaseType 產(chǎn)品類型
*/
public void consumePurchase(Purchase purchase, String purchaseType) {
LogUtil.e(TAG, "consumeAsync");
if (purchaseType.equals(BillingClient.SkuType.INAPP)) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(purchase.getDeveloperPayload())
.build();
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
payStateListener.onConsumeFinished(purchase);
}
});
} else if (purchaseType.equals(BillingClient.SkuType.SUBS)) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
payStateListener.onConsumeFinished(purchase);
}
}
});
} else {
payStateListener.onConsumeFinished(purchase);
}
}
}
}
private GooglePayStateListener payStateListener;
/**
* @param payStateListener google pay支付相關(guān)回調(diào)額數(shù)據(jù)
*/
public void setGooglePayStateListener(GooglePayStateListener payStateListener) {
this.payStateListener = payStateListener;
}
/**
* 跳轉(zhuǎn)到google play訂閱界面
*
* @param context
*/
public static void jump2GooglePlaySub(Context context) {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri content_url = Uri.parse("https://play.google.com/store/account/subscriptions");
intent.setData(content_url);
context.startActivity(intent);
}
/**
* 跳轉(zhuǎn)到google play訂閱指定產(chǎn)品id界面
*
* @param context
*/
public static void jump2GooglePlaySubById(Context context, String productId) {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri content_url = Uri.parse("https://play.google.com/store/account/subscriptions?sku=" + productId + "&package=" + context.getPackageName());
intent.setData(content_url);
context.startActivity(intent);
}
public void endConnection() {
if (billingClient != null && billingClient.isReady()) {
billingClient.endConnection();
}
}
public void dismissConnect() {
if (billingClient != null) billingClient.endConnection();
}
}
class PayUtils private constructor(context: Context) : PurchasesUpdatedListener {
private val context: Context? = WeakReference(context).get()
private var currentSusProduct: Purchase? = null
/**
* @description 使用by lazy方式楚里,在第一次使用的時(shí)候就會(huì)進(jìn)行初始化
*/
private val billingClient by lazy {
BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
}
fun init() {
//進(jìn)行谷歌服務(wù)器連接監(jiān)聽
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
LogUtil.e(TAG, "init Success")
payStateListener?.onConnectionSuccess()
} else {
LogUtil.e(TAG, "init Fail")
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
/**
* @description 監(jiān)聽谷歌包發(fā)生改變断部,在by lazy時(shí)就做了監(jiān)聽處理
* @param billingResult 購買包購買結(jié)果
* @param purchaseList 為空代表降級(jí),不為空則為正常購買或升級(jí)
*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: List<Purchase>?) {
payStateListener?.onPurchasesUpdated(billingResult, purchaseList)
}
/**
* 查詢已購買的產(chǎn)品班缎,通常用于補(bǔ)單或購買前校驗(yàn)
*/
fun queryHavePurchased() {
val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
if (areSubscriptionsSupported()) {
val subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
if (subscriptionResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchasesResult.purchasesList.addAll(subscriptionResult.purchasesList)
} else {
LogUtil.e(TAG, "Got an error response trying to query subscription purchases")
}
payStateListener?.onQueryPurchasedSuccess(subscriptionResult)
}
LogUtil.e(TAG, "queryHavePurchased: " + purchasesResult.purchasesList.toString())
}
/**
* @return 是否有可訂閱的商品
*/
private fun areSubscriptionsSupported(): Boolean {
val result = billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS)
val responseCode = result.responseCode
if (responseCode != BillingClient.BillingResponseCode.OK) {
LogUtil.e(TAG, "areSubscriptionsSupported() got an error response: $responseCode")
}
return responseCode == BillingClient.BillingResponseCode.OK
}
/**
* 下單購買消耗性商品的sku
*
* @param sku 產(chǎn)品的sku數(shù)組
*/
fun purchase(sku: String) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) != 0) {
XMToastUtil.showShortToastCenter(context!!.getString(R.string.pay_google_device_not_support),
context)
return
}
val params = SkuDetailsParams.newBuilder()
val skuList: MutableList<String> = ArrayList()
skuList.add(sku)
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build(),
SkuDetailsResponseListener { billingResult, list ->
if (list == null || list.size == 0) {
LogUtil.e(TAG,
"not match" + billingResult.debugMessage + billingResult.responseCode)
XMToastUtil.showShortToastCenter(context!!.getString(R.string.pay_google_no_package),
context)
payStateListener?.onPurchasesFail(sku,
BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED)
return@SkuDetailsResponseListener
}
if (list == null) return@SkuDetailsResponseListener
for (i in list.indices) {
val skuDetails = list[i]
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
val code =
billingClient.launchBillingFlow(getActivity(context),
flowParams).responseCode
if (code != BillingClient.BillingResponseCode.OK) {
payStateListener?.onPurchasesFail(skuDetails.title, code)
}
/* if (code == BillingClient.BillingResponseCode.OK) {
payStateListener?.onSubscribeSuccess(ProductType.NORMAL)
}*/
}
})
}
/**
* @description 消費(fèi)所有未消費(fèi)的產(chǎn)品包蝴光,含訂閱消費(fèi)包
*/
fun consumePurchase() {
val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
val purchasesList = purchasesResult.purchasesList ?: return
for (i in purchasesList.indices) {
val purchase = purchasesList[i]
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.setDeveloperPayload(purchase.developerPayload)
.build()
billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken ->
LogUtil.e(">>>>onConsumeResponse:",
"message:" + billingResult.debugMessage + billingResult.responseCode)
payStateListener?.onConsumeFinished(purchase)
}
}
}
/**
* @description 訂閱包的購買
* @param newSku 對(duì)應(yīng)的商品id
* @param productType 具體升降級(jí)類型
*/
fun subscribe(newSku: String, productType: ProductType) {
LogUtil.e(TAG, "newSku:$newSku")
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) != 0) {
XMToastUtil.showShortToastCenter(context!!.getString(R.string.pay_google_device_not_support),
context)
return
}
val skuList = ArrayList<String>()
skuList.add(newSku)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)
billingClient.querySkuDetailsAsync(params.build(),
SkuDetailsResponseListener { billingResult, list ->
val subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
LogUtil.e(TAG, "當(dāng)前訂閱的套餐:" + subscriptionResult.purchasesList.toString());
if (subscriptionResult.purchasesList != null && subscriptionResult.purchasesList.isNotEmpty()) {
currentSusProduct = subscriptionResult.purchasesList[0]
}
if (billingResult.responseCode == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
XMToastUtil.showShortToastCenter(context!!.getString(R.string.pay_check_network),
context)
return@SkuDetailsResponseListener
}
if (list.isNullOrEmpty()) {
XMToastUtil.showShortToastCenter(context!!.getString(R.string.pay_google_no_package),
context)
return@SkuDetailsResponseListener
}
for (i in list.indices) {
val skuDetails = list[i]
var builder: BillingFlowParams.Builder
//沒有訂閱記錄
builder = if (currentSusProduct == null) {
//沒有有效期內(nèi)的訂閱記錄
BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
} else {
//有訂閱記錄,根據(jù)type判斷是升級(jí)還是降級(jí)
BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.setReplaceSkusProrationMode(if (productType == ProductType.UPGRADE) BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION else BillingFlowParams.ProrationMode.DEFERRED)
.setOldSku(currentSusProduct!!.sku, currentSusProduct!!.purchaseToken)
}
val code =
billingClient.launchBillingFlow(getActivity(context),
builder.build()).responseCode
if (code != BillingClient.BillingResponseCode.OK) {
payStateListener?.onPurchasesFail(skuDetails.title, code)
}
}
})
}
/**
* 消耗包購買
* @param purchase 要消費(fèi)掉的產(chǎn)品類
* @param purchaseType 產(chǎn)品類型
*/
fun consumePurchase(purchase: Purchase, purchaseType: String) {
LogUtil.e(TAG, "consumeAsync")
if (purchaseType == BillingClient.SkuType.INAPP) {
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.setDeveloperPayload(purchase.developerPayload)
.build()
billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken ->
payStateListener?.onConsumeFinished(purchase)
}
} else if (purchaseType == BillingClient.SkuType.SUBS) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
payStateListener?.onConsumeFinished(purchase)
}
}
} else {
payStateListener?.onConsumeFinished(purchase)
}
}
}
}
//設(shè)置播放狀態(tài)回調(diào)監(jiān)聽
private var payStateListener: GooglePayStateListener? = null
fun setGooglePayStateListener(payStateListener: GooglePayStateListener?) {
this.payStateListener = payStateListener
}
/**
* @description 連接狀態(tài)關(guān)閉
*/
fun endConnection() {
if (billingClient?.isReady) {
billingClient?.endConnection()
}
}
/**
* @description 強(qiáng)制關(guān)閉
*/
fun dismissConnect() {
billingClient?.endConnection()
}
companion object : SingletonHolder<PayUtils, Context>(::PayUtils) {
private const val TAG = "GooglePayUtils"
/**
* @description 獲取當(dāng)前context的實(shí)際依附Activity
*/
private fun getActivity(context: Context?): Activity? {
return when (context) {
null -> null
is Activity -> context
is ContextWrapper -> getActivity(
context.baseContext)
else -> null
}
}
/**
* 跳轉(zhuǎn)到google play訂閱界面
*/
fun jump2GooglePlaySub(context: Context) {
val intent = Intent()
intent.action = "android.intent.action.VIEW"
val contentUrl = Uri.parse("https://play.google.com/store/account/subscriptions")
intent.data = contentUrl
context.startActivity(intent)
}
/**
* 跳轉(zhuǎn)到google play訂閱指定產(chǎn)品id界面
*/
fun jump2GooglePlaySubById(context: Context, productId: String) {
val intent = Intent()
intent.action = "android.intent.action.VIEW"
val contentUrl =
Uri.parse("https://play.google.com/store/account/subscriptions?sku=" + productId + "&package=" + context.packageName)
intent.data = contentUrl
context.startActivity(intent)
}
}
}
2.PayUtils的調(diào)用方式
代碼邏輯圖
//a.初始化
googlePayUtils = PayUtils.getInstance(activity)
googlePayUtils?.setGooglePayStateListener(this)
googlePayUtils?.init()
//b.setGooglePayStateListener的監(jiān)聽回調(diào)
/**
* 谷歌服務(wù)連接成功吝梅,查詢谷歌訂單做無感補(bǔ)單
*/
override fun onConnectionSuccess() {
googlePayUtils?.queryHavePurchased()
isReady = true
}
/**
* 查詢谷歌訂單做無感補(bǔ)單回調(diào)
*/
override fun onQueryPurchasedSuccess(purchasesResult: Purchase.PurchasesResult) {
this.purchasesResult = purchasesResult
for (purchase in purchasesResult.purchasesList) {
if (!purchase.isAcknowledged) {
productType = ProductType.UPGRADE
closeNotification = true
googlePayUtils?.consumePurchase(purchase, BillingClient.SkuType.SUBS)
}
}
}
/**
* 購買成功做消費(fèi)處理虱疏,或降級(jí)成功彈窗處理
*/
override fun onPurchasesUpdated(billingResult: BillingResult,
purchaseList: MutableList<Purchase>?) {
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
if (purchaseList != null && purchaseList.size > 0) {
for (purchase in purchaseList) {
val type =
if (productType == ProductType.NORMAL) BillingClient.SkuType.INAPP else BillingClient.SkuType.SUBS
googlePayUtils?.consumePurchase(purchase, type)
}
} else {
callback?.showSubscriptionDown()
}
}
else -> {
LogUtil.e(TAG, "message:" + billingResult.debugMessage)
}
}
}
/**
* 谷歌購買失敗提示
*/
override fun onPurchasesFail(sku: String, code: Int) {
XMToastUtil.showShortToastCenter(activity.getString(R.string.pay_google_purchase_fail), activity)
}
/**
* @description 谷歌產(chǎn)品消費(fèi)完畢,進(jìn)行本地存儲(chǔ)苏携,防止漏單
*/
override fun onConsumeFinished(purchase: Purchase) {
LogUtil.e(TAG, "onConsumeFinished: google消耗商品" + purchase.packageName + "成功做瞪,開始服務(wù)器消耗")
if (!closeNotification)
XMToastUtil.showShortToastCenter(activity.getString(R.string.pay_google_wait_a_while), activity)
//存儲(chǔ)谷歌訂單信息
localData = GooglePlayLocalData(orderId, purchase, productType)
GooglePayInfoUtils.saveGooglePayInfo(localData)
//谷歌訂單信息上報(bào)后臺(tái)
viewModel?.postGooglePayCallback(orderId,
purchase.signature,
purchase.originalJson,
if (productType == ProductType.DOWNGRADE) "2" else "1")
}
//c.開始支付入口
/**
* @description 開始支付
* @param
* @time 2020/11/11 11:41
*/
fun startPay(product: PurchaseData.Product,
productType: ProductType,
memberId: String,
contentId: String = "") {
this.product = product
this.productType = productType
this.contentId = contentId
this.memberId = memberId
LogUtil.e(TAG,
"startPay product: $product>>>productType: $productType>>>contentId: $contentId>>>memberId: $memberId")
if (!isReady) {
callback?.showSubscriptionServiceError()
return
}
closeNotification = false
//是否為單點(diǎn)
if (productType == ProductType.NORMAL) {
callback?.showChooseDialog()
} else {
// 1.支付為訂閱,檢測是否存在其他平臺(tái)購買的訂閱套餐
checkDeviceSupportPay(purchasesResult)
}
}
/**
* @description 檢測是否存在其他平臺(tái)購買的訂閱套餐
* @param
* @time 2020/11/11 11:42
*/
private fun checkDeviceSupportPay(purchasesResult: Purchase.PurchasesResult?) {
if (subscriptionResultData?.resultCode == XMediaErrorCode.CODE_SUCCESS) {
// 2.當(dāng)前訂閱是否存在谷歌支付方式或沒有訂閱歷史
if (isSubscriptionContainerGooglePay()) {
// 3.檢測當(dāng)前谷歌賬號(hào)是否綁定過其他賬號(hào)
queryGooglePayHistory(purchasesResult)
} else {
callback?.showOtherDevicePay()
}
} else {
callback?.showSubscriptionServiceError()
}
}
/**
* @description 當(dāng)前訂閱是否存在谷歌支付方式或沒有訂閱歷史
* @param
* @time 2020/11/11 14:14
*/
private fun isSubscriptionContainerGooglePay(): Boolean {
subscriptionResultData?.subscription?.productTime?.paymentMethod?.let {
return it == GOOGLE_PAY || it == OFFLINE_PAY
}
return true
}
/**
* @description 檢測當(dāng)前谷歌賬號(hào)是否綁定過其他賬號(hào)
* @param
* @time 2020/11/11 14:13
*/
private fun queryGooglePayHistory(purchasesResult: Purchase.PurchasesResult?) {
purchasesResult?.let {
val requestValue = StringBuffer()
for (index in it.purchasesList.indices) {
requestValue.append(it.purchasesList[index].orderId)
if (index != it.purchasesList.size - 1) requestValue.append(",")
}
if (requestValue.isNotEmpty()) {
// 4.檢測當(dāng)前谷歌賬號(hào)是否綁定過其他賬號(hào)
viewModel?.requestOrderCheck(requestValue.toString())
} else {
callback?.showChooseDialog()
}
}
if (purchasesResult == null) {
//無購買歷史,可以準(zhǔn)備下預(yù)訂單
callback?.showChooseDialog()
}
}
/**
* @description 5.處理下單前的檢測結(jié)果
*/
private fun handleOrderCheckData(orderCheckData: OrderCheckData) {
if (orderCheckData.resultCode == XMediaErrorCode.CODE_SUCCESS) {
if (orderCheckData.memberId == null) {
callback?.showChooseDialog()
return
} else {
//6.同一用戶装蓬,可以準(zhǔn)備下預(yù)訂單
if (orderCheckData.memberId == memberId) {
viewModel?.requestOrder(product?.productId.toString(), contentId)
} else {
callback?.showGoogleAccountConflict()
}
}
} else {
callback?.showOtherDevicePay()
}
}
//d.與后臺(tái)的接口回調(diào)
viewModel?.run {
//接口著拭,當(dāng)前已訂閱產(chǎn)品
subscriptionData.observe(activity) {
subscriptionResultData = it
}
//接口,5.下單前檢測回調(diào)通知牍帚,防止同一賬號(hào)用不同方式購買儡遮,導(dǎo)致續(xù)訂混亂
orderCheckData.observe(activity) {
handleOrderCheckData(it)
}
//接口,7.下單回調(diào)通知暗赶,提交一個(gè)預(yù)訂單給后臺(tái)
orderData.observe(activity) {
if (it.resultCode == XMediaErrorCode.CODE_SUCCESS) {
orderId = it.orderId
when (payType) {
GOOGLE_PAY -> {
closeNotification = false
if (productType == ProductType.NORMAL) {
//8.下單消費(fèi)包
startGoogleConsume()
} else {
//9.下單訂閱包
startGoogleSubscription()
}
}
else -> {
}
}
} else {
callback?.showOrderServiceError()
}
}
//接口鄙币,10.支付回調(diào)通知,谷歌購買已成功蹂随,需將數(shù)據(jù)上報(bào)給后臺(tái)開套餐
googlePayResultData.observe(activity) {
if (it.resultCode == XMediaErrorCode.CODE_SUCCESS) {
localData?.let { localData -> GooglePayInfoUtils.removeGooglePayInfo(localData) }
if (!closeNotification){
// 11.回調(diào)刷新訂閱界面
callback?.showSubscriptionSuccessful()
closeNotification = false
}
}
}
}
//e.開始使用PayUtils進(jìn)行谷歌的訂閱和消費(fèi)包購買
/**
* @description 開始谷歌單次購買
* @param
* @time 2020/11/13 10:34
*/
private fun startGoogleConsume() {
product?.let {
var sku = ""
for (payGateway in it.payGateways) {
if (payGateway.gatewayCode == GOOGLE_PAY) {
sku = payGateway.externalCode
}
}
googlePayUtils?.purchase(sku)
}
}
/**
* @description 開始谷歌訂閱
* @param
* @time 2020/11/11 15:19
*/
private fun startGoogleSubscription() {
//訂閱下單回調(diào)
product?.let { product ->
var sku = ""
for (payGateway in product.payGateways) {
if (payGateway.gatewayCode == GOOGLE_PAY) {
sku = payGateway.externalCode
}
}
subscriptionResultData?.subscription?.product?.grade?.let {
if (it.isNotEmpty() && it.toInt() > product.grade) {
productType = ProductType.DOWNGRADE
}
}
googlePayUtils?.subscribe(sku, productType)
}
}
//f.UI相關(guān)的回調(diào)接口
interface PayControllerCallback {
fun showChooseDialog()
fun showOtherDevicePay()
fun showSubscriptionServiceError()
fun showGoogleAccountConflict()
fun showOrderServiceError()
fun showSubscriptionSuccessful()
fun showExitNoActiveOrder()
fun showSubscriptionDown()
}