谷歌支付配置及安卓集成2020-11-25

谷歌支付

1.流程

image-20201125151026147.png

2.谷歌后台API配置

启用API

网址:https://play.google.com/console/u/0/developers/6637358084117674478/api-access

image-20201125152618027.png

进入谷歌管理后台,当未启动时需要启动对应API,随后会提示去创建服务账号

创建服务账号

网址:https://console.developers.google.com/iam-admin/serviceaccounts/create?project=wewatch-98a41

image-20201125151628369.png

选中应用(例如选择wewatch)>> 输入服务账号名称 >> 输入服务账号ID(这时候会有提示 应用名-应用ID.@appspot.gserviceaccount.com,注意核实应用是否一致) >>当创建完毕之后会得到一个p12文件,需要提供给后台,同时会需要记录下对应p12文件的密码

查看对应用户以及权限

网址:https://console.cloud.google.com/iam-admin/iam?project=wewatch-98a41

image-20201125153633202.png

当创建成功时,可以到IAM中查看对应的账号权限,为Editor权限

2.开启消息通知(Cloud Pub/Sub

定义:Cloud Pub/Sub是一种托管式的消息服务,当应用购买对于商品成功时,就会去找到对应的谷歌服务用户,去通知其状态的改变。

开启Cloud Pub/Sub网址:https://cloud.google.com/pubsub/

点击转到控制台,会到达该网址:https://console.cloud.google.com/cloudpubsub/topic/list?folder=&organizationId=&project=wewatch-98a41

image-20201125154545807.png

创建主题

image-20201125154657884.png

当点击创建主题时,会要求输入对应的主题名称

image-20201125155139663.png

创建成功后,会出现对应的主题ID和主题名称,名称是用于测试的,选择最右边的编辑按钮,或者直接点击左侧的订阅专栏,都能为主题创建订阅

创建订阅

image-20201125155255376.png

进入之后输入对应的订阅名称,选择传送类型为拉取,用于测试主题消息的连通性,其余选项选择默认,当创建成功时会进入订阅界面,这时候复制对应的主题名称到测试页面。正式订阅时,需要改为推送,这里拉取只是测试连通性。

网址:https://play.google.com/console/u/0/developers/6637358084117674478/app/4976005302300764813/monetization-setup

image-20201125155510110.png

权限授予和校验流程

原理图:

image-20201125161016130.png

首先进入Google play console >> 选择应用 >> 选择获利设置

当进入该页面以后,输入刚刚复制的主题名称

image-20201125155918230.png

当权限失败时就会出现该情况,这时候需要检查主题和订阅的权限。

1.给谷歌云消息账号添加推送的权限,例如:签名是应用的ID,后面格式一致91804594755@cloudservices.gserviceaccount.com

image-20201125160149887.png

点击添加成员后,输入账号,并给予推送权限

image-20201125160309620.png

2.给订阅者添加权限

image-20201125160544704.png

与主题添加权限一致,这里的账号添加的是谷歌API后台账号,即一开始我们创建谷歌后台账号,到此之后,便能确保当谷歌支付有状态改变时,谷歌能及时通知到后台,去对订单做对应的处理。

3.谷歌应用测试人员配置

谷歌控制台的测试人员配置

image-20201125161848417.png

在选项中输入对顶的gmail邮箱

进入应用配置测试人员

网址:https://play.google.com/console/u/0/developers/6637358084117674478/app/4976005302300764813/tracks/internal-testing?tab=testers

image-20201125162247785.png

点击应用>> 测试 >> 内部测试

点击图上的右键头,输入对应的邮箱地址,随后将底部的复制链接,发送给对应的谷歌账号,同意授权之后,需要等待一段时间,一般在24小时之内,该账号就有权限。校验测试购买的权限之一,就是进入到Play store商店中,查找对应的APP名字,如果能查到就有对应权限

测试补充说明

1.手机或平板需要有对应的谷歌服务、谷歌商店、稳定的vpn,才能顺利购买,通常可以使用Google Go三方软件进行配置检测,华为magic系统新手机除外

2.对于测试前提,需要上架对应的应用,当前上架应用需要审核,审核时间为7天,只有应用审核通过之后,才能进行以下测试

image-20201125170559803.png

3.对于测试周期,订阅的周期测试为5分钟,即自动续订是每5分钟一次,会续订6次,对于消费包,没有限制。对于订阅的升降级,谷歌内部有提供多种结算方式:

BillingFlowParams.IMMEDIATE_WITH_TIME_PRORATION(立即生效,购买的新套餐首月价格会减去生效包剩余价格)

BillingFlowParams.DEFERRED(下一个结算周期生效)

其余结算方式请查看该网址: https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode

4.订阅或消费商品的创建

网址:https://play.google.com/console/u/0/developers/6637358084117674478/app/4976005302300764813/managed-products

image-20201125163439803.png

点击应用>> 商品 >> 应用内商品

消耗商品配置

该页面为消费商品,即一次性商品,通过最右侧的创建商品,可以创建对应的商品信息

image-20201125163706946.png

输入产品ID和对应名称,点击最底下的创建模板

网址: https://play.google.com/console/u/0/developers/6637358084117674478/pricing-templates

image-20201125163830211.png

该模板可以选择不同国家,并且根据实际汇率进行价格转换

image-20201125164515135.png

选择完价格之后,对商品进行保存

订阅商品配置

image-20201125164654082.png

点击创建订阅内容

image-20201125164743186.png

订阅相比消耗,多了结算周期,和首订等功能添加,最后需要点击右下角的保存按钮,便可以生效。

5.安卓终端支付对接

a.逻辑图

image-20201125165420414.png

订阅流程:

1.判断是否为预付费用户

2.判断是否为订阅,默认未订阅

3.判断当前用户是否存在订阅套餐,若不是Google方式购买则不允许购买,若是则继续

4.判断已购买的套餐,对应的谷歌订单中的用户是否为同一用户,如果不是则不允许购买,若是则继续

5.每次购买前会查询谷歌订单,用于补单处理

6.访问后台API创建对应的订单

7.调用谷歌的订阅购买

8.调用谷歌的消费接口

9.将订单信息保存到本地中,并进行加密处理,防止漏单情况

10.将对应订单信息回调后台,通知后台去开通订单,当返回成功则,删除订单信息,若失败或存储,在应用启动时进行补单操作

11.当升级订阅时,会立马提示刷新订阅界面,当降级时,会有通知弹窗告知用户下个结算周期生效,最终完成订单处理

消费流程:

1.判断是否为预付费用户

2.判断是否为消费包

3.提示支付方式的弹窗

4.支付弹窗选择谷歌支付

5.访问后台API创建对应的订单

6.调用谷歌消费包购买

7.调用谷歌的消费接口

8.将订单信息保存到本地中,并进行加密处理,防止漏单情况

9.将对应订单信息回调后台,通知后台去开通订单,当返回成功则,删除订单信息,若失败或存储,在应用启动时进行补单操作

10.当购买成功时,会立马提示刷新界面,并将消费包按钮置与不可点击状态

b.谷歌API对应的提示码

错误码提示.png

c.代码

官方文档网址:https://developer.android.google.cn/google/play/billing/billing_library_overview

1.GoogleUtils.java和PayUtils.kt

public class GooglePayUtils implements PurchasesUpdatedListener {
    //持弱引用仿内存泄露
    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添加产品包更新回调监听 PurchasesUpdatedListener
        billingClient = BillingClient.newBuilder(getContext()).setListener(this).enablePendingPurchases().build();
        //开始连接谷歌服务
        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);
    }


    /**
     * 查询已购买的产品
     */
    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 产品的sku数组
     */
    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 对应的商品id
     * @param  productType 具体升降级类型
     */
    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, "当前订阅的套餐:" + 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) {
                                //没有有效期内的订阅记录
                                builder = BillingFlowParams.newBuilder()
                                        .setSkuDetails(skuDetails);

                            } else {
                                //有订阅记录,根据type判断是升级还是降级

                                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));
    }

    /**
     * 消耗购买的内购产品
     *
     * @param purchase     要消费掉的产品类
     * @param purchaseType 产品类型
     */
    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支付相关回调额数据
     */
    public void setGooglePayStateListener(GooglePayStateListener payStateListener) {
        this.payStateListener = payStateListener;
    }


    /**
     * 跳转到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);
    }

    /**
     * 跳转到google play订阅指定产品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方式,在第一次使用的时候就会进行初始化
     */
    private val billingClient by lazy {
        BillingClient.newBuilder(context)
            .setListener(this)
            .enablePendingPurchases()
            .build()
    }

    fun init() {
        //进行谷歌服务器连接监听
        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 监听谷歌包发生改变,在by lazy时就做了监听处理
     * @param billingResult 购买包购买结果
     * @param purchaseList 为空代表降级,不为空则为正常购买或升级
     */
    override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: List<Purchase>?) {
        payStateListener?.onPurchasesUpdated(billingResult, purchaseList)
    }

    /**
     * 查询已购买的产品,通常用于补单或购买前校验
     */
    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 产品的sku数组
     */
    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 消费所有未消费的产品包,含订阅消费包
     */
    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 对应的商品id
     * @param  productType 具体升降级类型
     */
    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, "当前订阅的套餐:" + 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) {
                        //没有有效期内的订阅记录
                        BillingFlowParams.newBuilder()
                            .setSkuDetails(skuDetails)
                    } else {
                        //有订阅记录,根据type判断是升级还是降级
                        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     要消费掉的产品类
     * @param purchaseType 产品类型
     */
    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)
                }
            }
        }
    }

    //设置播放状态回调监听
    private var payStateListener: GooglePayStateListener? = null
    fun setGooglePayStateListener(payStateListener: GooglePayStateListener?) {
        this.payStateListener = payStateListener
    }

    /**
     * @description 连接状态关闭
     */
    fun endConnection() {
        if (billingClient?.isReady) {
            billingClient?.endConnection()
        }
    }

    /**
     * @description 强制关闭
     */
    fun dismissConnect() {
        billingClient?.endConnection()
    }

    companion object : SingletonHolder<PayUtils, Context>(::PayUtils) {
        private const val TAG = "GooglePayUtils"

        /**
         * @description 获取当前context的实际依附Activity
         */
        private fun getActivity(context: Context?): Activity? {
            return when (context) {
                null -> null
                is Activity -> context
                is ContextWrapper -> getActivity(
                    context.baseContext)
                else -> null
            }
        }

        /**
         * 跳转到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)
        }

        /**
         * 跳转到google play订阅指定产品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的调用方式

代码逻辑图

image-20201125201202941.png
//a.初始化
googlePayUtils = PayUtils.getInstance(activity)
googlePayUtils?.setGooglePayStateListener(this)
googlePayUtils?.init()
//b.setGooglePayStateListener的监听回调

/**
 * 谷歌服务连接成功,查询谷歌订单做无感补单
 */
override fun onConnectionSuccess() {
    googlePayUtils?.queryHavePurchased()
    isReady = true
}

/**
 * 查询谷歌订单做无感补单回调
 */
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)
        }
    }
}

