英文:
How do you check if a user has bought an in-app purchase whenever he open the app?
问题
请注意:我正在谈论的是从应用中移除广告的一次性购买。
谷歌建议在onResume()
和onCreate()
方法中调用BillingClient.queryPurchases()
,但我不太确定如何实现。
首先,以下是实际应用内购买的代码,我从YouTube教程中获取的,甚至不确定它是否完整:
private void setupBillingClient(Context c) { //连接到Google Play
billingClient = BillingClient.newBuilder(c)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//成功设置BillingClient
loadAllSkus();
}
}
@Override
public void onBillingServiceDisconnected() {
//TODO:通过调用startConnection()实现重试逻辑,以处理与Google Play的连接中断
}
});
}
private void loadAllSkus() {
// ...
// 这里是加载商品信息的代码
// ...
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
// ...
// 这里是处理购买更新的代码
// ...
}
private void handlePurchase(Purchase purchase) {
// ...
// 这里是处理购买的代码
// ...
}
所以,显然要调用BillingClient.queryPurchases()
,我需要在每个活动中重新编写上述所有代码吗?
英文:
Note: I'm talking about a one-time purchase that removes ads from the app.
Google says you should call BillingClient.queryPurchases()
in your onResume()
and onCreate()
but I'm not sure how to do that.
First off here's the code for the actual in-app purchase,
I just got it from a youtube tutorial and I don't even know if it's complete:
private void setupBillingClient(Context c) { //connect to google play
billingClient = BillingClient.newBuilder(c)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//The BillingClient is setup successfully
loadAllSkus();
}
}
@Override
public void onBillingServiceDisconnected() {
//TODO: implement retry logic to handle lost connections to Google Play by calling startConnection() again
}
});
}
private void loadAllSkus() {
if (billingClient.isReady()) { //first check if BillingClient is ready
final SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull final BillingResult billingResult, @Nullable List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
assert skuDetailsList != null;
for (Object skuDetailsObject : skuDetailsList) {
final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
if (skuDetails.getSku().equals(sku)) { //if it found the sku in play store you can start billing flow
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); //use play store cache, no network
List<Purchase> purchases = result.getPurchasesList();
boolean isOwned = false;
if (purchases != null) //first check if he has already purchased
for (Purchase purchase : purchases) {
String thisSKU = purchase.getSku();
if (thisSKU.equals(sku)) {
isOwned = true;
Toast.makeText(getContext(), "You are a premium user", Toast.LENGTH_LONG).show();//TODO: Delete this toast
//TODO: Remove purchase options and ads here
break;
}
}
if (!isOwned) {
buyButton.setEnabled(true);
buyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build();
if (getActivity() != null)
billingClient.launchBillingFlow(getActivity(), billingFlowParams);
dismiss();
}
});
}
}
/* else if (skuDetails.getSku().equals("something else")) { //add other products
}*/
}
}
}
});
} else Toast.makeText(getContext(), R.string.billing_not_ready, Toast.LENGTH_LONG).show();
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<com.android.billingclient.api.Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase); //acknowledge the purchase
}
} else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
//already owned
} else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getSku().equals(sku) && 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(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Toast.makeText(getContext(), "Purchase Acknowledged", Toast.LENGTH_LONG).show();//TODO: Delete this maybe?
}
}
});
}
Toast.makeText(getContext(), R.string.purchase_done, Toast.LENGTH_LONG).show();
}
}
So apparently to call BillingClient.queryPurchases()
I have to rewrite all of the above code in each and every activity?
答案1
得分: 2
方法BillingClient.queryPurchases(),就像你知道的,会返回一个包含当前所有已购买商品的列表。
反复复制相同的代码始终是一种错误的方法/设计。
你需要做的是封装你的代码,使其尽可能独立于任何活动或任何其他组件,以便可以重用。
更好的方法是从Google的官方计费示例中获取灵感。你会发现所有计费逻辑都被封装起来,以便可以轻松地重用。
Java: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiJava
Kotlin: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiAppKotlin
一些澄清,如果你发现示例涉及订阅,而你需要非消耗品,或反之亦然。计费库对两者都是相同的,不同之处仅在于要查询的数据,例如对于订阅,请使用BillingClient.SkuType.SUBS,对于非消耗品,请使用BillingClient.SkuType.INAPP。但其余部分,如何连接计费,如何确认,如何启动购买流程等,对于两者都是完全相同的。没有单独的API或不同的实现方式,你只需要使用相同的API和相同的实现,只是更改正在查询的内容。
英文:
The method BillingClient.queryPurchases() as you know returns a list of all the currently owned purchases items.
Replicating the same code again and again is always an incorrect approach / design.
What you need to do is to encapsulate your code and make it as independent as possible from any Activity or any other component, so it can be reused.
For a better approach check the official billing samples from Google. You will see that all the billing logic is encapsulated so it can be easily reused.
Java: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiJava
Kotlin: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiAppKotlin
Some clarifications, if you find that the samples are about subscriptions and you need non-consumables, or vice versa. The billing library is the same for both, the difference is only the data to be queried, for example for subscriptions use BillingClient.SkuType.SUBS and for non-consumables BillingClient.SkuType.INAPP. But the rest, how to connect with billing, how to acknowledge, initiate the purchase flow, etc., is the exactly same for both. There are no separate APIs or different way to implement it, you use the same API and the same implementation just changing what is being queried.
答案2
得分: 2
我想我找到了答案。非常感谢大家。
public void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS, (billingResult1, list) -> {
Log.d("billingprocess", "purchasesResult.getPurchasesList():" + purchasesResult.getPurchasesList());
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
!Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
//在这里您可以让用户使用应用,因为他有有效的订阅
Toast.makeText(WebappActivity.this, "已订阅", Toast.LENGTH_SHORT).show();
} else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
loadAllSKUs(); // 转向支付流程
}
});
}
// 其他回调方法...
});
}
英文:
I think I found the answer. So thankful to everyone.
public void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Purchase.PurchasesResult purchasesResult=billingClient.queryPurchases(BillingClient.SkuType.SUBS);
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS,(billingResult1, list) -> {
Log.d("billingprocess","purchasesResult.getPurchasesList():"+purchasesResult.getPurchasesList());
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
!Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
//here you can pass the user to use the app because he has an active subscription
Toast.makeText(WebappActivity.this,"subbungSCCCCCriber",Toast.LENGTH_SHORT).show();
}
else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
loadAllSKUs(); // toward the billingflow
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论