android in app billing using version 3 - onIabPurchaseFinished never invoked - android

Have looked at this link :
onIabPurchaseFinished never called.
and ....
I have looked at the following link in so - which is the exact issue that I am facing :
IabHelper PurchaseFinishedListener
The issue stated by 'kevinl' in one of the answers in the above second link - is the issue I have i.e.
I have the 'onActivityResult' coded in my activity
The control does come into this method - but yet - it does not go to - onIabPurchaseFinished
I am invoking the Purchase flow as below:
public void onClickIap(View v) {
if(isAlreadyPurchased){
Log.d(TAG, "In onClickIap >>> The app is purchased ");
}else{
Log.d(TAG, "In onClickIap >>> The app is NOT purchased: ");
/** we need to check if this item was purchased from google store details **/
/** makes synchronous call -return control to callback method in listener **/
/** this is the heart of in app - where all the activity starts **/
mHelper.launchPurchaseFlow(this, ITEM_SKU_TEST_4, 10001,mPurchaseFinishedListener, "mypurchasetoken");
mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase){
Log.d(TAG, "In onIabPurchaseFinished ");
if (result.isFailure()) {
Log.d(TAG, "In onIabPurchaseFinished >>> purchase error ");
return;
}else if (purchase.getSku().equals(ITEM_SKU_TEST_4)) {
Log.d(TAG, "In onIabPurchaseFinished purchase success ");
/**TODO update the db **/
DbClass dbDbClass = new DbClass(getApplicationContext());
dbHelper.updatePurchasedQuiz(2);
/** set the flag **/
isAlreadyPurchased = true;
Log.d(TAG, "The app after all the shebang: ");
Intent intent = new Intent(getApplicationContext(),ExamActivity.class);
intent.putExtra("quizId", "2");
startActivity(intent);
}else{
Log.d(TAG, " ");
Log.d(TAG, "In onIabPurchaseFinished in a weird condition ");
}
}
};
}
}
My implementation of onActivityResult ( where I can see the debug logs )
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, " ");
Log.d(TAG, "<<<<<< in IAP ONACTIVITYRESULT >>>>>>");
Log.d(TAG, "onActivityResult " + requestCode + "," + resultCode + "," + data);
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
Dont know if this would be the cause but the way I am invoking / initiatlizing the mHelper is :
1 In my activity onCreate - I start a thread ( AsyncTask )
2 In the method - doInBackground - I am initializing this helper :
mHelper = new IabHelper(ExamHomeActivity.this, base64EncodedPublicKey);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { .... }
3 Then when this async task completes - I show some buttons on the screen
4 Clicking on any button invokes the method described above - onClickIap
Thanks
shankar

