Good day.I am implementing in-app purchase into android.The only items which shall be bough are coins.And by idea coins can be bought over and over.But as we know the google play in-app purchase keeps the item as purchased,and you can buy it only after consumption.So what is an best practice for this situation?Like i want to let user buy same coin pack over and over.What is the code for it?
This is the code i am using for in-app purchase flow.
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
#Override
public void onServiceConnected(ComponentName name,
IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
try {
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
Log.d("Fasfsafasfasfa", "onServiceConnected: "+ownedItems);
int response = ownedItems.getInt("RESPONSE_CODE");
Log.d("Fasfsafasfasfa", "onServiceConnected: "+response);
if (response == 0) {
ArrayList<String> ownedSkus =
ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
ArrayList<String> purchaseDataList =
ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
ArrayList<String> signatureList =
ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
String continuationToken =
ownedItems.getString("INAPP_CONTINUATION_TOKEN");
Log.d("Fasfsafasfasfa", "onServiceConnected: "+ownedSkus.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+purchaseDataList.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+signatureList.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+continuationToken);
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
// do something with this purchase information
// e.g. display the updated list of products owned by user
Log.d("Fasfsafasfasfa", "onServiceConnected: " + "purchased items are" + sku + " " + signature + " " + purchaseData);
}
// if continuationToken != null, call getPurchases again
// and pass in the token to retrieve more items
}
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
"small_pack", "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
} catch (RemoteException e) {
e.printStackTrace();
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
};
I have noticed this issue because on very first purchase everything is going fine,and whenever i try to buy same sku named item again,i get the fatal exception like this
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.IntentSender android.app.PendingIntent.getIntentSender()' on a null object reference
at ink.va.activities.BuyCoins$1.onServiceConnected(BuyCoins.java:103)
Which i can reckon means that i must first consume purchase and only after that again let user buy it..Am i right?Anyway who have a suggestion of this issue please?
Consume your item to make it available to be purchased again:
int response = mService.consumePurchase(3, getPackageName(), token);
Warning: Do not call the consumePurchase method on the main thread. Calling this method triggers a network request which could block your main thread. Instead, create a separate thread and call the consumePurchase method from inside that thread.
Consumable products can be purchased multiple times by same the user.
Non-consumable can be purchased only once.
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 couldn't find a way to test InApp Subscription with the test product ID by google i.e. private final String productID = "android.test.purchased"; // Test Product ID by Google
In the docs it is not written anywhere that InAPP subscription couldn't be tested with test product nor it is mentioned anywhere,how to test InApp subscription.
I have implemented my code following docs(InAppV3).
The doc says:
Implementing Subscriptions:
Launching a purchase flow for a subscription is similar to launching the purchase flow for a product, with the exception that the product type must be set to "subs". The purchase result is delivered to your Activity's onActivityResult method, exactly as in the case of in-app products.
and I have also implemented that properly.
My app is working if I replace "inapp" with "subs",i.e. it is working perfectly for products and not for subscriptions.
When I change "inapp" to "subs" then the purchase is returning:
09-24 14:01:12.943: I/(16929): isBillingSupported() - success : return 0
09-24 14:01:12.943: D/Finsky(2598): [281] InAppBillingUtils.getPreferredAccount: com.kgandroid.inappsubscriptiondemo: Account from first account - [MOn42QuZgF98vxJi0p3wAN3rfzQ]
09-24 14:01:12.943: I/(16929): getPurchases() - success return Bundle
09-24 14:01:12.943: I/(16929): getPurchases() - "RESPONSE_CODE" return 0
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_PURCHASE_ITEM_LIST" return []
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_PURCHASE_DATA_LIST" return []
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_DATA_SIGNATURE" return null
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_CONTINUATION_TOKEN" return null
As you can see no details for android.test.purchased is returning.The test inapp purchase dialog is also not opening.
The relevant purchase code(Though it is not related to the problem I guess):
void purchase()
{
if (!blnBind) return;
if (mService == null) return;
ArrayList<String> skuList = new ArrayList<String>();
skuList.add(productID);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
Bundle skuDetails;
try {
skuDetails = mService.getSkuDetails(3, getPackageName(), "subs", querySkus);
System.out.println(skuDetails);
Toast.makeText(context, "getSkuDetails() - success return Bundle", Toast.LENGTH_SHORT).show();
Log.i(tag, "getSkuDetails() - success return Bundle");
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(context, "getSkuDetails() - fail!", Toast.LENGTH_SHORT).show();
Log.w(tag, "getSkuDetails() - fail!");
return;
}
int response = skuDetails.getInt("RESPONSE_CODE");
Toast.makeText(context, "getSkuDetails() - \"RESPONSE_CODE\" return " + String.valueOf(response), Toast.LENGTH_SHORT).show();
Log.i(tag, "getSkuDetails() - \"RESPONSE_CODE\" return " + String.valueOf(response));
if (response != 0) return;
ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\" return " + responseList.toString());
if (responseList.size() == 0) return;
for (String thisResponse : responseList) {
try {
JSONObject object = new JSONObject(thisResponse);
String sku = object.getString("productId");
String title = object.getString("title");
String price = object.getString("price");
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"productId\" return " + sku);
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"title\" return " + title);
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"price\" return " + price);
if (!sku.equals(productID)) continue;
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "subs", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
Toast.makeText(context, "getBuyIntent() - success return Bundle", Toast.LENGTH_SHORT).show();
Log.i(tag, "getBuyIntent() - success return Bundle");
response = buyIntentBundle.getInt("RESPONSE_CODE");
//Toast.makeText(context, "getBuyIntent() - \"RESPONSE_CODE\" return " + String.valueOf(response), Toast.LENGTH_SHORT).show();
Log.i(tag, "getBuyIntent() - \"RESPONSE_CODE\" return " + String.valueOf(response));
if (response != 0) continue;
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), 0, 0, 0);
} catch (JSONException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
//Toast.makeText(context, "getSkuDetails() - fail!", Toast.LENGTH_SHORT).show();
Log.w(tag, "getBuyIntent() - fail!");
} catch (SendIntentException e) {
e.printStackTrace();
}
}
}
Does subscription supports test purchases??
If not,how to test subscription??
If yes,why google is returning null??
Any related docs or links will be also helpful.
I've never tried testing subs using the Test Product ID ("android.test.purchased") and I'm not sure it would work as seems to be a Product ID not a Subscription ID. I use real subscription IDs (that I create on Google Play Developer Console), but I use an account other than my developer account (i.e. the one you use to publish the app).
If you go to https://play.google.com/apps/publish/ then click on Settings, you'll see a field called "Gmail accounts with testing access". You can type one or more gmail addresses for accounts with which you want to test your purchases. Any account added there that purchases a subscription will not be charged and subscriptions will last for 24 hours. (source: http://developer.android.com/google/play/billing/billing_testing.html and my own experience)
That works for me. Although I did seem to have found a bug: Trying to cancel an Android test subscription gives me a 500 response code
Good luck with your app!
Edit:
From Google Docs (http://developer.android.com/google/play/billing/billing_testing.html#billing-testing-test)
You cannot use your developer account to test the complete in-app
purchase process because Google payments does not let you buy items
from yourself.
Have you tried using another account?
I am a novice Android developer who has never used in-app billing before. So I have followed the steps outlined in Google's own documentation at http://developer.android.com/google/play/billing/billing_integrate.html.
I added the IInAppBillingService.aidl file under main/aidl/com.android.vending.billing and my structure matches countless screenshots I've seen online.
I added code very similar to the sample code to a part of my app that interacts with my online marketplace. A simplified version of that code, with extraneous stuff removed, is:
imports...
import android.content.ServiceConnection;
import com.android.vending.billing.IInAppBillingService;
public class intMarket extends Activity {
IInAppBillingService mService;
public static final String SHARED_PREFS_NAME="myAppSettings";
public static String purchaseToken = "";
ServiceConnection mServiceConn = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
#Override
public void onServiceConnected(ComponentName name,
IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intbrowser);
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
String tUrl = getString(R.string.urlMarket);
SharedPreferences settings = getSharedPreferences(SHARED_PREFS_NAME, 0);
myWebView.loadUrl(tUrl);
}
#Override
public void onDestroy() {
super.onDestroy();
if (mService != null) {
unbindService(mServiceConn);
}
}
#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");
purchaseToken = jo.getString("purchaseToken");
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
}
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
#JavascriptInterface
public void purchaseItem(Integer itemID, String itemName, BigDecimal itemPrice) {
// Credit Purchase
ArrayList<String> skuList = new ArrayList<String> ();
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
String purchaseCreditsResponse = "Success";
try {
Bundle skuDetails = mService.(3, getPackageName(), "inapp", querySkus);
Log.d("WPS", skuDetails.toString());
int response = skuDetails.getInt("RESPONSE_CODE");
Log.d("WPS", Integer.toString(response));
if (response == 0) {
ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
String thisItemID = "0";
for (String thisResponse : responseList) {
JSONObject object = new JSONObject(thisResponse);
String itemPrice = object.getString("price");
if (itemPrice.equals(itemPrice.toString()))
thisItemID = object.getString("productId");
}
if (Integer.parseInt(thisItemID) > 0) {
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), thisItemID, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
if (buyIntentBundle.getInt("RESPONSE_CODE") == 0) {
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
//startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
// purchaseToken
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Buying: " + itemName, Toast.LENGTH_LONG).show();
}
} else {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 001.", Toast.LENGTH_LONG).show();
}
} else {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 002.", Toast.LENGTH_LONG).show();
}
} catch (RemoteException e) {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 003.", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 004.", Toast.LENGTH_LONG).show();
}// catch (IntentSender.SendIntentException e) {
// purchaseCreditsResponse = "Failure";
// Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 005.", Toast.LENGTH_LONG).show();
//}
// Purchase Report
// Report purchase and get response into purchaseResponse
// Todo
// Credit Consumption
// Todo
}
}
}
I then compiled a version of this and uploaded the APK to Google Play as an alpha test case and published it. I have been making updates and using ADB to install the new compiled APKs to my phone, but I continually get the following from this code in LogCat:
03-23 15:07:55.325 2958-3009/? D/WPS﹕ Bundle[mParcelledData.dataSize=48]
03-23 15:07:55.325 2958-3009/? D/WPS﹕ 5
Which, of course, indicates: BILLING_RESPONSE_RESULT_DEVELOPER_ERROR
I read in another SO post that you cannot test billing on a device whose main account is the same as the developer's account. So I spent several hours yesterday tracking down another phone and getting it setup using a completely separate account (and the only account on the device). I uploaded a new build to Google Play as an alpha test and published it. I waited for this other phone to get the new version and tried testing billing again. The phone reported this error message that I had built in:
Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 002.
Which indicates the same error I was running into before. This implies that there is a problem with my code, which would not surprise me.
I have not done anything with the big giant license code that appears in the Google Play developer panel because the above referenced documentation says nothing about it being required. But is it? If not, what about my code is causing this problem?
Verify that com.android.vending.BILLING is added to your manifest permissions.
Also verify that your app is signed with your production key and the version on Google Play should be the same as the version you are testing with.
Also have a look at this lightweight, straight forward library you can use on Github below:
https://github.com/anjlab/android-inapp-billing-v3
People comment that this is a bug in Google's billing API (maybe Google considers it a feature?).
Seems like you have to consume the purchaseToken, just as the guy here does:
String purchaseToken = o.optString("token", o.optString("purchaseToken"));
// Consume purchaseToken, handling any errors
mService.consumePurchase(3, getPackageName(), purchaseToken);
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.