android-inapp-billing-v3
android-inapp-billing-v3 copied to clipboard
Question: How do I verify canceled subscription?
I am using: bp.isSubscribed(ID)
to verify my subscription. But when I cancel a subscription the method bp.isSubscribed(ID
) still returning true. What should I do?
I was also facing the similar. This is what I did to get around it.
Invoke the method bp.loadOwnedPurchasesFromGoogle(); Then check the value of transactionDetails, if the TransactionDetails object return null it means, that the user did not subscribe or cancelled their subscription, otherwise they are still subscribed.
`
private void updateTextViews() {
checkIfUserIsSusbcribed();
}
void checkIfUserIsSusbcribed(){
boolean purchaseResult = bp.loadOwnedPurchasesFromGoogle();
if(purchaseResult){
TransactionDetails subscriptionTransactionDetails = bp.getSubscriptionTransactionDetails(SUBSCRIPTION_ID);
if(subscriptionTransactionDetails!=null) {
//User is still subscribed
showToast("User is still subscribed");
} else {
//Not subscribed
showToast("Not subscribed");
}
}
}
`
Also, point to note is that the TransactionDetails object will only return null after the period of the subscription has expired.
Hi @KRIPT4 I still getting true using your code even when I cancel the subscription. There are something more to do?
I have noticed that bp.loadOwnedPurchasesFromGoogle() will return false, and therefore the rest of the code is not run..
I can't believe its so hard to get an accurate answer on if a users subscription is still valid or not.
@haroldogtf It worked for me. Beware of response times! The subscription is not canceled at the moment, it may take a few minutes, I have seen cases of 5 to 10 minutes until it disappears from Google Play.
Here is my logic, and I get strange results..
So in MainActivity.java, I check the subscription status via the following (I also use the same code block in the AppWidgetProvider.java so that it checks subscription status each time the widget gets refreshed -- every 1hr):
bp = new BillingProcessor(this, "LICENSEKEYHERE", this);
bp.initialize();
boolean purchaseResult = bp.loadOwnedPurchasesFromGoogle();
if(purchaseResult) {
TransactionDetails subscriptionTransactionDetails = bp.getSubscriptionTransactionDetails("widget_subscription");
if (subscriptionTransactionDetails != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("widgetPurchased", true);
editor.apply();
}else{
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("widgetPurchased", false);
editor.apply();
}
}
The code in MainActivity.java to handle the purchase flow is:
boolean purchaseResult = bp.loadOwnedPurchasesFromGoogle();
if(purchaseResult) {
TransactionDetails subscriptionTransactionDetails = bp.getSubscriptionTransactionDetails("widget_subscription");
if (subscriptionTransactionDetails != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("widgetPurchased", true);
editor.apply();
fragment = (Fragment) fragmentClass.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.flContent, fragment, tag).commitAllowingStateLoss();
} else {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("widgetPurchased", false);
editor.apply();
SweetAlertDialog sweetAlertDialog = new SweetAlertDialog(this, SweetAlertDialog.CUSTOM_IMAGE_TYPE)
.setTitleText("Subscribe")
.setContentText("The BIG Ferry Planner widget is a premium feature. Please subscribe to use it.")
.setCustomImage(R.drawable.ic_app)
.setConfirmText("Ok, got it")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
//bp.purchase(MainActivity.this, "android.test.purchased");
bp.subscribe(MainActivity.this, "widget_subscription");
}
});
sweetAlertDialog.setCancelable(false);
sweetAlertDialog.show();
}
}
So, what am I doing wrong? Why do I get strange and unpredictable results?
This is an example to remove the adView. I have it working properly in 20 Apps. Adapt it to your application, it should work.
`
package com.test;
import .;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
////////////////////////////////////////////////////////////////////////////////////////////////
// SAMPLE APP CONSTANTS
private static final String ACTIVITY_NUMBER = "activity_num";
private static final String LOG_TAG = "IN-APP-TEST";
// PRODUCT & SUBSCRIPTION IDS
//private static final String PRODUCT_ID = "buyproduct";
private static final String SUBSCRIPTION_ID = "subspro";
private static final String LICENSE_KEY = "****************"; // PUT YOUR MERCHANT KEY HERE;
// put your Google merchant id here (as stated in public profile of your Payments Merchant Center)
// if filled library will provide protection against Freedom alike Play Market simulators
private static final String MERCHANT_ID=null;
Boolean subsProOK = false;
private BillingProcessor bp;
private boolean readyToPurchase = false;
AdView mAdView;
////////////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState) {
////////////////////////////////////////////////////////////////////////////////////////////
if(!BillingProcessor.isIabServiceAvailable(this)) {
Toast.makeText(this, "In-app billing service is not available, update Android Market / Google Play Store to version> = v3.9.16", Toast.LENGTH_LONG).show();
}
bp = new BillingProcessor(this, LICENSE_KEY, MERCHANT_ID, new BillingProcessor.IBillingHandler() {
@Override
public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) {
//showToast("onProductPurchased: " + productId);
updateTextViews();
}
@Override
public void onBillingError(int errorCode, @Nullable Throwable error) {
//showToast("onBillingError: " + Integer.toString(errorCode));
}
@Override
public void onBillingInitialized() {
//showToast("onBillingInitialized");
readyToPurchase = true;
updateTextViews();
}
@Override
public void onPurchaseHistoryRestored() {
//showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d(LOG_TAG, "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d(LOG_TAG, "Owned Subscription: " + sku);
updateTextViews();
}
});
////////////////////////////////////////////////////////////////////////////////////////////
mAdView = view.findViewById(R.id.adView);
if (!subsProOK){
mAdView.setVisibility(View.VISIBLE);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
} else {
mAdView.setVisibility(View.GONE);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
private void updateTextViews() {
checkIfUserIsSusbcribed();
}
void checkIfUserIsSusbcribed(){
boolean purchaseResult = bp.loadOwnedPurchasesFromGoogle();
if(purchaseResult){
TransactionDetails subscriptionTransactionDetails = bp.getSubscriptionTransactionDetails(SUBSCRIPTION_ID);
if(subscriptionTransactionDetails!=null) {
//User is still subscribed
//showToast("User is still subscribed");
subsProOK = true;
} else {
//Not subscribed
//showToast("Not subscribed");
subsProOK = false;
}
}
}
@Override
public void onDestroy() {
if (bp != null) {
bp.release();
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!bp.handleActivityResult(requestCode, resultCode, data))
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onResume() {
super.onResume();
updateTextViews();
}
}
`
I think I have tracked down the issue...
I use the following in MainActivity.java:
boolean purchaseResult = bp.loadOwnedPurchasesFromGoogle();
if(purchaseResult){
TransactionDetails subscriptionTransactionDetails = bp.getSubscriptionTransactionDetails("widget_subscription");
if(subscriptionTransactionDetails!=null) {
//User is still subscribed
Log.d("BILLING ", "Subscription is valid");
fragment = (Fragment) fragmentClass.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.flContent, fragment, tag).commitAllowingStateLoss();
} else {
//Not subscribed
Log.d("BILLING ", "Subscription is NOT valid");
SweetAlertDialog sweetAlertDialog = new SweetAlertDialog(this, SweetAlertDialog.CUSTOM_IMAGE_TYPE)
.setTitleText("Subscribe")
.setContentText("The BIG Ferry Planner widget is a premium feature. Please subscribe to use it.")
.setCustomImage(R.drawable.ic_app)
.setConfirmText("Ok, got it")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
Log.d("BILLING", "Starting Widget Purchase Flow");
bp.subscribe(MainActivity.this, "widget_subscription");
}
});
sweetAlertDialog.setCancelable(false);
sweetAlertDialog.show();
}
}else{
Log.d("BILLING", "loadOwnedPurchasesFromGoogle returned false");
}
But, if I use similar code in AppWidgetProvider.java, loadOwnedPurchasesFromGoogle() always returns FALSE.
So perhaps it's because its being run in an AppWidget, not in the Application itself..
It is possible, although I execute it in every activity or fragment.
Looking at what loadOwnedPurchasesFromGoogle actually calls (not at a PC now so can't remember function names) it ultimately uses getContext().getPackageName() which may very well not return the name of the parent app.
This would cause it not to know which app to check purchases for, therefore failing (returning false).
All speculation, but I think I'm on the right track.
Ok, finally at a PC.. so, in the library
loadOwnedPurchasesFromGoogle()
does the following:
public boolean loadOwnedPurchasesFromGoogle()
{
return loadPurchasesByType(Constants.PRODUCT_TYPE_MANAGED, cachedProducts) &&
loadPurchasesByType(Constants.PRODUCT_TYPE_SUBSCRIPTION, cachedSubscriptions);
}
Inside loadPurchasesByType() you will find the following:
Bundle bundle = billingService.getPurchases(Constants.GOOGLE_API_VERSION,
contextPackageName, type, null);
And in BillingProcessor you can see the following:
private BillingProcessor(Context context, String licenseKey, String merchantId, IBillingHandler handler,
boolean bindImmediately)
{
super(context.getApplicationContext());
signatureBase64 = licenseKey;
eventHandler = handler;
contextPackageName = getContext().getPackageName();
cachedProducts = new BillingCache(getContext(), MANAGED_PRODUCTS_CACHE_KEY);
cachedSubscriptions = new BillingCache(getContext(), SUBSCRIPTIONS_CACHE_KEY);
developerMerchantId = merchantId;
if (bindImmediately)
{
bindPlayServices();
}
}
So, contextPackageName = getContext().getPackageName(); is obviously used to define the package name (app name) and I assume used in the call to google as "show me all the purchases that belong to 'appName' please".
Now, I speculate that getContext().getPackageName(); is not returning the name of my main package/app, as Im calling it from AppWidgetProvider.
This methods not works. Today I was unsubscribe (cancel my subscription), but this lib returns purchDetails.purchaseInfo.purchaseData.autoRenewing = true and returns List(BillingHistoryRecord) with size > 0. Google play showing, that user have no subscription (status - canceled).
So, all users can cancel subscription and use app free.
PS: bp.loadOwnedPurchasesFromGoogle() used every app starting, before checking purchases.
Sorry for my ugly russian english!
Once the user cancels, they have until the end of the current billing cycle to use the widget. After that, it will show as cancelled.
This methods not work to me too.
Like @nikitoSha said.
Does anyone have a solution?