You are passing the mPurchaseFinishedListener before you have initialized it (which happens right after you call launchPurchaseFlow).
Also, initializing the listener in the onClick is not a very good idea. You will be doing this every time the button is pressed. Take that out from the onClickIap() method.
You can do that statically in your Activity, just like it is done in Google's sample app Trivial Drive:
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase){
Log.d(TAG, "In onIabPurchaseFinished ");
...
...
...
}
};
Initializing the IabHelper is probably best to be done in the onCreate() of the activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_subscribe);
mHelper = new IabHelper(this);
}
Then do not forget to dispose of it in onDestory():
#Override
protected void onDestroy() {
super.onDestroy();
// very important:
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
Also, there is no need to set up the IabHelper from a background thread. Just call mHelper.startSetup from your UI thread whenever you are ready.

Related

Play billing library connection and querying sku details

I need some input here on how connection is made and querying the sku details. I'm working on the tutorial and copying the in app billing logic over to my app.
https://codelabs.developers.google.com/codelabs/play-billing-codelab
I followed the tutorial without any issues. The issue lies in how the connection is made and then querying the sku details.
When I made an instance of BillingManager class, it'll attempt to make a connection -
public BillingManager(Activity activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
Then, I will be making async query to get the sku details -
private void handleManagerAndUiReady() {
// Start querying for SKUs
List<String> inAppSkus = mBillingProvider.getBillingManager()
.getSkus(SkuType.INAPP);
mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP,
inAppSkus,
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode,
List<SkuDetails> skuDetailsList) {
if (responseCode == BillingResponse.OK
&& skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
Log.w(TAG, "Got a SKU: " + details);
}
}
}
});
// Show the UI
displayAnErrorIfNeeded();
}
Then I got the listener getting an error yet the connection is made without any issues.
D/StoreListFragment: onCreate
I/StoreListFragment: SkuDetailsResponseListener response code: -1
D/StoreListFragment: onViewCreated
I/BillingManager: onBillingSetupFinished() response: 0
So I had to figure out for some time and gave up to check the basics of Play Billing Library -
https://medium.com/exploring-android/exploring-the-play-billing-library-for-android-55321f282929
That is where I found the solution, I just put the query in the connection where it is successfully connected. I realized the play billing library doesn't check the connection BEFORE it goes querying the sku details or am I doing wrong somewhere since the tutorial is working fine?
private void createBillingClient() {
mBillingClient = BillingClient.newBuilder(getActivity()).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
//setting up a listener for the queries
SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode,
List<SkuDetails> skuDetailsList) {
Log.i(TAG, "response code: " + responseCode);
}
};
List<String> skuList = Arrays.asList("sku_01", "sku_02");
SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
.setSkusList(skuList).setType(BillingClient.SkuType.SUBS).build();
mBillingClient.querySkuDetailsAsync(skuDetailsParams, responseListener);
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
I tried this logic to check if the connection is ready then execute the runnable like as trivial drive - citing this url. It looks like the logic doesn't check if the billing connection is pending....
https://github.com/zumrywahid/in_app_example
Is Billing Client connected? : false
Client is already in the process of connecting to billing service.
onBillingSetupFinished() error code: 5
BillingManager constructor is already starting connection and if you initialize the manager and immediately call any method that passes runnable to executeServiceRequest(Runnable runnable) will also try to start connection at the same time. You can disable startServiceConnection() in the constructor since connection status is always checked in executeServiceRequest() and starts connection if needed.
I solved this issue in the following way:
I created a helper class that manages the billing and added listeners to it, so I can do things only when other actions are ready.
For example, I tried to show a list of in-app purchases in the onCreate of the activity, but the list was always empty. That was because the result of querySkuDetailsAsync was received after the code that displayed the list was executed.
I created a listener and I only display the list after the result is received.
interface BillingResponseIsAvailableListener {
void onBillingResponseIsAvailable(List<SkuDetails> theList);
}
public class BillingTools {
List<String> skuList = new ArrayList<>();
private List<BillingResponseIsAvailableListener> listeners = new ArrayList<>();
private BillingClient billingClient;
private Context context;
public BillingTools(Context context) {
this.context = context;
createSKUList(); //method to create the list of sku's for your app
}
public void addListener(BillingResponseIsAvailableListener toAdd) {
listeners.add(toAdd);
}
and where you query the sku list, just add the listener when the list is ready:
billingClient.querySkuDetailsAsync(params.build(), (billingResult2, skuDetailsList) -> {
if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (BillingResponseIsAvailableListener bL : listeners) {
bL.onBillingResponseIsAvailable(skuDetailsList);
}
}
});
The above code is just an example, I recommend you create your own listeners for whatever you need to do. It is part of a method called queryOptionsList();
In the calling activity, just make the listener do whatever you need to do with the list:
BillingTools takeTheirMoney = new BillingTools(this);
takeTheirMoney.startBillingClient();
takeTheirMoney.addListener(new BillingResponseIsAvailableListener() {
#Override
public void onBillingResponseIsAvailable(List<SkuDetails> theList) {
showPurchaseOptions(findViewById(R.id.purchases_container), theList, takeTheirMoney);
}
});
takeTheirMoney.queryOptionsList();
If you are having problems with the connection not being ready, just add another listener to monitor when the connection is successful, and in that listener place additional actions (which can have their own listeners as well).

