Google Play计费API:如何了解用户已订阅?

港南风

我想了解用户是否从MainActivity主动订阅了Basic / Premium内容。有一个BillingClientLifecycle类可以启动订阅过程。据我了解,queryPurchses应显示用户是否具有有效订阅。但是很明显,它显示(通过Toasts,我放置在此处以显示订阅状态),即使实际上未订阅用户,该用户也已订阅。

public void queryPurchases() {
        if (!billingClient.isReady()) {
            Log.e(TAG, "queryPurchases: BillingClient is not ready");
        }
        Log.d(TAG, "queryPurchases: SUBS");
        Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
        if (result == null) {
            Log.i(TAG, "queryPurchases: null purchase result");
            processPurchases(null);
            ///
            Toast.makeText(applicationContext,"queryPurchases: null purchase result", Toast.LENGTH_SHORT).show();
        } else {
            if (result.getPurchasesList() == null) {
                Log.i(TAG, "queryPurchases: null purchase list");
                processPurchases(null);
                ///
                Toast.makeText(applicationContext,"queryPurchases: null purchase list", Toast.LENGTH_SHORT).show();
            } else {
                processPurchases(result.getPurchasesList());
                ///
                Toast.makeText(applicationContext,"user has subscription!", Toast.LENGTH_SHORT).show();
            }
        }
    }

我在这里做错了什么?我想根据订阅状态更新主要活动。BillingClientLifecycle是如下:

