Is there any example available on how to use RESTORE_TRANSACTIONS request to restore an In-app product purchase information? I came up with this code, but it always returns 0, so it doesn't recognize if the product is purchased or not: Everything is set up correctly.
Bundle request = BillingHelper.makeRequestBundle("RESTORE_TRANSACTIONS");
request.putLong("NONCE", 32436756l);
try
{
Bundle response = BillingHelper.mService.sendBillingRequest(request);
int response_code = response.getInt("RESPONSE_CODE", -1);
if (response_code == 0)
{
// Product purchased
}
}
catch (RemoteException e)
{
e.printStackTrace();
}
I found no examples on google and in the documentation, so any guidance would be great.
I used this method:
public static void restoreTransactionInformation(Long nonce)
{
if (amIDead())
{
return;
}
Log.i(TAG, "confirmTransaction()");
Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
// The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
request.putLong("NONCE", nonce);
try
{
Bundle response = mService.sendBillingRequest(request);
//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);
//The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
}
catch (RemoteException e)
{
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}
, The call is as follows:
BillingHelper.restoreTransactionInformation(BillingSecurity.generateNonce());
Related
Can someone please point a link where i can easily understand how to use GCM for device to device notification. What is required to make device as server and receiver.
Let me explain my requirement , you guys might have a better solution to my problem
I want to send a notification(pre defined message) to one or group of people on a button click, the other user can do the same.
Thanks
Let me try to explain the basic working
You need- A server and two mobile phones
Step 1 You have a server that both the phones connect to.
Step 2 You go to Google Developers page at this link
Step 3 You create a new project there and then add a GCM API to your project. Save the sender_id of the project. Also check the credentials of the API and add a new browser key and save that key.
Step 4 Add GCM to your project. For that, goto the root directory where your Eclipse is installed. Then navigate to the folder extras/google/google-play-services/libsproject/ and then copy the folder that is there to another location. Then import that folder into your workspace in Eclipse and change its properties to make it a library. Then add this to your app project.
Step 5 You need to register your mobile with GCM. For that you can work with a sample code like this
// to start process
public void registerDevice() {
// TODO Auto-generated method stub
try {
if (checkPlayServices()) {
regid = getRegistrationId();
if (regid.isEmpty()) { // creating a new reg id
registerInBackground();
} else
saveid();
} else{
}
} catch (Exception e) {
}
}
// to create a new id
private void registerInBackground() {
// TODO Auto-generated method stub
try {
AsyncRegister logMeIn = new AsyncRegister("<you project id here>", context);
logMeIn.doneWith = this;
logMeIn.execute();
System.out.println("execute complete");
} catch (Exception e) {
}
}
// to fetch stored registration id
private String getRegistrationId() {
// TODO Auto-generated method stub
try {
int i=myPrefs.getInt("cuid", 0);
if(id!=i){
myPrefs.edit().putInt("cuid", id).commit();
int registeredVersion = myPrefs.getInt("appversion",
Integer.MIN_VALUE); // to check if app is updated
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
myPrefs.edit().putInt("appversion",currentVersion).commit();
}
return "";
}
String registrationId = myPrefs.getString("regid", "");
if (registrationId.isEmpty()) {
return "";
}
int registeredVersion = myPrefs.getInt("appversion",
Integer.MIN_VALUE); // to check if app is updated
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
myPrefs.edit().putInt("appversion", currentVersion).commit();
return "";
}
// when id is stored previously then this is called
return registrationId;
} catch (Exception e) {
}
}
//to fetch the current app version
private int getAppVersion() throws NameNotFoundException {
// TODO Auto-generated method stub
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return packageInfo.versionCode;
}
// to check if play services are there on the device
private boolean checkPlayServices() {
// TODO Auto-generated method stub
try {
int resultCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
toSend = new Bundle();
//tell user google play needs to be updated
} else {
//tell user their system does not support GCM
}
myPrefs.edit().putBoolean("support", false).commit();
return false;
}
myPrefs.edit().putBoolean("support", true).commit();
return true;
} catch (Exception e) {
}
}
Step 6 Save the registration ID and pass it to your server to save it.
Step 7 When user clicks the send button, pass a message to your server. The server will pull out the list of ids stored with the DB and will then push the message to GCM along with those ids.
Step 8 Now GCM will send that message to all the phones whose ids were given to it.
Step 9 Add a receiver in your app that receives the text from GCM and then displays it to the user.
This is a brief explanation. If you want to understand this in details please read online tutorials. Else you can also text me on my group (link you will find in my profile description) and i will gladly help you out.
String postData = "{ \"registration_ids\": [ \"" + OtherDeviceToken+ "\" ], " +
"\"delay_while_idle\": true, " +
"\"data\": {\"tickerText\":\"Your title\", "+
"\"contentTitle\":\"AppName\", " +
"\"message\": \" " + edtYourMessage.getText().toString() + "\"}}";
MediaType JSON= MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, postData);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://android.googleapis.com/gcm/send")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "key=" + SERVER_API_KEY)
.post(body)
.build();
Execute the okkhttp request.
I want to enable server-side Calendar API access for my android app.
I have followed the steps given here .
I am getting a null value for the authorization code.
I think I am giving wrong values for 'scope' field and the 'server_client_id' field.
Please give me an example showing correct fields values for 'scope' and 'server_client_id' in the getToken() method.
Thanks for any help.
PS- I have used google+ sign in for android given here for connecting to a google account.
EDIT- Here is my code. I have given the OAuth 2.0 scope for the Google Calendar API in the scope field.
I have taken Client ID for Android application from Developers Console and put in 'serverClientID' field. This is probably where I am wrong. I don't know how to get Server's Client ID which is required by the
public class AsyncGetAuthToken extends AsyncTask<String, Void, String>{
#Override
protected String doInBackground(String... params) {
Bundle appActivities = new Bundle();
appActivities.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
"MainActivity");
String scopeString = "https://www.googleapis.com/auth/calendar.readonly";
String serverClientID = CLIENT_ID;
String scopes = "oauth2:server:client_id:" + serverClientID + ":api_scope:" + scopeString;
String code = null;
try {
code = GoogleAuthUtil.getToken(
MainActivity.this, // Context context
Plus.AccountApi.getAccountName(mGoogleApiClient), // String accountName
scopes, // String scope
appActivities // Bundle bundle
);
} catch (IOException transientEx) {
// network or server error, the call is expected to succeed if you try again later.
// Don't attempt to call again immediately - the request is likely to
// fail, you'll hit quotas or back-off.
return null;
} catch (UserRecoverableAuthException e) {
// Requesting an authorization code will always throw
// UserRecoverableAuthException on the first call to GoogleAuthUtil.getToken
// because the user must consent to offline access to their data. After
// consent is granted control is returned to your activity in onActivityResult
// and the second call to GoogleAuthUtil.getToken will succeed.
startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST_CODE);
return null;
} catch (GoogleAuthException authEx) {
// Failure. The call is not expected to ever succeed so it should not be
// retried.
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
}
And in my onActivityResult, I look for the Auth Code
if (requestCode == AUTH_CODE_REQUEST_CODE) {
if (responseCode == RESULT_OK){
Bundle extra = intent.getExtras();
String oneTimeToken = extra.getString("authtoken");
Log.d("LOG", "one time token" + oneTimeToken);
}
}
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.
Hey folks,
I am slightly modified the dungeons example for the Android In-App-Billing SDK article. I am having trouble with the RESTORE_TRANSACTIONS request. I first make a legitimate purchase and that goes fine, I get a call to onPurchaseStateChange with no issue. However, when I try to use RESTORE_TRANSACTIONS request, I was expecting to get a constructed verified list of purchases, but when I trace it, the JSON returned is verified just fine, but contains no transactions! Within Security.java in the Dungeons example you see this code in the verifyPurchase method:
if (Consts.DEBUG) {
Log.d(TAG, "Parsing JSON object");
}
JSONObject jObject;
JSONArray jTransactionsArray = null;
int numTransactions = 0;
long nonce = 0L;
try {
jObject = new JSONObject(signedData);
// The nonce might be null if the user backed out of the buy page.
nonce = jObject.optLong("nonce");
jTransactionsArray = jObject.optJSONArray("orders");
if (jTransactionsArray != null) {
numTransactions = jTransactionsArray.length();
}
if (Consts.DEBUG) {
Log.d(TAG, "JSON Array has " + numTransactions + " transactions");
}
} catch (JSONException e) {
if (Consts.DEBUG) {
Log.d(TAG, e.getMessage());
}
return null;
}
Am I misunderstanding the purpose of the RESTORE_TRANSACTIONS? Isn't it supposed to return a verified list of purchases just like REQUEST_PURCHASE through the onPurchaseStateChange?
Nevermind, I have to look much closer. The RESTORE_TRANSACTIONS is meant to be used with MANAGED product ids. I was purchasing an UNMANAGED item.
http://developer.android.com/guide/market/billing/billing_admin.html#billing-purchase-type