Identifier not found in Directory

I am integrating Azure in my app.I have created app at Azure portal in Active directory and for integration using ADAL library but when i am running the app getting login screen of Microsoft and after login getting error this.
i am getting this error
For login created button and onclick on button calling this
if (mResult != null) {
// logout
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();
authenticationContext.getCache().removeAll();
} else {
// login
authenticationContext.acquireToken(LoginActivity.this,Constant.CLIENT_ID,
Constant.CLIENT_SECRETE_KEY, Constant.REDIRECT_URL, "", PromptBehavior.Auto, "",
callback);//CLIENT_ID=my appid at the of app registration //CLIENT_SECRETE_KEY=secret key of registered app in Active Directory //REDIRECT_URL=passing valid url
}
and initializing authenticationcontext in oncreate() of LoginActivity
authenticationContext = new AuthenticationContext(LoginActivity.this,
Constant.AUTHORITY_URL, true);// Authority_URL=https://login.windows.net/mydirectoryname.onmicrosoft.com
for callback
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
authenticationContext.onActivityResult(requestCode, resultCode, data);
}
private void showInfo(String msg) {
Log.e("", msg);
}
private AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
#Override
public void onError(Exception exc) {
showInfo("getToken Error:" + exc.getMessage());
}
#Override
public void onSuccess(AuthenticationResult result) {
mResult = result;
startActivity(new Intent(LoginActivity.this, SecondActivity.class));
if (mResult.getUserInfo() != null) {
Log.v("", "User info userid:" + result.getUserInfo().getUserId()
+ " displayableId:" + result.getUserInfo().getDisplayableId());
}
}
};
and in Azure Portal in app registration grand the permission of KEYVAULT and MicrosoftAzureActiveDirectory (sign in and read user profile permission)
What i am doing wrong?
Your parameters are probably wrong. Check this example on GitHub: https://github.com/Azure-Samples/active-directory-android/blob/master/TaskApplication/src/main/java/com/microsoft/aad/test/todoapi/ToDoActivity.java#L188.
There it is done with:
mAuthContext.acquireToken(ToDoActivity.this, Constants.RESOURCE_ID,
Constants.CLIENT_ID, Constants.REDIRECT_URL, Constants.USER_HINT,
new AuthenticationCallback<AuthenticationResult>() {
#Override
public void onError(Exception exc) {
if (mLoginProgressDialog.isShowing()) {
mLoginProgressDialog.dismiss();
}
Toast.makeText(getApplicationContext(),
TAG + "getToken Error:" + exc.getMessage(), Toast.LENGTH_SHORT)
.show();
navigateToLogOut();
}
#Override
public void onSuccess(AuthenticationResult result) {
if (mLoginProgressDialog.isShowing()) {
mLoginProgressDialog.dismiss();
}
if (result != null && !result.getAccessToken().isEmpty()) {
setLocalToken(result);
sendRequest();
} else {
navigateToLogOut();
}
}
});
You shouldn't have your client secret in the mobile app anyway. Anyone can unpack you application and find it.
if (result != null && !result.getAccessToken().isEmpty()) {
is wrong, add token null check or add getStatus success check...
yes, ms special create bad android sample [ I think :) ];
simplest way is open your app creted under AD and open edit manifest:
from sample:
AUTHORITY_URL - is "identifierUris" from app manifest (not android manifest; add-manifest from ad registered web-api app);
CLIENT_ID - is appId;
RESOURCE_ID - is from manifest too;
"requiredResourceAccess": [ { "resourceAppId": some times it comes in auth ex message;
and you should add same reply Url at AD app too;
if redirect after auth directly to todoactivity for some reason result failed status, but if open add user you can see there sign-in user info and sign in success status;
So after fix that sample it works at all, but it was a hard 1-2 days brainstorm;
expected more form ms sample, looks like with each year qulity is go momre and more low;

Android inapp purchasing consume able product

I have implemented in-app purchased in my application and my product type is Managed and i am using API version 3.
When i make purchase from my credit card it is successfully done.
But the problem is if i uninstall my application and want to purchase this with same account it will charge me again?
According to Google rules of managed product type we only purchase the product once? But why is this happening ?
any one help me please?
here is my PurchaseActivity.java class
public abstract class PurchaseActivity extends BlundellActivity implements OnIabSetupFinishedListener, OnIabPurchaseFinishedListener {
private IabHelper billingHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_purchase);
setResult(RESULT_CANCELED);
billingHelper = new IabHelper(this, AppProperties.BASE_64_KEY);
billingHelper.startSetup(this);
}
#Override
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
Log.d("In-app Billing set up" + result);
dealWithIabSetupSuccess();
} else {
Log.d("Problem setting up In-app Billing: " + result);
dealWithIabSetupFailure();
}
}
protected abstract void dealWithIabSetupSuccess();
protected abstract void dealWithIabSetupFailure();
protected void purchaseItem(String sku) {
billingHelper.launchPurchaseFlow(this, sku, 123, this);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
billingHelper.handleActivityResult(requestCode, resultCode, data);
}
*/
#Override
public void onIabPurchaseFinished(IabResult result, Purchase info) {
if (result.isFailure()) {
dealWithPurchaseFailed(result);
} else if (pmg.SKU.equals(info.getSku())) {
dealWithPurchaseSuccess(result, info);
}
finish();
}
protected void dealWithPurchaseFailed(IabResult result) {
Log.d("Error purchasing: " + result);
}
protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
Log.d("Item purchased: " + result);
// DEBUG XXX
// We consume the item straight away so we can test multiple purchases
billingHelper.consumeAsync(info, null);
// END DEBUG
}
#Override
protected void onDestroy() {
disposeBillingHelper();
super.onDestroy();
}
private void disposeBillingHelper() {
if (billingHelper != null) {
billingHelper.dispose();
}
billingHelper = null;
}
}
This is working as intended - in your code you are consuming the in-app purchase immediately, which means you can then purchase it again:
protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
Log.d("Item purchased: " + result);
// DEBUG XXX
// We consume the item straight away so we can test multiple purchases
billingHelper.consumeAsync(info, null);
// END DEBUG
}
There's nothing that says you can't purchase a managed product more than once. What you can't do is purchase a managed product before a previous purchase of the same managed item has been consumed. So this is working exactly as intended, and if you remove that call to consumeAsync, you'll see that you can't purchase it again.
Sample use case:
Imagine some game where you can purchase extra lives. First, the user would purchase the extra lives (a managed in app product), your game (client or server) would then add those lives to the user's profile, for example, and assuming that was successful, you'd tell Google Play that the purchase has been consumed.
This is important in order to handle error cases - for example say the user's device dies in between the initial purchase and the addition of lives to the user's profile. Your app can then, the next time it's launched, try again to add those lives, and consume the purchase on success. And, obviously you wouldn't want the user trying to purchase even more lives before you successfully grant them - which is why you can't purchase a managed product twice before it's been consumed.