public class BillingClientLifecycle implements LifecycleObserver, PurchasesUpdatedListener,
    BillingClientStateListener, SkuDetailsResponseListener {

private static final String TAG = "BillingLifecycle";

Context applicationContext = MainActivity.getContextOfApplication();

/**
 * The purchase event is observable. Only one observer will be notified.
 */
public SingleLiveEvent<List<Purchase>> purchaseUpdateEvent = new SingleLiveEvent<>();

/**
 * Purchases are observable. This list will be updated when the Billing Library
 * detects new or existing purchases. All observers will be notified.
 */
public MutableLiveData<List<Purchase>> purchases = new MutableLiveData<>();

/**
 * SkuDetails for all known SKUs.
 */
public MutableLiveData<Map<String, SkuDetails>> skusWithSkuDetails = new MutableLiveData<>();

private static volatile BillingClientLifecycle INSTANCE;

private Application app;
private BillingClient billingClient;

public BillingClientLifecycle(Application app) {
    this.app = app;
}

public static BillingClientLifecycle getInstance(Application app) {
    if (INSTANCE == null) {
        synchronized (BillingClientLifecycle.class) {
            if (INSTANCE == null) {
                INSTANCE = new BillingClientLifecycle(app);
            }
        }
    }
    return INSTANCE;
}

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void create() {
    Log.d(TAG, "ON_CREATE");
    // Create a new BillingClient in onCreate().
    // Since the BillingClient can only be used once, we need to create a new instance
    // after ending the previous connection to the Google Play Store in onDestroy().
    billingClient = BillingClient.newBuilder(app)
            .setListener(this)
            .enablePendingPurchases() // Not used for subscriptions.
            .build();
    if (!billingClient.isReady()) {
        Log.d(TAG, "BillingClient: Start connection...");
        billingClient.startConnection(this);
    }
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void destroy() {
    Log.d(TAG, "ON_DESTROY");
    if (billingClient.isReady()) {
        Log.d(TAG, "BillingClient can only be used once -- closing connection");
        // BillingClient can only be used once.
        // After calling endConnection(), we must create a new BillingClient.
        billingClient.endConnection();
    }
}

@Override
public void onBillingSetupFinished(BillingResult billingResult) {
    int responseCode = billingResult.getResponseCode();
    String debugMessage = billingResult.getDebugMessage();
    Log.d(TAG, "onBillingSetupFinished: " + responseCode + " " + debugMessage);
    if (responseCode == BillingClient.BillingResponseCode.OK) {
        // The billing client is ready. You can query purchases here.
        querySkuDetails();
        queryPurchases();
    }
}

@Override
public void onBillingServiceDisconnected() {
    Log.d(TAG, "onBillingServiceDisconnected");
    // TODO: Try connecting again with exponential backoff.
}

/**
 * Receives the result from {@link #querySkuDetails()}}.
 * <p>
 * Store the SkuDetails and post them in the {@link #skusWithSkuDetails}. This allows other
 * parts of the app to use the {@link SkuDetails} to show SKU information and make purchases.
 */
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
    if (billingResult == null) {
        Log.wtf(TAG, "onSkuDetailsResponse: null BillingResult");
        return;
    }

    int responseCode = billingResult.getResponseCode();
    String debugMessage = billingResult.getDebugMessage();
    switch (responseCode) {
        case BillingClient.BillingResponseCode.OK:
            Log.i(TAG, "onSkuDetailsResponse: " + responseCode + " " + debugMessage);
            if (skuDetailsList == null) {
                Log.w(TAG, "onSkuDetailsResponse: null SkuDetails list");
                skusWithSkuDetails.postValue(Collections.<String, SkuDetails>emptyMap());
            } else {
                Map<String, SkuDetails> newSkusDetailList = new HashMap<String, SkuDetails>();
                for (SkuDetails skuDetails : skuDetailsList) {
                    newSkusDetailList.put(skuDetails.getSku(), skuDetails);
                }
                skusWithSkuDetails.postValue(newSkusDetailList);
                Log.i(TAG, "onSkuDetailsResponse: count " + newSkusDetailList.size());
            }
            break;
        case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED:
        case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:
        case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:
        case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE:
        case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
        case BillingClient.BillingResponseCode.ERROR:
            Log.e(TAG, "onSkuDetailsResponse: " + responseCode + " " + debugMessage);
            break;
        case BillingClient.BillingResponseCode.USER_CANCELED:
            Log.i(TAG, "onSkuDetailsResponse: " + responseCode + " " + debugMessage);
            break;
        // These response codes are not expected.
        case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED:
        case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
        case BillingClient.BillingResponseCode.ITEM_NOT_OWNED:
        default:
            Log.wtf(TAG, "onSkuDetailsResponse: " + responseCode + " " + debugMessage);
    }
}

/**
 * Query Google Play Billing for existing purchases.
 * <p>
 * New purchases will be provided to the PurchasesUpdatedListener.
 * You still need to check the Google Play Billing API to know when purchase tokens are removed.
 */
public void queryPurchases() {
    if (!billingClient.isReady()) {
        Log.e(TAG, "queryPurchases: BillingClient is not ready");
    }
    Log.d(TAG, "queryPurchases: SUBS");
    Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
    if (result == null) {
        Log.i(TAG, "queryPurchases: null purchase result");
        processPurchases(null);
        ///
        Toast.makeText(applicationContext,"queryPurchases: null purchase result", Toast.LENGTH_SHORT).show();
    } else {
        if (result.getPurchasesList() == null) {
            Log.i(TAG, "queryPurchases: null purchase list");
            processPurchases(null);
            ///
            Toast.makeText(applicationContext,"queryPurchases: null purchase list", Toast.LENGTH_SHORT).show();
        } else {
            processPurchases(result.getPurchasesList());
            ///
            Toast.makeText(applicationContext,"user has subscription!", Toast.LENGTH_SHORT).show();
        }
    }
}

/**
 * Called by the Billing Library when new purchases are detected.
 */
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult == null) {
        Log.wtf(TAG, "onPurchasesUpdated: null BillingResult");
        return;
    }
    int responseCode = billingResult.getResponseCode();
    String debugMessage = billingResult.getDebugMessage();
    Log.d(TAG, "onPurchasesUpdated: $responseCode $debugMessage");
    switch (responseCode) {
        case BillingClient.BillingResponseCode.OK:
            if (purchases == null) {
                Log.d(TAG, "onPurchasesUpdated: null purchase list");
                processPurchases(null);
            } else {
                processPurchases(purchases);
            }
            break;
        case BillingClient.BillingResponseCode.USER_CANCELED:
            Log.i(TAG, "onPurchasesUpdated: User canceled the purchase");
            break;
        case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
            Log.i(TAG, "onPurchasesUpdated: The user already owns this item");
            break;
        case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
            Log.e(TAG, "onPurchasesUpdated: Developer error means that Google Play " +
                    "does not recognize the configuration. If you are just getting started, " +
                    "make sure you have configured the application correctly in the " +
                    "Google Play Console. The SKU product ID must match and the APK you " +
                    "are using must be signed with release keys."
            );
            break;
    }
}

/**
 * Send purchase SingleLiveEvent and update purchases LiveData.
 * <p>
 * The SingleLiveEvent will trigger network call to verify the subscriptions on the sever.
 * The LiveData will allow Google Play settings UI to update based on the latest purchase data.
 */
