Note that there are some explanatory texts on larger screens.

plurals
  1. POIn-App Billing: Inventory not correct; makes user purchase again
    primarykey
    data
    text
    <p>Edit*** still looking for an answer to my original question. Why won't Android see the item has been bought previously instead of making the user pay again? Setting SharedPreferences is an OK idea, but what if the user uninstalls? They would have to buy again. I do not want this for the users. Thank you.</p> <p>I tried to figure out why the app does not inventory correctly, I took this from TrivialDrive and tried to make it my own. I believe I removed all the consume and I only have one item to purchase; a premium upgrade. This should be bought only once and never charged twice. When I compile and run the app on my tablet I pass the purchase process OK and all seems well, until I use the task manager to close the app and re-open it. Once it's re-opened it asks to purchase premium again instead of passing that and performing the premium function.</p> <p>Here is the code:</p> <pre><code>public class myClass extends SherlockActivity { private DrawerLayout mDrawerLayout; private ListView mDrawerList; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; private String[] mPlanetTitles; public static final int DIALOG_DOWNLOAD_PROGRESS = 0; ProgressBar pd = null; private ProgressDialog mProgressDialog; Spinner spLoadFrom; WebView wv; private LinearLayout container; private Button nextButton, closeButton; private EditText findBox; private static final String TAG = "Web"; IabHelper mHelper; static boolean mIsPremium = false; boolean mIsUserPremium = false; boolean searchAllowed = false; static final String PREM_SKU = "prem"; private ArrayAdapter&lt;CharSequence&gt; spinnerArrayAdapter; String name_free[] = { }; String displayName_free[] = { }; String name_premium[] = { }; String displayName_premium[] = {}; /** Called when the activity is first created. */ @SuppressLint("NewApi") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.mylayout); mTitle = mDrawerTitle = getTitle(); mPlanetTitles = getResources().getStringArray( R.array.fMenu); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); // set a custom shadow that overlays the main content when the drawer // opens mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); // set up the drawer's list view with items and click listener mDrawerList.setAdapter(new ArrayAdapter&lt;String&gt;(this, R.layout.drawer_list_item, mPlanetTitles)); mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); // enable ActionBar app icon to behave as action to toggle nav drawer getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); // ActionBarDrawerToggle ties together the the proper interactions // between the sliding drawer and the action bar app icon mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description for accessibility */ R.string.drawer_close /* "close drawer" description for accessibility */ ) { public void onDrawerClosed(View view) { getSupportActionBar().setTitle(mTitle); supportInvalidateOptionsMenu(); // creates call to // onPrepareOptionsMenu() } public void onDrawerOpened(View drawerView) { getSupportActionBar().setTitle(mDrawerTitle); supportInvalidateOptionsMenu(); // creates call to // onPrepareOptionsMenu() } }; mDrawerLayout.setDrawerListener(mDrawerToggle); if (savedInstanceState == null) { selectItem(0); } mProgressDialog = new ProgressDialog(this); mProgressDialog.setIndeterminate(false); mProgressDialog.setMax(100); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); wv = (WebView) findViewById(R.id.webview); pd = (ProgressBar) findViewById(R.id.pBar); spLoadFrom = (Spinner) findViewById(R.id.Spinner02); if (mIsPremium == true) { spinnerArrayAdapter = new ArrayAdapter&lt;CharSequence&gt;(this, android.R.layout.simple_spinner_item, displayName_premium); } else { spinnerArrayAdapter = new ArrayAdapter&lt;CharSequence&gt;(this, android.R.layout.simple_spinner_item, displayName_free); } spinnerArrayAdapter .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spLoadFrom.setAdapter(spinnerArrayAdapter); SpinnerListener spListener = new SpinnerListener(); spLoadFrom.setOnItemSelectedListener(spListener); String base64EncodedPublicKey = "hidden"; mHelper = new IabHelper(this, base64EncodedPublicKey); Log.d(TAG, "Starting setup."); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { @Override public void onIabSetupFinished(IabResult result) { Log.d(TAG, "Setup finished."); if (!result.isSuccess()) { // Oh noes, there was a problem. complain("Problem setting up in-app billing: " + result); return; } // Hooray, IAB is fully set up. Now, let's get an inventory of // stuff we own. Log.d(TAG, "Setup successful. Querying inventory."); mHelper.queryInventoryAsync(mGotInventoryListener); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getSupportMenuInflater().inflate(R.menu.menu3, menu); return super.onCreateOptionsMenu(menu); } /* Called whenever we call invalidateOptionsMenu() */ @Override public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content // view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(final MenuItem item) { if (mDrawerToggle.onOptionsItemSelected(getMenuItem(item))) { return true; } int itemId = item.getItemId(); if (itemId == R.id.search) { if (searchAllowed == false) { } else if (itemId == R.id.contact) { emailme(); } else if (itemId == R.id.rate) { Uri uri = Uri.parse("market://details?id=" + getPackageName()); Intent myAppLinkToMarket = new Intent(Intent.ACTION_VIEW, uri); try { startActivity(myAppLinkToMarket); } catch (ActivityNotFoundException e) { Toast.makeText(this, "Unable to find Play Market", Toast.LENGTH_SHORT).show(); } } return super.onOptionsItemSelected(item); } public void buyPrem() { Log.d(TAG, "Upgrade button clicked; launching purchase flow for upgrade."); /* * TODO: for security, generate your payload here for verification. See * the comments on verifyDeveloperPayload() for more info. Since this is * a SAMPLE, we just use an empty string, but on a production app you * should carefully generate this. */ String payload = ""; mHelper.launchPurchaseFlow(this, PREM_SKU, 10001, mPurchaseFinishedListener, payload); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); // Pass on the activity result to the helper for handling if (!mHelper.handleActivityResult(requestCode, resultCode, data)) { // not handled, so handle it ourselves (here's where you'd // perform any handling of activity results not related to in-app // billing... super.onActivityResult(requestCode, resultCode, data); } else { Log.d(TAG, "onActivityResult handled by IABUtil."); } } IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { @Override public void onIabPurchaseFinished(IabResult result, Purchase purchase) { Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase); if (result.isFailure()) { complain("Error purchasing: " + result); // Handle error return; } if (!verifyDeveloperPayload(purchase)) { complain("Error purchasing. Authenticity verification failed."); return; } Log.d(TAG, "Purchase successful."); if (purchase.getSku().equals(PREM_SKU)) { // bought the premium upgrade! Log.d(TAG, "Purchase is premium upgrade. Congratulating user."); alert("Thank you for upgrading to premium!"); mIsPremium = true; mIsUserPremium = true; searchAllowed = true; } } }; IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inventory) { Log.d(TAG, "Query inventory finished."); if (result.isFailure()) { complain("Failed to query inventory: " + result); return; } /*if (inventory.hasPurchase(PREM_SKU)) { mHelper.consumeAsync(inventory.getPurchase(PREM_SKU), null); }*/ Log.d(TAG, "Query inventory was successful."); Purchase premiumPurchase = inventory.getPurchase(PREM_SKU); mIsPremium = (premiumPurchase != null &amp;&amp; verifyDeveloperPayload(premiumPurchase)); Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM")); if (mIsPremium) { searchAllowed = true; mIsUserPremium = true; Log.d(TAG, "Should be premium by now..."); } Log.d(TAG, "Initial inventory query finished; enabling main UI."); } }; boolean verifyDeveloperPayload(Purchase p) { String payload = p.getDeveloperPayload(); /* * TODO: verify that the developer payload of the purchase is correct. * It will be the same one that you sent when initiating the purchase. * * WARNING: Locally generating a random string when starting a purchase * and verifying it here might seem like a good approach, but this will * fail in the case where the user purchases an item on one device and * then uses your app on a different device, because on the other device * you will not have access to the random string you originally * generated. * * So a good developer payload has these characteristics: * * 1. If two different users purchase an item, the payload is different * between them, so that one user's purchase can't be replayed to * another user. * * 2. The payload must be such that you can verify it even when the app * wasn't the one who initiated the purchase flow (so that items * purchased by the user on one device work on other devices owned by * the user). * * Using your own server to store and verify developer payloads across * app installations is recommended. */ return true; } /*public void consumeItem() { mHelper.queryInventoryAsync(mReceivedInventoryListener); }*/ IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener = new IabHelper.QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { // Handle failure } else { searchAllowed = false; //mHelper.consumeAsync(inventory.getPurchase(PREM_SKU), // mConsumeFinishedListener); } } }; IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() { @Override public void onConsumeFinished(Purchase purchase, IabResult result) { if (result.isSuccess()) { } else { // handle error } } }; @Override public void onDestroy() { super.onDestroy(); if (mHelper != null) mHelper.dispose(); mHelper = null; } void complain(String message) { Log.e(TAG, "**** TrivialDrive Error: " + message); alert("Error: " + message); } void alert(String message) { AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(message); bld.setNeutralButton("OK", null); Log.d(TAG, "Showing alert dialog: " + message); bld.create().show(); } private android.view.MenuItem getMenuItem(final MenuItem item) { return new android.view.MenuItem() { @Override public int getItemId() { return item.getItemId(); } public boolean isEnabled() { return true; } @Override public boolean collapseActionView() { // TODO Auto-generated method stub return false; } @Override public boolean expandActionView() { // TODO Auto-generated method stub return false; } @Override public ActionProvider getActionProvider() { // TODO Auto-generated method stub return null; } @Override public View getActionView() { // TODO Auto-generated method stub return null; } @Override public char getAlphabeticShortcut() { // TODO Auto-generated method stub return 0; } @Override public int getGroupId() { // TODO Auto-generated method stub return 0; } @Override public Drawable getIcon() { // TODO Auto-generated method stub return null; } @Override public Intent getIntent() { // TODO Auto-generated method stub return null; } @Override public ContextMenuInfo getMenuInfo() { // TODO Auto-generated method stub return null; } @Override public char getNumericShortcut() { // TODO Auto-generated method stub return 0; } @Override public int getOrder() { // TODO Auto-generated method stub return 0; } @Override public SubMenu getSubMenu() { // TODO Auto-generated method stub return null; } @Override public CharSequence getTitle() { // TODO Auto-generated method stub return null; } @Override public CharSequence getTitleCondensed() { // TODO Auto-generated method stub return null; } @Override public boolean hasSubMenu() { // TODO Auto-generated method stub return false; } @Override public boolean isActionViewExpanded() { // TODO Auto-generated method stub return false; } @Override public boolean isCheckable() { // TODO Auto-generated method stub return false; } @Override public boolean isChecked() { // TODO Auto-generated method stub return false; } @Override public boolean isVisible() { // TODO Auto-generated method stub return false; } @Override public android.view.MenuItem setActionProvider( ActionProvider actionProvider) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setActionView(View view) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setActionView(int resId) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setAlphabeticShortcut(char alphaChar) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setCheckable(boolean checkable) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setChecked(boolean checked) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setEnabled(boolean enabled) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setIcon(Drawable icon) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setIcon(int iconRes) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setIntent(Intent intent) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setNumericShortcut(char numericChar) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setOnActionExpandListener( OnActionExpandListener listener) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setOnMenuItemClickListener( OnMenuItemClickListener menuItemClickListener) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setShortcut(char numericChar, char alphaChar) { // TODO Auto-generated method stub return null; } @Override public void setShowAsAction(int actionEnum) { // TODO Auto-generated method stub } @Override public android.view.MenuItem setShowAsActionFlags(int actionEnum) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setTitle(CharSequence title) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setTitle(int title) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setTitleCondensed(CharSequence title) { // TODO Auto-generated method stub return null; } @Override public android.view.MenuItem setVisible(boolean visible) { // TODO Auto-generated method stub return null; } }; } private class DrawerItemClickListener implements ListView.OnItemClickListener { public void onItemClick(AdapterView&lt;?&gt; parent, View view, int position, long id) { switch (position) { case 0: mDrawerLayout.closeDrawer(mDrawerList); break; case 1: Intent c1 = new Intent(getBaseContext(), 1.class); startActivity(c1); break; case 2: Intent c2 = new Intent(getBaseContext(), 5.class); startActivity(c2); break; case 3: Intent c3 = new Intent(getBaseContext(), 3.class); startActivity(c3); break; case 4: Intent c4 = new Intent(getBaseContext(), 4.class); startActivity(c4); break; default: } } } public void selectItem(int position) { switch (position) { case 0: break; case 1: setContentView(R.layout.1); break; case 2: setContentView(R.layout.2); break; case 3: setContentView(R.layout.3); break; case 4: setContentView(R.layout.4); break; default: } } @Override public void setTitle(CharSequence title) { mTitle = title; getSupportActionBar().setTitle(mTitle); } /** * When using the ActionBarDrawerToggle, you must call it during * onPostCreate() and onConfigurationChanged()... */ @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass any configuration change to the drawer toggls mDrawerToggle.onConfigurationChanged(newConfig); } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload