I'm currently migrating IAB v2 to v3.
In v2, if you owned specific item that you try to buy, Google Play dialog shows "you already own this item" with red text color. In v3, i can get error response 5 (Item Already Owned) but Google Play dialog never show up to screen.
I'm using latest sample code IabHelper and it seems like returning with not starting intent if response is not 0.
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null)
listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
Is it possible to behavior same as v2? or should i create dialog saying with "already owned" in my App?
Is it possible to behavior same as v2? or should i create dialog saying with "already owned" in my App?
Yes, this issue has been reported and an update IAB v3 repository is available here.
The crash you mention is fixed with this repo.
Note:
As reported by Bruno Oliveira.
Related
Hi i am implementing InApp purchase in my android application For this i am using IInAppBillingService. Implementation is done, but now i am facing a problem when user update or
downgrade the subscription.The problem is suppose if user have a active monthly subscription now in the mid of the month if he update the subscription to yearly what i want is to use setReplaceSkusProrationMode of billing library so that the cost will manage automatically means remaning balance of 15 days will get adjusted in new yearly subscription.
How to get this when using IInAppBillingService.The code which i am using for launching purchase is as below please help me i am lost.
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused
* while the user interacts with Google Play, and the result will be delivered via the
* activity's {#link android.app.Activity#onActivityResult} method, at which point you must call
* this object's {#link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* #param act The calling activity.
* #param sku The sku of the item to purchase.
* #param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or
* ITEM_TYPE_SUBS)
* #param oldSkus A list of SKUs which the new SKU is replacing or null if there are none
* #param requestCode A request code (to differentiate from other responses -- as in
* {#link android.app.Activity#startActivityForResult}).
* #param listener The listener to notify when the purchase process finishes
* #param extraData Extra data (developer payload), which will be returned with the purchase
* data when the purchase completes. This extra data will be permanently bound to that
* purchase and will always be returned when the purchase is queried.
*/
public void launchPurchaseFlow(Activity act, String sku, String itemType, List<String> oldSkus,
int requestCode, OnIabPurchaseFinishedListener listener, String extraData)
throws IabAsyncInProgressException {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle;
if (oldSkus == null || oldSkus.isEmpty()) {
// Purchasing a new item or subscription re-signup
buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType,
extraData);
} else {
// Subscription upgrade/downgrade
if (!mSubscriptionUpdateSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE,
"Subscription updates are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
buyIntentBundle = mService.getBuyIntentToReplaceSkus(5, mContext.getPackageName(),
oldSkus, sku, itemType, extraData);
}
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
There are two ways to implement in app purchases in an android app:
The AIDL which is the one that provides the mService that you use.
The Google Play Billing Library, for which you have to add the following dependency in app build.gradle implementation 'com.android.billingclient:billing:2.0.3'
It seems you are trying to call a Billing Library method from the AIDL service and of course that is not possible.
The AIDL way is now deprecated and should not be used in new projects.
That is, you should modify what you have already done to use the billing library and then you can call the methods that you now find missing.
I am implementing the new billing API 2.0 which has this purchase acknowledge method.
Earlier i was using AIDL for my purchase and i had following use case is that i used to pass developer payload during initiating purchase and used to get back my developer payload in the response as a part of purchase object.
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
MY_SKU, "subs", developerPayload);
PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
// Start purchase flow (this brings up the Google Play UI).
// Result will be delivered through onActivityResult().
startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}
and in my on on activity result i used to get purchase object
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1001) {
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
if (resultCode == RESULT_OK) {
try {
JSONObject jo = new JSONObject(purchaseData);
String sku = jo.getString("productId");
alert("You have bought the " + sku + ". Excellent choice,
adventurer!");
}
catch (JSONException e) {
alert("Failed to parse purchase data.");
e.printStackTrace();
}
}
}
}
This purchase object has a developer payload which i send to the server for verification.
Now in the new API the developer payload can be added only after purchase is completed or when we consume a purchase, so the problem is after purchase is acknowledged i need the updated purchase object but how to get it?
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(/* my payload */)
.build();
client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
In acknowledgePurchaseResponseListener i get only the response code whether it is success or failure. But i need to updated purchase object object with developerPayload and isAcknowledged flag true.
Is there a way to do so? Could not find anything in documentation.
The local cache of purchases is updated by the time your acknowledgePurchaseResponseListener is called so you can query the purchase from the cache using https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#querypurchases. We will consider adding the update purchase to the listener for a future library release to make this more convenient.
I know this question is everywhere in Stack-overflow and there are many answers to this question but I am unable to resolve it. I have tried many answers but still not able to solve the issue, I know that I am doing some silly mistake somewhere in my code, can anyone help me out finding the issue?
Here is My screen-shot for in-app products :-
Declared permissions inside Manifest :-
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
Defining SKU as :-
public static final String SKU1 = "gas";
public static final String SKU2 = "infinite_gas";
Purchase Method :-
public void launchPurchaseFlow(Activity act, String sku, String itemType,
int requestCode, OnIabPurchaseFinishedListener listener,
String extraData) {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null)
listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: "
+ itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3,
mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: "
+ getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null)
listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle
.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: "
+ requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(), Integer.valueOf(0),
Integer.valueOf(0), Integer.valueOf(0));
} catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku "
+ sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED,
"Failed to send intent.");
if (listener != null)
listener.onIabPurchaseFinished(result, null);
} catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku "
+ sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION,
"Remote exception while starting purchase flow");
if (listener != null)
listener.onIabPurchaseFinished(result, null);
}
}
Bind Service Intent :-
Intent serviceIntent = new Intent(
"com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0)
.isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn,
Context.BIND_AUTO_CREATE);
Consume Method :-
void consume(Purchase itemInfo) throws IabException {
checkNotDisposed();
checkSetupDone("consume");
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
"Items of type '" + itemInfo.mItemType
+ "' can't be consumed.");
}
try {
String token = itemInfo.getToken();
String sku = itemInfo.getSku();
if (token == null || token.equals("")) {
logError("Can't consume " + sku + ". No token.");
throw new IabException(IABHELPER_MISSING_TOKEN,
"PurchaseInfo is missing token for sku: " + sku + " "
+ itemInfo);
}
logDebug("Consuming sku: " + sku + ", token: " + token);
int response = mService.consumePurchase(3,
mContext.getPackageName(), token);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successfully consumed sku: " + sku);
} else {
logDebug("Error consuming consuming sku " + sku + ". "
+ getResponseDesc(response));
throw new IabException(response, "Error consuming sku " + sku);
}
} catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION,
"Remote exception while consuming. PurchaseInfo: "
+ itemInfo, e);
}
}
Error while purchasing :-
Note :- The Application in Alpha testing phase is published.
Do i need to Approve my tester account from somewhere ?
Here are the whole process i do :-
Uploaded the signed apk with release certificated to developer
console.
I have published my apk to alpha channel.
I have listed my product Ids to developer console.
I have activated my product Ids and on developer console it is marked as Active.
I have listed the test account in developer console.
I have installed the same apk that I uploaded to developer console to my deveice.
The device is logged in with the test account not the developer account.
The Id that I use in my app is same as I had listed on console as per logcat message.
Any help will be greatly appreciated
Thanks in advance.
yes , i solved the problem , i was missing with this last and very important step :-
Open opt-in url with test account and click on "Become a tester"
I am working on Android Product which supports in app purchase and have large quantity of users doing in app purchases , out of all users some users are facing "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED" error and the response returned is 7 .
I am calling consume call after every purchase being awarded , and also verifying my inventory when the InApp is setup on the start of application and check for any owned item and call the consume on it.
Any recommendation's to solve the issue .
See here. Google says that you MUST consume managed inapp items. If you don't, the user cannot purchase another one. After successful purchase, use:
int response = mService.consumePurchase(3, getPackageName(), token);
You get the token from the purchaseData JSON object:
final String token = jo.getString("purchaseToken");
But if you for some reason did not consume a purchase, you are stuck.
I ran into the same problem because I upgraded to iap api v3 and in v2 this was not a problem.
When you get this error, try to consume all purchases of the given productId. Or just consume everything purchased during setup of the service. Note that you might want to actually provision the purchase after consumePurchase() successfully returns depending on the semantics of your application.
Call the following code after the service is connected (in onServiceConnected()) and of course execute it in background:
String continuationToken="";
boolean hasMorePurchases=false;
do {
try {
Bundle purchases = mService.getPurchases(3, activity.getPackageName(), "inapp", continuationToken);
int response = purchases.getInt("RESPONSE_CODE");
if (response == 0) {
continuationToken = purchases.getString("INAPP_CONTINUATION_TOKEN");
if(!TextUtils.isEmpty(continuationToken)) hasMorePurchases=true;
final ArrayList<String> purchaseDataList = purchases.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for(String purchaseJSON : purchaseDataList) {
JSONObject object = new JSONObject(purchaseJSON);
String productId = object.getString("productId");
String orderId = object.getString("orderId");
String purchaseToken = object.getString("purchaseToken");
Log.i(getClass().getSimpleName(),"consuming purchase of " + productId + ", orderId " + orderId);
mService.consumePurchase(3, activity.getPackageName(), purchaseToken);
}
} else {
Log.e(getClass().getSimpleName(), "could not get purchases: " + response);
}
} catch (RemoteException e) {
Log.e(getClass().getSimpleName(), "RemoteException during getPurchases:", e);
} catch (JSONException e) {
Log.e(getClass().getSimpleName(), "JSONException during getSkuDetails:", e);
}
} while(hasMorePurchases);
Note that each call to getPurchases returns a maximum of 700 purchases, so you need to use the continuation token to get more.
You can of course just use this code if you get the ALREADY_OWNED error code and only for the productId involved. Afterwards, try start the purchase again.
I am trying to implement in app purchase in my android application. I am using In app billing API v3 for implementing IAP (in app purchase). For reference I am following Google provided trivialdrive sample. In launchPurchaseFlow method
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
In the above code getBuyIntent does not expect nonce, we have extradata in which we pass developerpayload (specific to each purchase item).
Here I am not able to figure out how to pass nonce as it was passed in API V2 and received as response on successful purchase. Is there no need of nonce in V3?
Thanks
When using IAB API v3, simply use the original JSON response you receive from Google Play and perform an OpenSSL verification on it, using the signature and public RSA.
It's best that you perform this check through your own API, as bundling the public RSA key inside your application is unsafe. Here's a PHP sample for this OpenSSL verification.
As that sample illustrates, it simply takes the parameters from the request: the response data and the signature and verifies it using the public key.
You only need to tamper with the original JSON data inside your app and on your API for purposes related to figuring out what the user bought, not for verifying the validity of the purchase itself.
So in short: don't worry about the nonce. IAB API v3 dropped it and you shouldn't concern yourself with its absence.
APIv3 returns orderId, which can be used as nonce to avoid replay attacks.