private void processPurchases(List<Purchase> purchasesList) {
    if (purchasesList != null) {
        Log.d(TAG, "processPurchases: " + purchasesList.size() + " purchase(s)");
    } else {
        Log.d(TAG, "processPurchases: with no purchases");
    }
    if (isUnchangedPurchaseList(purchasesList)) {
        Log.d(TAG, "processPurchases: Purchase list has not changed");
        return;
    }
    purchaseUpdateEvent.postValue(purchasesList);
    purchases.postValue(purchasesList);
    if (purchasesList != null) {
        logAcknowledgementStatus(purchasesList);
    }
}

/**
 * Log the number of purchases that are acknowledge and not acknowledged.
 * <p>
 * https://developer.android.com/google/play/billing/billing_library_releases_notes#2_0_acknowledge
 * <p>
 * When the purchase is first received, it will not be acknowledge.
 * This application sends the purchase token to the server for registration. After the
 * purchase token is registered to an account, the Android app acknowledges the purchase token.
 * The next time the purchase list is updated, it will contain acknowledged purchases.
 */
private void logAcknowledgementStatus(List<Purchase> purchasesList) {
    int ack_yes = 0;
    int ack_no = 0;
    for (Purchase purchase : purchasesList) {
        if (purchase.isAcknowledged()) {
            ack_yes++;
        } else {
            ack_no++;
        }
    }
    Log.d(TAG, "logAcknowledgementStatus: acknowledged=" + ack_yes +
            " unacknowledged=" + ack_no);
}

/**
 * Check whether the purchases have changed before posting changes.
 */
private boolean isUnchangedPurchaseList(List<Purchase> purchasesList) {
    // TODO: Optimize to avoid updates with identical data.
    return false;
}

/**
 * In order to make purchases, you need the {@link SkuDetails} for the item or subscription.
 * This is an asynchronous call that will receive a result in {@link #onSkuDetailsResponse}.
 */
public void querySkuDetails() {
    Log.d(TAG, "querySkuDetails");

    List<String> skus = new ArrayList<>();
    skus.add(Constants.BASIC_SKU);
    skus.add(Constants.PREMIUM_SKU);

    SkuDetailsParams params = SkuDetailsParams.newBuilder()
            .setType(BillingClient.SkuType.SUBS)
            .setSkusList(skus)
            .build();

    Log.i(TAG, "querySkuDetailsAsync");
    billingClient.querySkuDetailsAsync(params, this);
}

/**
 * Launching the billing flow.
 * <p>
 * Launching the UI to make a purchase requires a reference to the Activity.
 */
public int launchBillingFlow(Activity activity, BillingFlowParams params) {
    String sku = params.getSku();
    String oldSku = params.getOldSku();
    Log.i(TAG, "launchBillingFlow: sku: " + sku + ", oldSku: " + oldSku);
    if (!billingClient.isReady()) {
        Log.e(TAG, "launchBillingFlow: BillingClient is not ready");
    }
    BillingResult billingResult = billingClient.launchBillingFlow(activity, params);
    int responseCode = billingResult.getResponseCode();
    String debugMessage = billingResult.getDebugMessage();
    Log.d(TAG, "launchBillingFlow: BillingResponse " + responseCode + " " + debugMessage);
    return responseCode;
}

/**
 * Acknowledge a purchase.
 * <p>
 * https://developer.android.com/google/play/billing/billing_library_releases_notes#2_0_acknowledge
 * <p>
 * Apps should acknowledge the purchase after confirming that the purchase token
 * has been associated with a user. This app only acknowledges purchases after
 * successfully receiving the subscription data back from the server.
 * <p>
 * Developers can choose to acknowledge purchases from a server using the
 * Google Play Developer API. The server has direct access to the user database,
 * so using the Google Play Developer API for acknowledgement might be more reliable.
 * TODO(134506821): Acknowledge purchases on the server.
 * <p>
 * If the purchase token is not acknowledged within 3 days,
 * then Google Play will automatically refund and revoke the purchase.
 * This behavior helps ensure that users are not charged for subscriptions unless the
 * user has successfully received access to the content.
 * This eliminates a category of issues where users complain to developers
 * that they paid for something that the app is not giving to them.
 */