In-App-Billing: Cant set up IAB Helper exception

I am trying to set up in-app-billing and test using a static product ID in Google Play.
Am following developer tutorial here.
When the launhPurcahseFlow method is called on the labHelper object I get the exception:
java.lang.IllegalStateException: IAB helper is not set up. Can't
perform operation: launchPurchaseFlow at
com.android.vending.billing.IabHelper.checkSetupDone(IabHelper.java:782)
Have been searching for hours and can't find a solution that works.
Any input appreciated.
My code is:
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
// compute your public key and store it in base64EncodedPublicKey
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
//perform service binding to Google Bill ser and return ny errors with IabResult object and listener
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
alert("Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
//mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
//ILLEGALSTAEEXCEPTION THROWN HERE
mHelper.launchPurchaseFlow(this, testProduct, RC_REQUEST,
new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
else if (purchase.getSku().equals(testProduct)) {
// give user access to premium content and update the UI
//set the purchaesd booean to true
//when purcajsed add this code
editor.putBoolean("purchased", true);
editor.commit();
Toast.makeText(getApplicationContext(), "ADD FREE VERSION PURCAHSED!!!" +
" Details OrderID: "+purchase.getOrderId() +" Payload ID: "+purchase.mDeveloperPayload, Toast.LENGTH_LONG).show();
Log.d("CONNECT TO GOOGLE BILL", "ITEM PURCAHSED! : "+purchased);
}
}
}, "diveAppPurchase");
}//onCreate
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
This be useful to those like me, who are dealing with in app purchasing first time.
The set up call is asynchronous, therefore this must complete before the launchPurchaseFlow is called. So i disabled a 'Purchase' button to make the launchPurchaseFlow call, which is enabled once the set up call is done. Works fine:
Set up call
//perform service binding to Google Bill ser and return ny errors with IabResult object and listener
mHelper.startSetup(...)
if successfully then enable button and call mHelper.launchPurchaseFlow(...) method
Also, when using the test product id provided by google you may note a signature exception is thrown during the launchPurchaseFlow with a subsequent result failure although the transaction is successful , this is apparently a known bug that google are aware of

