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

1.应用内商品类型

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

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

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

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

2.订单 ID

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

订单 ID 是一个字符串,表示 Google Play 上的金融交易。此字符串包含在通过电子邮件发送给买家的收据中,第三方开发者使用此订单 ID 管理 Google Play 管理中心的“订单管理”部分中的退款。销售和付款报告中也会使用订单 ID。

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

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

3.唯一的订阅产品配置选项

结算周期:用户订阅处于有效状态时向用户扣款的频率。您可以在 Google Play 管理中心选择的结算周期有每周、1 个月、3 个月、6 个月和按年结算。系统会按订阅项目设定的计费间隔和价格无限期持续收费。在每次订阅续订时,Google Play 都会自动从用户帐号中扣除相关费用,然后通过电子邮件通知用户扣款情况。

免费试订期:用户可以在不付费的情况下访问订阅内容的时长。免费试订期可以吸引用户免费试订阅您的内容,然后用户再决定是否购买。 您可以将免费试订期设置为 3 天或更长时间。

初次体验价:在特定数量的初次“体验”结算周期内的订阅价格。初次体验价可吸引用户试订阅您的内容,同时为您带来一些收入。初次体验价必须低于正常的订阅价格。

4.集成方式

依赖:

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

权限

<uses-permission android:name="com.android.vending.BILLING" />

去混淆:

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

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

一致的报价

注意:此功能目前正在测试中,不能保证其可以广泛使用。

在提供折扣 SKU 时,Google Play 还会返回 SKU 的原价,以便您向用户显示他们正在享受折扣。我们建议您既使用 getPrice() 向用户显示折扣价,又使用 getOriginalPrice() 显示商品的原价。

SkuDetails 包含两种检索 SKU 原价的方法:

getOriginalPriceAmountMicros() - 返回折扣前未设置格式的 SKU 原价。

getOriginalPrice() - 返回采用其他货币格式的原价。

启用应用内商品的购买:

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

// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .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。

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

用户取消订阅怎么办?

1.后台每天有脚本在跑,刷新票据信息,更新用户信息

2.客户端本地记录上次订阅的信息,进入app的时候请求后台接,更新用户信息

之前iap用的aidl方式,有冲突吗

aidl的方式和billing client同时使用,会编译不过.IInAppBillingService重复,删除项目中的IInAppBillingService之后,iap功能可以正常使用,用的是billing aar里面的IInAppBillingService.考虑之后的维护,可将iap的实现也改用billing client的方式.

测试数据

订阅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版本返回的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
}

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版本返回的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
}

后台数据

首次订阅

{
 "kind": "androidpublisher#subscriptionPurchase",
 "startTimeMillis": "1578887739401",
 "expiryTimeMillis": "1578888572431",
 "autoRenewing": true,
 "priceCurrencyCode": "HKD",
 "priceAmountMicros": "31040000",
 "countryCode": "IL",
 "developerPayload": "",
 "paymentState": 1,
 "orderId": "GPA.3371-0189-6001-86275",
 "purchaseType": 0,
 "acknowledgementState": 1
}

自动续订

{
 "kind": "androidpublisher#subscriptionPurchase",
 "startTimeMillis": "1578887739401",
 "expiryTimeMillis": "1578889172431",
 "autoRenewing": true,
 "priceCurrencyCode": "HKD",
 "priceAmountMicros": "31040000",
 "countryCode": "IL",
 "developerPayload": "",
 "paymentState": 1,
 "orderId": "GPA.3371-0189-6001-86275..0",
 "purchaseType": 0,
 "acknowledgementState": 1
}

字段含义 https://developers.google.cn/android-publisher/api-ref/purchases/subscriptions

集成链接:

https://developer.android.google.cn/google/play/billing/billing_library_overview#Handling-multiple-devices

测试链接

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

前端订阅流程

前端订阅流程

后台订阅流程

后台订阅流程

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