public void acknowledgePurchase(String purchaseToken) {
    Log.d(TAG, "acknowledgePurchase");
    AcknowledgePurchaseParams params = AcknowledgePurchaseParams.newBuilder()
            .setPurchaseToken(purchaseToken)
            .build();
    billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() {
        @Override
        public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
            int responseCode = billingResult.getResponseCode();
            String debugMessage = billingResult.getDebugMessage();
            Log.d(TAG, "acknowledgePurchase: " + responseCode + " " + debugMessage);
        }
    });
}

}

我正在考虑在BillingClientLifecycle内部使用共享的首选项(而不是Toasts),并从MainActivity类或任何其他类中检索订阅状态,这些类需要在启动应用程序时通知订阅状态。尽管我不想使用共享首选项,而是直接调用订阅信息。

亚历克斯

计费过程的实现看起来不错,但是缺少检查以确定当前是否真正激活订阅。

可以通过使用LiveData对象来进行观察。这样我们就不需要SharedPreferences左右的状态。我将在下面的观察部分进行介绍。详细答案:


采购清单

首先,让我们解释一下购买清单在计费API中的确切含义:

  1. 这是用户针对应用内商品或订阅进行的所有购买的列表
  2. 这些购买必须由应用程序或后端确认(通过后端推荐,但两者均可)
  3. 这采购清单包括支付其仍悬而未决,也被支付不承认呢。

看到正在执行确认步骤,我假设付款确认已成功完成。

第3点是为什么它不检测实际的订阅状态,因为不检查购买状态。


检查订阅状态

queryPurchases()呼叫返回用户为所请求的产品付款。我们收到的数组可以有多个项目(每个应用内项目或订阅最多可以有一个项目)。我们需要检查所有。

每次购买都有更多数据。这是我们检查状态所需的方法:

  • getSku() //验证产品是否是我们想要的
  • getPurchaseState() //获取实际的购买状态
  • isAcknowledged() //要知道付款是否已确认,如果未确认,则表示付款尚未成功

为了检查当前是否已针对PREMIUM sku付款有效:

boolean isPremiumActive = Constants.PREMIUM_SKU.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged()

如果我们要检查是否有任何订阅处于活动状态,请检查其他sku是否相同(遍历sku和购买)

*请注意,如果现在isPremiumActive为true,则表示用户当前具有有效的订阅。这意味着,如果用户取消了他的订阅,但直到结束期仍付款,则该值仍为true。仅仅是因为用户仍然有权在计费期届满之前访问内容。

*如果订阅期确实结束(取消或过期),则开票客户将不再退还购买的款项。


观察当前状态

现在我们知道如何验证购买的商品,我们可以使用LiveData轻松读取此状态,以便我们可以随时访问它。在示例中,我们已经有te LiveData purchases,它包含所有购买的商品,并在queryPurchases()调用后填充

  1. 创建LiveData

让我们创建一个使用此purchasesLiveData的新LiveData ,但根据我们是否激活PREMIUM_SKU将返回true或false:

public LiveData<Boolean> isSubscriptionActive = Transformations.map(purchases, purchases -> {
    boolean hasSubscription = false;
    for (Purchase purchase : purchases) {
        // TODO: Also check for the other SKU's if needed
        if (Constants.PREMIUM_SKU.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged()) {
            // This purchase is purchased and acknowledged, it is currently active!
            hasSubscription = true;
        }
    }
    return hasSubscription;
});

在BillingClientLifecycle中添加此块,如果购买列表更改,它将发出值true或false

  1. 观察它

与往常一样,在要接收更新的活动中观察此LiveData:

billingClientLifecycle.isSubscriptionActive.observe(this, hasSubscription -> {
    if (hasSubscription) {
        // User is subscribed!
        Toast.makeText(this, "User has subscription!", Toast.LENGTH_SHORT).show();
    } else {
        // User is a regular user!
    }
});

放在MainActivity您的情况下。它将观察订阅的更改,并在更改时触发两个功能之一。

*如果不需要实时数据,而是直接获取值的方法,则也可以只使用内的布尔值字段,billingClientLifecycleprocessPurchases()使用与上述相同方法方法中正确更新此


高级

对于更高级的用法,我们还可以使用购买对象的其他状态:

如果购买状态为Purchase.PurchaseState.PENDING,则表示Google或用户仍然需要执行一些步骤来验证付款。基本上,这意味着计费API无法确定付款是否已完成(如果发生)。我们也可以例如通过显示一条消息来告知用户有关此状态的信息,以完成其付款等。