/**
 * 购买成功做消费处理,或降级成功弹窗处理
 */
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  谷歌产品消费完毕,进行本地存储,防止漏单
 */
override fun onConsumeFinished(purchase: Purchase) {
    LogUtil.e(TAG, "onConsumeFinished: google消耗商品" + purchase.packageName + "成功,开始服务器消耗")
    if (!closeNotification)
    XMToastUtil.showShortToastCenter(activity.getString(R.string.pay_google_wait_a_while), activity)

    //存储谷歌订单信息
    localData = GooglePlayLocalData(orderId, purchase, productType)
    GooglePayInfoUtils.saveGooglePayInfo(localData)
    //谷歌订单信息上报后台
    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
        //是否为单点
        if (productType == ProductType.NORMAL) {
            callback?.showChooseDialog()
        } else {
            // 1.支付为订阅,检测是否存在其他平台购买的订阅套餐
            checkDeviceSupportPay(purchasesResult)
        }
    }

    /**
     * @description 检测是否存在其他平台购买的订阅套餐
     * @param
     * @time 2020/11/11 11:42
     */
    private fun checkDeviceSupportPay(purchasesResult: Purchase.PurchasesResult?) {

        if (subscriptionResultData?.resultCode == XMediaErrorCode.CODE_SUCCESS) {
             // 2.当前订阅是否存在谷歌支付方式或没有订阅历史
            if (isSubscriptionContainerGooglePay()) {
                // 3.检测当前谷歌账号是否绑定过其他账号
                queryGooglePayHistory(purchasesResult)
            } else {
                callback?.showOtherDevicePay()
            }
        } else {
            callback?.showSubscriptionServiceError()
        }
    }

    /**
     * @description 当前订阅是否存在谷歌支付方式或没有订阅历史
     * @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 检测当前谷歌账号是否绑定过其他账号
     * @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.检测当前谷歌账号是否绑定过其他账号
                viewModel?.requestOrderCheck(requestValue.toString())
            } else {
                callback?.showChooseDialog()
            }
        }

        if (purchasesResult == null) {
            //无购买历史,可以准备下预订单
            callback?.showChooseDialog()
        }
    }

    /**
     * @description 5.处理下单前的检测结果
     */
    private fun handleOrderCheckData(orderCheckData: OrderCheckData) {
        if (orderCheckData.resultCode == XMediaErrorCode.CODE_SUCCESS) {
            if (orderCheckData.memberId == null) {
                callback?.showChooseDialog()
                return
            } else {
                //6.同一用户,可以准备下预订单
                if (orderCheckData.memberId == memberId) {
                    viewModel?.requestOrder(product?.productId.toString(), contentId)
                } else {
                    callback?.showGoogleAccountConflict()
                }
            }
        } else {
            callback?.showOtherDevicePay()
        }
    }