mHelper.launchPurchaseFlow start twice

For buy i have code
public void onClick(View v)
{
// FIRST CHECK IF THE USER IS ALREADY A SUBSCRIBER.
mHelper.launchPurchaseFlow(SubscribeIntroActivity.this, SUBSCRIBE_SKU, RC_REQUEST, mPurchaseFinishedListener);
}
public void onClick(View v)
{
// FIRST CHECK IF THE USER IS ALREADY A SUBSCRIBER.
mHelper.launchPurchaseFlow(SubscribeIntroActivity.this, SUBSCRIBE_SKU, RC_REQUEST, mPurchaseFinishedListener);
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d(TAG,"Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d(TAG,"Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_PREMIUM)) {
// bought the premium upgrade!
Log.d(TAG, "Purchase is premium upgrade. Congratulating user.");
Log.d(TAG,"Thank you for upgrading to premium!");
mIsPremium = true;
isShortVersion=false;
DrawIsShortVersion();
}
}
};
Error in LOG IS
Caused by: java.lang.IllegalStateException: Can't start async operation (launchPurchaseFlow) because another async operation(launchPurchaseFlow) is in progress.
when i click button its open buyer dialog and i can buy without any problem, but if i click button dialog is opened, Push BACK hardware button on device (didnt buy) i see first activity with button BUY, push it for buy again and raise error. May be somebody know about this error? what i do wrong?
... added if (mHelper != null) mHelper.flagEndAsync(); before call launchPurchaseFlow, now no errors, but mHelper.flagEndAsync(); kill previos task ?
Can you post your Logs?
In most cases an error occours when an Async PurcaseFlow is running and yout try to run an other Purcase.
what are you doing in your Purcase Listener?
Edit: you have multiple Coices to make that:
First: create a global boolean -> mTaskIsRunning;
private void doBuyProduct(int id){
if(!mTaskIsRunning){
mHelper.launchPurchaseFlow(SubscribeIntroActivity.this, SUBSCRIBE_SKU, RC_REQUEST, mPurchaseFinishedListener);
mTaskIsRunning=true;
}
}
Then in your Listener:
....
mTaskIsRunning=false;
Or you can disable the Buttons while a Task is Running
Same Procedure :
private void doBuyProduct(int id){
disableBuyButtons();
mHelper.launchPurchaseFlow(SubscribeIntroActivity.this, SUBSCRIBE_SKU, RC_REQUEST, mPurchaseFinishedListener);
}
and in the FinishListener:
enableButtons();

Categories

Resources