如果已付款但尚未确认购买,则表示中的确认步骤BillingClientLifecycle未成功。此外,在这种情况下,Google Play会自动将付款退还给用户。例如:对于月度订阅,确认期为3天,因此3天后,用户将钱退还并删除了购买。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何在开源应用中实现Google Play计费订阅?

来自分类Dev

如何确定用户是否取消了Google Play订阅?

来自分类Dev

Google Play计费订阅:强制性“保留”的新政策:需要在应用程序用户界面中更改哪些内容?

来自分类Dev

如何获取Google Play订阅中的用户数据列表

来自分类Dev

Android / Google Play:我真的需要我的OWN服务器来管理inapp计费订阅吗?

来自分类Dev

使用Directory API创建Google日历后,如何以编程方式向Google Apps用户订阅公司日历?

来自分类Dev

使用Directory API创建Google日历后,如何以编程方式向Google Apps用户订阅公司日历?

来自分类Dev

如何测试订阅的Google Play实时通知?

来自分类Dev

如何将总价设置为 google play 订阅项目

来自分类Dev

如何在 Android 中推迟订阅(Google Play 结算)?

来自分类Dev

Google Play应用内审核API。如何检查用户是否对该应用进行了评分?

来自分类Dev

Google Contacts API了解是否与google +联系

来自分类Dev

未配置应用程序以通过Google Play计费

来自分类Dev

如何降级Google Play中的api?

来自分类Dev

如何降级Google Play中的api?

来自分类Dev

如何检查用户是否已登录Swift中的Google帐户?

来自分类Dev

Google Play用户安装问题

来自分类Dev

如何使用Google+ API注销用户?

来自分类Dev

API的Google登录Google Play服务8.3

来自分类Dev

实施Google Play游戏服务-如何让用户登录

来自分类Dev

如何知道“哪个网站用户来自Google Play?”

来自分类Dev

如何允许用户从Google Play游戏中删除数据?

来自分类Dev

为什么在 Google Play API 中取消订阅后到期会发生变化?

来自分类Dev

Google Maps Javascript API客户端计费

来自分类Dev

Google Play游戏服务检查用户是否已登录Unity3d

来自分类Dev

Google Maps API已禁用?

来自分类Dev

从Google Play取消发布应用后,应用内订阅会如何处理?

来自分类Dev

如何为Google云存储免费配额启用计费

来自分类Dev

Google Play服务库是否已更新?

Related 相关文章

  1. 1

    如何在开源应用中实现Google Play计费订阅?

  2. 2

    如何确定用户是否取消了Google Play订阅?

  3. 3

    Google Play计费订阅:强制性“保留”的新政策:需要在应用程序用户界面中更改哪些内容?

  4. 4

    如何获取Google Play订阅中的用户数据列表

  5. 5

    Android / Google Play:我真的需要我的OWN服务器来管理inapp计费订阅吗?

  6. 6

    使用Directory API创建Google日历后,如何以编程方式向Google Apps用户订阅公司日历?

  7. 7

    使用Directory API创建Google日历后,如何以编程方式向Google Apps用户订阅公司日历?

  8. 8

    如何测试订阅的Google Play实时通知?

  9. 9

    如何将总价设置为 google play 订阅项目

  10. 10

    如何在 Android 中推迟订阅(Google Play 结算)?

  11. 11

    Google Play应用内审核API。如何检查用户是否对该应用进行了评分?

  12. 12

    Google Contacts API了解是否与google +联系

  13. 13

    未配置应用程序以通过Google Play计费

  14. 14

    如何降级Google Play中的api?

  15. 15

    如何降级Google Play中的api?

  16. 16

    如何检查用户是否已登录Swift中的Google帐户?

  17. 17

    Google Play用户安装问题

  18. 18

    如何使用Google+ API注销用户?

  19. 19

    API的Google登录Google Play服务8.3

  20. 20

    实施Google Play游戏服务-如何让用户登录

  21. 21

    如何知道“哪个网站用户来自Google Play?”

  22. 22

    如何允许用户从Google Play游戏中删除数据?

  23. 23

    为什么在 Google Play API 中取消订阅后到期会发生变化?

  24. 24

    Google Maps Javascript API客户端计费

  25. 25

    Google Play游戏服务检查用户是否已登录Unity3d

  26. 26

    Google Maps API已禁用?

  27. 27

    从Google Play取消发布应用后,应用内订阅会如何处理?

  28. 28

    如何为Google云存储免费配额启用计费

  29. 29

    Google Play服务库是否已更新?

热门标签

归档