//d.与后台的接口回调
viewModel?.run {
    //接口,当前已订阅产品
    subscriptionData.observe(activity) {
        subscriptionResultData = it
    }
    //接口,5.下单前检测回调通知,防止同一账号用不同方式购买,导致续订混乱
    orderCheckData.observe(activity) {
        handleOrderCheckData(it)
    }

    //接口,7.下单回调通知,提交一个预订单给后台
    orderData.observe(activity) {
        if (it.resultCode == XMediaErrorCode.CODE_SUCCESS) {
            orderId = it.orderId
            when (payType) {
                GOOGLE_PAY -> {
                    closeNotification = false
                    if (productType == ProductType.NORMAL) {
                        //8.下单消费包
                        startGoogleConsume()
                    } else {
                        //9.下单订阅包
                        startGoogleSubscription()
                    }
                }
                else -> {

                }
            }
        } else {
            callback?.showOrderServiceError()
        }
    }

    //接口,10.支付回调通知,谷歌购买已成功,需将数据上报给后台开套餐
    googlePayResultData.observe(activity) {
        if (it.resultCode == XMediaErrorCode.CODE_SUCCESS) {
            localData?.let { localData ->               GooglePayInfoUtils.removeGooglePayInfo(localData) }

            if (!closeNotification){
                // 11.回调刷新订阅界面
                callback?.showSubscriptionSuccessful()
                closeNotification = false
            }
        }
    }
}
//e.开始使用PayUtils进行谷歌的订阅和消费包购买
/**
 * @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() {
    //订阅下单回调
    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相关的回调接口
interface PayControllerCallback {
    fun showChooseDialog()

    fun showOtherDevicePay()

    fun showSubscriptionServiceError()

    fun showGoogleAccountConflict()

    fun showOrderServiceError()

    fun showSubscriptionSuccessful()

    fun showExitNoActiveOrder()

    fun showSubscriptionDown()
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容