I'm very confused on how in-app billing works. I've read the documentation and I must have missed something because I don't understand the final step I need to implement into my application to make this work. The in-app billing works great, however, if a user uninstalls my app and installs it again at a future date, my application doesn't know how to determine if the in-app purchase has previously been made. Here's a snippet from my main class where I attempt to handle all of this:
#Override
public void onCreate(Bundle savedInstanceState)
{
mContext = this;
startService(new Intent(mContext, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);
}
I am using the example classes from the dungeons example project. What I don't understand is how the below code works at the time of purchase, but re-running it doesn't work to check if something been purchased already. I have been stuck on this part for about a month now and I've been getting very frustrated with it.
public Handler mTransactionHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "
+ BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "
+ BillingHelper.latestPurchase.productId);
if (BillingHelper.latestPurchase.isPurchased())
{
new Message("Thank you!", getApplicationContext());
PAY_VERSION = true;
SharedPreferences purchasePreferences = getSharedPreferences(PURCHASE_PREFERENCES, Activity.MODE_PRIVATE);
Editor purchaseEditor = purchasePreferences.edit();
purchaseEditor.putBoolean("purchased", PAY_VERSION);
purchaseEditor.commit();
Intent intent = getIntent();
finish();
startActivity(intent);
}
};
What I need is some way to query the server to see if this item has been purchased or not. I understand that there's a PURCHASE_STATE_CHANGED thing there somewhere however I have no idea how to do anything when it determines the state has changed or how to initiate it to check. I'm lost and all I need is a good push in the right direction because so far I'm just completely lost.
EDIT:
I've also heard you need to parse JSON, but I have no idea how to even begin doing that.
EDIT 2:
Am I supposed to call this stuff to check?
BillingHelper.restoreTransactionInformation(BillingSecurity.generateNonce());
BillingHelper.getPurchaseInformation(new String[] {"myItem"});
That code previously had crashed on my sisters phone (SGS3, ICS) but not on mine (GN, ICS, and JB work). I was calling it in onCreate() of my first activity. Not really sure what to do with getPurchaseInformation(...) once it's been called. It has no return value so I'm not sure if I can parse the JSON or whatever I'm supposed to do...
Also, those 2 lines give me this:
08-27 11:54:04.271: E/BillingService(17702): BillingHelper not fully instantiated
08-27 11:54:04.271: E/BillingService(17702): BillingHelper not fully instantiated
JSON
An example of a JSON order object that includes a subscription purchase token is shown below.
{ "nonce" : 1836535032137741465,
"orders" :
[{ "notificationId" : "android.test.purchased",
"orderId" : "transactionId.android.test.purchased",
"packageName" : "com.example.dungeons",
"productId" : "android.test.purchased",
"developerPayload" : "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
"purchaseTime" : 1290114783411,
"purchaseState" : 0,
"purchaseToken" : "rojeslcdyyiapnqcynkjyyjh" }]
}
How to parse JSON in Java.
Android supports JSON library
Android Billing
If you have a remote server, we recommend that you store purchase information on your server instead of in a local database on a device. Look here.
Read about Managed per user account, Unmanaged, or Subscription
I use billing like this:
In onCreate
BillingHelper.setCompletedHandler(handlerTransaction);
You have handler in your code.
Next in onClick or something
BillingHelper.requestPurchase(this, currentMarketProduct.getMarketId());
You are on the right track with the second update. The trick is to realise everything is done in async.
The typical flow is as follows:
User installs your app.
On first load of your app, you check if you need to restore
purchases.
If you do, send a RESTORE_TRANSACTION synchronous request to Google.
Google will respond with a acknowlegment response to your
RESTORE_TRANSACTION request. (This is only an acknowlegement that
they received your request.)
At this point, you should mark that you had already sent a restore request to Google and no further restores needs to be sent from the app.
Now asynchronously Google will start sending a 'PURCHASE_STATE_CHANGED' event to your app for each in-app purchase the user has previously purchased. This call is the same as what Google would had sent if the user had made that purchase for the first time.
Since it's the same call, your app would pick up the event and handled it normally as if the user has just purchased the in-app product (thereby "restoring" the purchased feature).
In regard to steps 2 and 5, what I've done for my app is to keep a SharedPreference value called 'APP_INITIALISED' that defaults to false. Everytime my app starts up, if 'APP_INITIALISED' is false, I tell Google to RESTORE_TRANSACTION (step 2) then I set APP_INITIALISED to true (step 5).
Related
I'm having trouble figuring out how to detect when a refund has been issued for a managed (uncomsumable) in-app product in Android using com.android.billingclient:billing:2.0.3. The problem seems fairly deep though maybe I'm making it more complicated than it ought to be.
To begin, I've made a test purchase which has been acknowledged AND refunded:
Looking at the logs of my app I see the following:
D/BillingManager: Got a verified purchase: Purchase. Json: {"orderId":"GPA.3362-7185-5389-78416","packageName":"com.glan.input","productId":"pro","purchaseTime":1567672759460,"purchaseState":0,"purchaseToken":"pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ","acknowledged":true}
I/BillingManager: purchase pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ is in 1 state
There's something funny going on here:
We can see the order IDs match up between what's in the image and the detected purchase
The first log line is printing the purchase with Log.d(TAG, "Got a verified purchase: " + purchase); which is printing the underlying JSON which represents the purchase.
Note that "purchaseState":0
The second log line is issued with Log.i(TAG, "purchase " + purchase.getPurchaseToken() + " is in " + purchase.getPurchaseState() + " state");.
Note that here purchase.getPurchaseState() is resulting in a value of 1
If I look at the implementation of getPurchaseState in Android Studio I see the following:
public #PurchaseState int getPurchaseState() {
switch (mParsedJson.optInt("purchaseState", PurchaseState.PURCHASED)) {
case 4:
return PurchaseState.PENDING;
default:
return PurchaseState.PURCHASED;
}
}
Earlier in the file the PurchaseState interface is declared as:
#Retention(SOURCE)
public #interface PurchaseState {
// Purchase with unknown state.
int UNSPECIFIED_STATE = 0;
// Purchase is completed.
int PURCHASED = 1;
// Purchase is waiting for payment completion.
int PENDING = 2;
}
It seems like getPurchaseState never returns PurchaseState.UNSPECIFIED_STATE and only returns PENDING which the JSON comes with a value of 4. I've confirmed that a state of PENDING is correctly returned when the purchase is performed with a payment method that takes a while to approve.
I've found posts like In-App Billing v3 - Don't detect refund which suggest that Play Services are caching purchases but I'm not convinced that's causing this problem because if I modify my code betweens runs of my app to acknowledge/consume the purchase those get state changes get immediately reflected in the JSON of the purchase.
How am I supposed to detect a refunded managed product?
I have one purchase (SkuType.INAPP) in my application. I make a test purchase and then make a refund.
Problem:
purchase.getOriginalJson() // contains "purchaseState":0
purchase.getPurchaseState() // returns 1
Inside com.android.billingclient.api.Purchase:
public int getPurchaseState() {
switch(this.zzc.optInt("purchaseState", 1)) {
case 4:
return 2;
default:
return 1;
}
}
//...
public #interface PurchaseState {
int UNSPECIFIED_STATE = 0;
int PURCHASED = 1;
int PENDING = 2;
}
Hacky way to check purchaseState from original json:
purchase.getOriginalJson().contains(String.format("\"purchaseState\":%s", Purchase.PurchaseState.PURCHASED))
Unfortunately, this problem still exists!
More details here.
You can check if still purchase exits following
binding.btnRestore.setOnClickListener(v->{
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
for ( Purchase purchase : purchasesResult.getPurchasesList()){
handlePurchase(purchase);
}
});
Google Play returns the purchases made by the user account logged in to the device. If the request is successful, the Play Billing Library stores the query results in a List of Purchase objects.
Note: Only active subscriptions appear on this list. As long as the in-app product is on this list, the user should have access to it. For further information, see the Handle SUBSCRPTION_ON_HOLD section of Add subscription-specific features.
To retrieve the list, call getPurchasesList() on the PurchasesResult. You can then call a variety of methods on the Purchase object to view relevant information about the item, such as its purchase state or time. To view the types of product detail information that are available, see the list of methods in the Purchase class.
You should call queryPurchases() at least twice in your code:
Call queryPurchases() every time your app launches so that you can restore any purchases that a user has made since the app last stopped.
Call queryPurchases() in your onResume() method, because a user can make a purchase when your app is in the background (for example, redeeming a promo code in the Google Play Store app).
Calling queryPurchases() on startup and resume guarantees that your app finds out about all purchases and redemptions the user may have made while the app wasn't running. Furthermore, if a user makes a purchase while the app is running and your app misses it for any reason, your app still finds out about the purchase the next time the activity resumes and calls queryPurchases().
Query most recent purchases
The queryPurchases() method uses a cache of the Google Play Store app without initiating a network request. If you need to check the most recent purchase made by the user for each product ID, you can use queryPurchaseHistoryAsync(), passing the purchase type and a PurchaseHistoryResponseListener to handle the query result.
queryPurchaseHistoryAsync() returns a PurchaseHistory object that contains info about the most recent purchase made by the user for each product ID, even if that purchase is expired, cancelled, or consumed. Use queryPurchases() whenever possible, as it uses the local cache, instead of queryPurchaseHistoryAsync(). If using queryPurchaseHistoryAsync(), you can also combine it with a Refresh button, allowing users to update their list of purchases.
The following code demonstrates how you can override the onPurchaseHistoryResponse() method:
private void handlePurchase(Purchase purchase) {
if(purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (purchase.getSku().equals(skuPro)) {
EntityPRO entityPRO = RoomDB.getDatabase(context).proDAO().getLastItem();
entityPRO.isBought = true;
RoomDB.getDatabase(context).proDAO().updateSpecificSLI(entityPRO);
Toast.makeText(context, context.getString(R.string.pro_succesfully_bought), Toast.LENGTH_LONG).show();
}
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
}
}
I have the serverless android app with simple functional: if user has some in-app subscription (auto renewable), then he can use functional in app, otherwise there is no. I know, how to make functional with obtaining subscriptions info (price, title etc) and calling payment. But I can not check if current user has active (not cancelled) subscriptions. I read so many information on many sites and tutorials, and there was written that I must use google API in my server. But I do not have my own server.
I used two different libraries for in-app subscriptions:
'com.anjlab.android.iab.v3:library:1.0.44'
and
'com.android.billingclient:billing:1.1'
but no one helped me for checking if user has active subscriptions. So, how to make this task? Help me please, maybe I missed some information...
Edit: Anjlab library hasn't been updated in the longest time ever. Kindly use Google's own billing library, this step-by-step process should help you easily integrate it into your app - Google In App Billing library
Using the anjlab in-app-billing library I was also facing the similar. This is what I did to get around it.
Invoke the method billingProcessor.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.
void checkIfUserIsSusbcribed(){
Boolean purchaseResult = billingProcessor.loadOwnedPurchasesFromGoogle();
if(purchaseResult){
TransactionDetails subscriptionTransactionDetails = billingProcessor.getSubscriptionTransactionDetails(YOUR_SUBSCRIPTION_ID);
if(subscriptionTransactionDetails!=null)
//User is still subscribed
else
//Not subscribed
}
}
Also, point to note is that the TransactionDetails object will only return null after the period of the subscription has expired.
Have you try to call bp.loadOwnedPurchasesFromGoogle(); ?
Edit
So try this :
Purchase purchase = inventory.getPurchase(product);
Log.d(TAG, "Purchase state: " + purchase.getPurchaseState());
// 0 (purchased), 1 (canceled), or 2 (refunded).
if (purchase.getPurchaseState() == 0
|| purchase.getPurchaseState() == 2) {
showPremiumVersion();
} else {
showFreeVersion();
}
Or this solution :
bp.isPurchased("yourSKU")
The isPurchsed method can't catch history of error's purchase / canceled Purchase/ Retrived Purchase.
Use this and don't forget to add INTERNET permission in your manifest.
TransactionDetails transactionDetails = billingProcessor.getPurchaseTransactionDetails("productId");
if (transactionDetails != null) {
//Already purchased
//You may save this as boolean in the SharedPreferences.
}
I'm on the edge of finishing my first app, and one last remaining thing is to implement IAP billing, so that's why I am currently reading quite a lot about the topic (including security concerns like encryption, obfuscation and stuff).
My app is a free version, with the ability to upgrade to full verison via IAP, so there would be just one managed purchase item "premium". I have a few questions about this:
In the Google IAP API example (trivialdrivesample), there's always the IAP check in MainActivity to see if the user bought the premium version, done via
mHelper.queryInventoryAsync(mGotInventoryListener);
My first concern:
This does mean that the user always needs to have an internet/data connection at app-startup, to be able switch to the premium version right? What if the user doesn't have an internet connection? He would go with the lite version I guess, which I would find annoying.
So I thought about how to save the isPremium status locally, either in the SharedPrefs or in the app database. Now, I know you can't stop a hacker to reverse engineer the app, no matter what, even so because I don't own a server to do some server-side validation.
Nevertheless, one simply can't save an "isPremium" flag somewhere, since that would be too easy to spot.
So I was thinking about something like this:
User buys Premium
App gets the IMEI/Device-ID and XOR encodes it with a hardcoded String key, saves that locally in the app database.
Now when the user starts the app again:
App gets encoded String from database, decodes it and checks if decodedString == IMEI. If yes -> premium
If no, then the normal queryInventoryAsync will be called to see if the user bought premium.
What do you think about that approach? I know it's not supersecure, but for me it's more important that the user isn't annoyed (like with mandatory internet connection), than that the app will be unhackable (which is impossible anyway).
Do you have some other tips?
Another thing, which I currently don't have a clue about, is how to restore the transaction status when the user uninstalls/reinstalls the app. I know the API has some mechanism for that, and aditionally my database can be exported and imported through the app (so the encoded isPremium flag would be exportable/importable as well). Ok, I guess that would be another question, when the time is right ;-)
Any thoughts and comments to this approach are welcome, do you think that's a good solution? Or am I missing something/heading into some wrong direction?
I too was making the same investigations, but during my testing I figured out that you do not need to store it, as Google do all the caching you need and I suspect (though I have not investigated it) that they are doing so as securely as possible (seeing as it in their interest too!)
So here is what i do
// Done in onCreate
mHelper = new IabHelper(this, getPublicKey());
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log("Problem setting up In-app Billing: " + result);
} else {
Log("onIabSetupFinished " + result.getResponse());
mHelper.queryInventoryAsync(mGotInventoryListener);
}
}
});
// Called by button press
private void buyProUpgrade() {
mHelper.launchPurchaseFlow(this, "android.test.purchased", 10001,
mPurchaseFinishedListener, ((TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId());
}
// Get purchase response
private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
if (result.isFailure()) {
Log("Error purchasing: " + result);
return;
}
else if (purchase.getSku().equals("android.test.purchased")) {
Log("onIabPurchaseFinished GOT A RESPONSE.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
}
};
// Get already purchased response
private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// handle error here
Log("Error checking inventory: " + result);
}
else {
// does the user have the premium upgrade?
mIsPremium = inventory.hasPurchase("android.test.purchased");
setTheme();
Log("onQueryInventoryFinished GOT A RESPONSE (" + mIsPremium + ").");
}
}
};
So what happens here?
The IAB is set up and calls startSetup, on a successful completion (as long as it has been run once with an internet connection and is set up correctly it will always succeed) we call queryInventoryAsync to find out what is already purchased (again if this has been called while online it always works while offline).
So if a purchase is completed successfully (as can only be done while online) we call queryInventoryAsync to ensure that it has been called while online.
Now there is no need to store anything to do with purchases and makes your app a lot less hackable.
I have tested this many ways, flight mode, turning the devices off an on again and the only thing that messes it up is clearing data in some of the Google apps on the phone (Not likely to happen!).
Please contribute to this if you have different experiences, my app is still in early testing stage.
I refactored ne0's answer into a static method, including the comments from snark.
I call this method when my app starts - you'll need to enable your features at the TODO
/**
* This is how you check with Google if the user previously purchased a non-consumable IAP
* #param context App Context
*/
public static void queryPlayStoreForPurchases(Context context)
{
final IabHelper helper = new IabHelper(context, getPublicKey());
helper.startSetup(new IabHelper.OnIabSetupFinishedListener()
{
public void onIabSetupFinished(IabResult result)
{
if (!result.isSuccess())
{
Log.d("InApp", "In-app Billing setup failed: " + result);
}
else
{
helper.queryInventoryAsync(false, new IabHelper.QueryInventoryFinishedListener()
{
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
// If the user has IAP'd the Pro version, let 'em have it.
if (inventory.hasPurchase(PRO_VERSION_SKU))
{
//TODO: ENABLE YOUR PRO FEATURES!!
Log.d("IAP Check", "IAP Feature enabled!");
}
else
{
Log.d("IAP Check", "User has not purchased Pro version, not enabling features.");
}
}
});
}
}
});
}
This will work across reboots and without a network connection, provided the user purchased the item.
Since you already know that it's impossible to make it unhackable using this system, I would recommend not attempting to prevent hacking. What you are proposing is known as "Security through obscurity" and is usually a bad idea.
My advice would be to try queryInventoryAsync() first, and only check your 'isPremium' flag if there is no internet connection.
There are also a few potential other ways of going about this, such as having separate free and premium apps, instead of an in app purchase. How other people handle this and the tools Google makes available might warrant an investigation.
queryInventoryAsync will automatically take into account uninstall and reinstalls, as it tracks purchases for the logged in user.
Yes the purchases can be retrieved offline. Also, I'm thinking about counting how many times the user opens the app as a mechanism before showing the billing UI.
I've implemented in-app billing from this tutorial. The item to buy is the ability to set a custom background. It works great, but when I uninstall and re-install the app (or clear the user prefs), I'm having trouble figuring out how to verify that somebody has already purchased the in-app item.
public void buySelected() {
if (backgroundColorsPurchased == true) {
this.colorChangeDialog(); //if user has already purchased, just call the dialog instead of re-buying.
//if the person has cleared their prefs, they'll have to be online to re-verify that they did indeed buy the item.
}else{
if(BillingHelper.isBillingSupported()){
BillingHelper.requestPurchase(mContext, "background.colors");
BillingHelper.setCompletedHandler(mTransactionHandler);
} else {
Log.i(TAG,"Can't purchase on this device");
}
}
}
Then I have the handler:
public Handler mTransactionHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
if(BillingHelper.latestPurchase.isPurchased()){
//this is where we show the stuff that the person purchased. In this case, the dialog to change the background colour.
backgroundColorsPurchased = true; //just setting this to true so that the next time somebody clicks the donate button it'll just open the dialog.
//call the change background dialog
colorChangeDialog();
}else{
//fail
Toast.makeText(getApplicationContext(), "Fail", Toast.LENGTH_SHORT).show();
}
}
};
How am I able to verify that the item has been purchased before? The market just keeps popping a dialog window that says, "You have already purchased this item, or the purchase is still pending." When I try using something like if(BillingHelper.latestPurchase.isPurchased()){ I get a force close if it's not within the handler.
You need to call restoreTransactions take a look at the default example that Android has provided for In App Billing for more reference.
You need to restore transactions if the preferences/data have been cleared. That will send you information of the items the user has purchased, and you can process authorizations as usual. Read reference and check the Dungeons sample about how to do this.
you can simply use Fire-base login & realtime database with field premium_enabled - true/false (this will help if user goes for another mobile with old google id for Play-store)
step wise
add google login in your App.
make an entry of user in fire-base user_id/email_id and premium_enabled field and store data in it. (you can add fields as per your requirement)
if user purchased than make an entry premium_enabled - true & also use shared preference for offline check
Ok so I have been trying to fix this for days, and I'm not coming here looking for someone to do my work for me as I have been troubleshooting and fixed every single error message in the LogCat. I am developing an Android game using Andengine (this might be part of the problem so being familiar with it could help). I'm not doing anything too fancy, my game activities are all single scene and don't have any Physics or anything like that, just a bunch of sprites and textures. I also used Andengine for all of the other activities in my game because I find it to be a very easy way to set up graphically appealing screens. One such screen is my in-app store, where users can buy levelpacks and new sprites. The billing part of this all works great, the purchases go through to the Market and there's nothing too complicated there...
When the user clicks buy, the market screen pops up and loads the product they have selected (these are real products, not the android tests although the game is not published). The Market screen pops up over the current activity, regardless of whether I use the "Android 2.0" implementation where it is part of the game's stack or I use the "Android 1.6" implementation and it is part of its own stack. I would prefer to use the Android 2.0 implementation but if I can only get the 1.6 to work I will take that. So anyway, the problem arises when the user either cancels the purchase using the back button or completes the purchase with a credit card, both result in the market screen disappearing and the app starting a new activity that is just a black screen (which eventually times out and causes a force close). The purchase goes through OK but the user does not get the product because the game force quits before we get to the code to change the user's items in the game. Now for some code, I used this tutorial (http://www.anddev.org/advanced-tutorials-f21/simple-inapp-billing-payment-t52060.html) without changing much of anything. The BillingHelper class is most important, as it holds the requestPurchase() method and the startBuyPageActivity() methods. I call request purchase from my StoreFront activity like this:
BillingHelper.requestPurchase(StoreFront.this, itemID);
and in the onCreate of the StoreFront I have this stuff (as told to do by the tut):
startService(new Intent(mContext, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);
...
//some handler that billing needs
public Handler mTransactionHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
if(BillingHelper.latestPurchase.isPurchased()){
//TODO do something here if we've completed our latest purchase,
//this should be with the status bar notifications and
//saved preferences
}
};
};
So I don't think the problem lies there. Here are the relevant parts of BillingHelper
protected static void requestPurchase(Context activityContext, String itemId){
if (amIDead()) {
return;
}
Log.i(TAG, "requestPurchase()");
Bundle request = makeRequestBundle("REQUEST_PURCHASE");
request.putString("ITEM_ID", itemId);
try {
Bundle response = mService.sendBillingRequest(request);
//The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
//The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI
PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");
//The REQUEST_ID key provides you with a unique request identifier for the request
Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);
C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());
startBuyPageActivity(pendingIntent, new Intent(), activityContext);
} catch (RemoteException e) {
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: "+isBillingSupported());
}
}
Which I have tried calling from StoreFront with a variety of arguments as "ActivityContext" such as StoreFront.this, getApplicationContext(), a static context store elsewhere, a static Activity stored elsewhere, getBaseContext() anything I could possible think of...
Here is the other relevant activity
private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){
//android 1.6 method
try {
pendingIntent.send(context, 0, intent);
} catch (CanceledException e){
Log.e(TAG, "startBuyPageActivity CanceledException");
}
}
Nothing fancy, I just want the user to be returned to any of my various activities (preferably StoreFront) when they either buy the item or press back during the process. HELP PLEASE!
Edit: I want any possible solution to allow in-app billing to return to my app after the purchase is complete, even the messiest solution.
EDIT
A logcat and method calls of what the issue:
"BillingService Starting",
BillingHelper.setCompletedHandler(),
StoreFront.onStart() called,
StoreFront.onResume() called,
"BillingService Service starting with onCreate",
"BillingService Market Billing Service Successfully Bound",
"BillingService Market Billing Service Connected",
BillingHelper.instantiateHelper(),
then this is where I actually click the buy button in the store (all of that runs just when opening StoreFront):
BillingHelper.setCompletedHandler(),
BillingHelper.isBillingSupported(),
BillingHelper.amIDead(),
BillingHelper.makeRequestBundle(),
"BillingService isBillingSupported response was: RESULT OK",
BillingHelper.requestPurchase(),
BillingHelper.amIDead(),
"BillingService requestPurchase()",
BillingHelper.makeRequestBundle(),
"BillingService current request is ......",
"BillingService REQUEST PURCHASE Sync Response code: RESULT OK",
BillingHelper.startBuyPageActivity(),
"BillingService Recieved action: com.android.vending.billing.RESPONSE CODE",
"BillingService checkResponseCode got requestID..."
"BillingService checkResponseCode go responseCode RESULT ERROR"
(this is because I can't purchase on this device),
and then I get an Error message saying: "E 32427 Surface surface (identity=5925) is invalid, err=-19 (No such device)" and from there nothing works anymore.
Also I have tested this on a different phone (another developer I am working with, who can actually buy things in it but still gets the black screen error) and he never got the Handler messages you mentioned in your comment either
Edit: if I had to guess where the error is, I'd say it's this
06-16 11:20:23.635: DEBUG/dalvikvm(3807): GC_EXPLICIT freed 53K, 45% free 3710K/6663K, external 1K/513K, paused 102ms
06-16 11:20:23.885: ERROR/Surface(3807): surface (identity=158) is invalid, err=-19 (No such device)
06-16 11:20:23.905: ERROR/Surface(3807): surface (identity=158) is invalid, err=-19 (No such device)
06-16 11:20:23.905: ERROR/Surface(3807): surface (identity=158) is invalid, err=-19 (No such device)
06-16 11:20:23.905: ERROR/Adreno200-EGL(3807): egliSwapWindowSurface: unable to dequeue native buffer
Note that the interrupted exception is expected by the Andengine library so that is a red herring.
Also (I hope this is allowed on here) I will offer paypal reward for a solution. If this is against the Terms of SO then just delete this line, please don't close this question.
I may know what's wrong and I have a test for you to do. The buy screen runs a finish call after the user cancels the purchase or completes the purchase. For me for some reason the finish call was drifting down into the currently running activity and (CLOSING??? it).
Here's the relevant line from the log:
06-16 11:20:22.774: WARN/ActivityManager(132): Duplicate finish request for HistoryRecord{40ace828 com.android.vending/.billing.InAppBuyPageActivity}
I think I fixed this in my code problem is that I can't remember exactly what I did (maybe didn't call finish in my purchase complete handler...)
I don't know anything about Andgen, but what would happen if a finish call got called on the main Andgen activity? I'd imagine it would stop execution and you might get a black screen and an app crash.
So to test this, create a separate activity for you buy page. Doesn't need to be complicated - maybe have it just buy one canned product after it starts up. Run your code and see if it still gives you the black screen of doom. My bet: it may exit out of the activity back to your game but I think it'll work.
Hope this helps and Good Luck!