订阅3.0.0 - xiaoniudonghe2015/Android-Java-Code-Style GitHub Wiki

1.政策

2020年11月1日前需要将billing version至少升级到2,11月1日后Google会强制打开Resubscribe功能。

从2021年8月2日开始,所有新应用都必须使用Billing Library版本3或更高版本。到2021年11月1日,对现有应用程序的所有更新都必须使用Billing Library版本3或更高版本。

2.新功能

2.1用户可使用现金付款

新增了一种新的付款方式,该方式可以在设备外完成交易,例如在当地的便利店用现金付款。根据世界银行的数据,全世界有20亿人无法使用银行帐户,因此现金交易可以帮助解锁新买家,尤其是在新兴市场中,现金是一种流行的付款方式。

宣布用户可以轻松地在印度尼西亚和马来西亚的50,000多个地点使用现金一次性购买商品,包括在7-11和Alfamart等领先零售商处。

弹窗上的内容:您可以在附近的商店使用现金或信用卡购买预先已充值的Google Play礼品卡。

2.2用户可用应用外订阅促销代码

在I / O 2019上,我们推出了一次性订阅促销代码,该代码是唯一的字母数字代码,可以分发给各个用户以进行兑换。由于此订阅是在您的应用程序外部启动的,因此它仅适用于使用Billing Library 2.0或更高版本的开发人员。当您提供免费订阅的促销代码时,即使您的应用尚未安装,用户也可以轻松地在Play商店中兑换它们。该代码可被多个用户兑换,并可用于营销活动以推动收购。例如,您可以在广告或社交促销中发布自定义代码,以创造性地与潜在的新用户互动。用户可以通过在购买订阅时在其付款方式中输入自定义代码来在您的应用中兑换。

当时测得时候,复制粘贴兑换码,兑换的按钮不可点,手动输入可以

2.3对购买进行归因

许多游戏和应用程序都需要确保将应用程序内购买归因于特定的游戏内角色,头像或个人资料。允许您在启动购买流程时指定此信息。购买完成后,您可以检索信息并正确归属购买。这样就无需使用已弃用的AIDL开发人员有效负载来构建自定义解决方案。

2.4以Kotlin和java版本提供,使用unity新插件

对于使用Unity的游戏开发人员,我们还启动了一个基于Billing Library 3的Unity IAP插件。该插件使Unity开发人员可以满足Billing Library版本的要求,并访问所有Play计费功能。

3.应用内商品类型

Google Play 结算服务可用于销售以下类型的应用内商品:

一次性商品:需要一次性(非定期)向用户收取费用(通过用户提供的付款方式)的应用内商品。Google Play 管理中心将一次性商品称为“受管理的商品”,Google Play 结算库将其称为“INAPP”。

奖励产品:需要用户观看视频广告才能获得的应用内商品。Google Play 管理中心将奖励的产品称为“奖励产品”,Google Play 结算库则将其称为“INAPP”。

订阅:需要定期向用户收取相关费用(通过用户提供的付款方式)的应用内商品。Google Play 结算库将这些订阅内容称为“SUBS”。

4.订单 ID

Google Play 结算服务使用购买令牌和订单 ID 跟踪商品和交易。

订单 ID 是一个字符串,表示 Google Play 上的金融交易。订单ID是表示在谷歌Play这样的金融交易的字符串。此字符串包含在通过电子邮件发送给买方的收据中。您可以使用订单ID来管理销售和付款报告中使用的退款。

对于一次性商品和奖励产品,每次购买交易都会创建一个新令牌和一个新订单 ID。

对于订阅,首次购买交易会创建一个购买令牌和一个订单 ID。对于每个连续的结算周期,购买令牌会保持不变,并且系统会发出新的订单 ID。升级、降级和重新注册都会创建新的购买令牌和订单 ID

5.集成方式

依赖:

implementation 'com.android.billingclient:billing:3.0.0'

去混淆:

-keep class com.android.vending.billing.**

连接到 Google Play:

您必须先建立与 Google Play 的连接,然后才能发送 Google Play 结算服务请求,建立连接的具体操作步骤如下

private BillingClient billingClient;
...
billingClient = BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() == BillingResponse.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

要启用待处理的购买交易,请在初始化应用时调用 enablePendingPurchases()。请注意,如果您不调用 enablePendingPurchases(),就不能实例化 Google Play 结算库。

查询应用内商品详情:

List<String> skuList = new ArrayList<> ();
skuList.add("premium_upgrade");
skuList.add("gas");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult,
                List<SkuDetails> skuDetailsList) {
            // Process the result.
        }
    });

在用户购买商品之前,检索商品价格是一个重要步骤,因为提供给每个用户的价格因他们所在的国家/地区而异。

启用应用内商品的购买:

有些 Android 手机安装的 Google Play 商店应用可能是旧版的,不支持订阅等商品类型。因此,在应用进入结算流程之前,请调用 isFeatureSupported() 以检查设备是否支持您要销售的商品。

// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .setOldSku(oldSku, oldPurchaseToken)
        .build();
int responseCode = billingClient.launchBillingFlow(flowParams);

实现 onPurchasesUpdated() 方法来处理可能的响应代码

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponse.OK
            && purchases != null) {
        for (Purchase purchase : purchases) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponse.USER_CANCELED) {
        // Handle an error caused by a user cancelling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

如果商品购买成功,系统还会生成购买令牌,它是一个唯一标识符,表示用户及其所购应用内商品的商品 ID。 您的应用可以在用户设备上存储购买令牌,理想情况下,也可以将购买令牌传递到安全的后端服务器,用于验证购买交易及防范欺诈行为。购买令牌对于一次性商品的每笔购买交易和每个奖励产品都是唯一的。不过,由于订阅是一次性购买并按固定的结算周期自动续订,因此订阅的购买令牌在各个结算周期内保持不变。

用户还会收到包含交易收据的电子邮件,其中包含订单 ID 或交易的唯一 ID。用户每次购买一次性商品时,都会收到包含唯一订单 ID 的电子邮件。此外,用户最初购买订阅时以及后续定期自动续订时,也会收到这样的电子邮件。您可以在 Google Play 管理中心内使用订单 ID 来管理退款。

注意:表示订阅续订的订单号带有一个额外的整数,该整数表示该订单具体对应的是第几次续订。例如,初始订阅的订单 ID 可能是 GPA.1234-5678-9012-34567,后续订单 ID 是 GPA.1234-5678-9012-34567..0(第一次续订 orderID)、GPA.1234-5678-9012-34567..1(第二次续订 orderID),依此类推。

确认购买交易

如果您使用的是 Google Play 结算库版本 2.0 或更高版本,则必须在三天内确认所有购买交易。如果没能正确确认,将导致系统对相应购买交易按退款处理。

Google Play 支持从您的应用内部(应用内)或外部(应用外)购买商品。为了确保无论用户在哪里购买您的商品,Google Play 都能提供一致的购买体验,您必须在授予用户权利后尽快确认通过 Google Play 结算库收到的所有处于 SUCCESS 状态的购买交易。如果您在三天内未确认购买交易,则用户会自动收到退款,并且 Google Play 会撤消该购买交易。对于待处理的交易,该三天期限不包含购买交易处于 PENDING 状态的时间,而是从购买交易改为 SUCCESS 状态时算起。

您可以使用以下某种方法确认购买交易:

对于消耗型商品,请使用客户端 API 中的 consumeAsync()。

对于非消耗型商品,请使用客户端 API 中的 acknowledgePurchase()。

还可以使用服务器 API 中新增的 acknowledge() 方法。

对于订阅,您必须确认包含新购买令牌的任何购买交易。这意味着,需要确认所有初始购买、计划变更和重新注册,但无需确认后续续订。要确定购买交易是否需要确认,您可以检查购买交易中的确认字段。

如何确认订阅购买交易:

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        // Grant entitlement to the user.
        ...

        // Acknowledge the purchase if it hasn't already been acknowledged.
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

对于消耗型商品,consumeAsync() 接受包含开发者有效负载字段的 ConsumeParams 对象,如以下示例中所示:

BillingClient client = ...
ConsumeResponseListener listener = ...

ConsumeParams consumeParams =
    ConsumeParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build();

client.consumeAsync(consumeParams, listener);

对于非消耗型商品,acknowledgePurchase() 接受包含开发者有效负载字段的 AcknowledgePurchaseParams 对象,如以下示例中所示:

BillingClient client = ...
AcknowledgePurchaseResponseListener listener = ...

AcknowledgePurchaseParams acknowledgePurchaseParams =
    AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build();

client.acknowledgePurchase(acknowledgePurchaseParams, listener);

验证购买交易

强烈建议您使用可靠的安全后端服务器验证购买详情。如果不能使用服务器,您可以在应用中执行安全性相对较低的验证。

在服务器上验证购买交易

通过在服务器上实现购买验证逻辑,您可以保护您的应用,让企图对您的 APK 文件进行逆向工程并停用其验证逻辑的攻击者无计可施。要在安全后端服务器上验证购买详情,请完成以下步骤:

从您的应用中,将购买令牌和用户帐号凭据发送到安全后端服务器。验证成功后,安全后端服务器应将购买交易与用户关联。

从应用获取令牌后:

使用 Google Play Developer API 的订阅和购买交易部分执行 GET 请求,从 Google Play 检索购买详情(Purchases.products - 针对一次性商品购买交易或奖励产品购买交易,或者 Purchases.subscriptions - 针对订阅)。GET 请求包含应用软件包名称、商品 ID 和令牌(即购买令牌)。

Google Play 将返回购买详情。

安全后端服务器将验证订单 ID 是否为不代表先前购买交易的唯一值。

安全后端服务器使用在第 1 步中收到的用户帐号凭据,将购买令牌与发起购买时所在的应用实例的用户关联。

(可选)如果您正在验证订阅,并且订阅正在升级、降级或用户在订阅失效前已重新订阅,请查看 linkedPurchaseToken 字段。Purchases.subscriptions 资源中的 linkedPurchaseToken 字段包含上一笔(即“原始”)购买交易的令牌。如需详细了解 linkedPurchaseToken,请参阅 Purchases.subscriptions。

应用内商品可供用户使用。

测试数据

iap aidl返回的是Purchase数据

{
    "orderId":"GPA.3382-5419-8887-56592",
    "packageName":"Your packageName",
    "productId":"Your sku",
    "purchaseTime":1578970608459,
    "purchaseState":0,
    "developerPayload":"8616637735029",
    "purchaseToken":"jailjldjodcfobjdnecogkne.AO-J1OzDUL9387-iFzY1rCFZh6vmd60zvUNeK4AsdWTX2H2gHeEzS_pHTPk5FWXfGUjAZfEg4lg4Qx7_6hUtQNuyXw-TDJ9l9qhDvNYS4oEiBh9jCtmF17-OEVqlg5k_Zhv1yjbAS4NlECFmzYVI4w4efPqXYa4Vtw"
}

iap billing clinet2.0.3/3.0.0版本返回的Purchase数据

{
    "orderId":"GPA.3330-3961-6880-80045",
    "packageName":"Your packageName",
    "productId":"Your sku",
    "purchaseTime":1578968578580,
    "purchaseState":0,
    "purchaseToken":"jbgahpkackbjdkljjdoicjmd.AO-J1Oyii85vUPXIQOC0i8O_OA1Te9Gu0w24ICK5qWatI5j40v9xGExSbJttR3r-pLOzRAZG7Vt1zI4KwyaKLFL_RJiHPHbU4BbQjE2rzueRJ7d55vbpA0-lsqrPKj_VQ3fb9xwZKVXZ_aXfspRxC-m8kQvI4hYGjA",
    "acknowledged":false
}

订阅billing clinet1.1版本返回的Purchase数据

{
    "orderId":"GPA.3348-7871-6771-70225",
    "packageName":"Your packageName",
    "productId":"Your sku",
    "purchaseTime":1578476273560,
    "purchaseState":0,
    "purchaseToken":"kolmffbjfnhifngemgcligoa.AO-J1OzAGwyZYPiJA00HGf55Nus4vtkTFnBEGWRAoWUhIpNbyQJBmPnTrCMGw-esCFIPsUcLuvkcp2cd1ceBwhj8JCqFziFuFMqKRhAQ37Vn4a9AVwTngDBjeqzBV9wvLyg7OloTRC3tBnUOICNTmsXUMpFGCiqY0Q",
    "autoRenewing":false
}

订阅billing clinet2.0.3/3.0.0版本返回的Purchase数据

{
    "orderId":"GPA.3372-9098-0097-72086",
    "packageName":"Your packageName",
    "productId":"Your sku",
    "purchaseTime":1578728874156,
    "purchaseState":0,
    "purchaseToken":"igoidlngdakbbpppghepbkmg.AO-J1OxfjkJQUQ45GvupOp6TmZSiFF1WCCXRXjPklKkBbhEX-O8P4E-Y0iMxy1qW3FJfkomTH5HDmk1KdbiBo-5Pj_ZFjs1j_QbEuuRn0SWJkarQmuwUBT-syt40QZgtJiOiHBINHFBod3JbYl0FHpeZz9-5Ns0rEQ",
    "autoRenewing":false,
    "acknowledged":true
}

快速集成

需要BillingManager.java,请找我

集成之后大概是这个样子

public class SubscribeActivity extends AppCompatActivity implements BillingManager.BillingUpdatesListener {
    private BillingManager mBillingManager;
    private String mSku = "";
    private Purchase mPurchase;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBillingManager = new BillingManager(this, this);
        try {
            if (mBillingManager != null) {
                ArrayList<String> skuList = new ArrayList<>();
                skuList.add("your sku1");
                skuList.add("your sku2");
                if (!TextUtils.isEmpty(mSku) && mPurchase != null) {
                    mBillingManager.initiatePurchaseFlow("your sku", skuList, mSku, mPurchase.getPurchaseToken(), BillingClient.SkuType.SUBS);
                } else {
                    mBillingManager.initiatePurchaseFlow("your sku", skuList, BillingClient.SkuType.SUBS);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBillingManager != null) {
            mBillingManager.destroy();
        }
    }

    @Override
    public void onBillingClientSetupFinished() {

    }

    @Override
    public void onConsumeFinished(String sku, int result) {

    }

    @Override
    public void onPurchasesUpdated(List<Purchase> purchases) {
        if (isActivityDestroyed()) {
            return;
        }
        if (purchases != null && !purchases.isEmpty()) {
            for (Purchase purchase : purchases) {
                String sku = purchase.getSku();
                String token = purchase.getPurchaseToken();
                if (!TextUtils.isEmpty(sku) && !TextUtils.isEmpty(token) &&
                        (TextUtils.equals(sku,"your sku1")
                                || TextUtils.equals(sku, "your sku2"))) {
                    mBillingManager.acknowledgePurchase(purchase);
                    mSku = sku;
                    mPurchase = purchase;
                }
            }
        }
    }

    @Override
    public void onBillingError(int result) {

    }

    public boolean isActivityDestroyed() {
        if (isFinishing() ||
                (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed())) {
            return true;
        } else {
            return false;
        }
    }
}

集成链接:

https://developer.android.com/google/play/billing/integrate#java

测试链接

https://developer.android.google.cn/google/play/billing/billing_testing#billing-testing-test

⚠️ **GitHub.com Fallback** ⚠️