5

I set up the "Dungeons" InAppBilling example locally and I am ready to try it out, but I am a bit confused. I have a button like this:

Button donate = (Button)findViewById(R.id.donate);     
donate.setOnClickListener(new Button.OnClickListener() {  
        public void onClick(View v) {     
        // But what do I do here? :)
        }
});

And when it is called, what do I need to do to actually go to the pay screen on android store?

Thanks!

Siddharth
  • 9,349
  • 16
  • 86
  • 148
GeekedOut
  • 16,905
  • 37
  • 107
  • 185
  • read this documentation: http://developer.android.com/guide/google/play/billing/index.html – ariefbayu Jun 26 '12 at 03:20
  • @ariefbayu hi I read it, and I have a bunch of pieces of code in place, but I am not sure how to actually pull it off – GeekedOut Jun 26 '12 at 03:21
  • I got the system to go to the checkout page, but there is an unexpected crash that I bring up here. http://stackoverflow.com/questions/11286656/android-in-app-payments-crashing-during-checkout – GeekedOut Jul 02 '12 at 00:19

5 Answers5

4

I better suggest you to use this code as this example is quiet simple and easy to handle at first .. the things you have to do is

http://blog.blundell-apps.com/simple-inapp-billing-payment/

  1. download the sample project code from the above link (Having Description of code and download link at bottom)
  2. in your android project where you want to implement in app billing, create package com.android.vending.billing and place IMarketBillingService.aidl (you can find this and all files mention below in the project you downloaded in step 1)
  3. place following utility files in any package and correct import statements accordingly.

          * BillingHelper.java
          * BillingReceiver.java
          * BillingSecurity.java
          * BillingService.java
          * C.java
    
  4. place the public key (you can find it in developer console in the bottom section of edit profile) in the BillingSecurity.java in line saying String base64EncodedPublicKey = "your public key here"

  5. Declare the following permission (outside the application tag), service and receiver (Inside the application tag) in your manifest like shown below(can also see manifest which is along the code for reference)

     //outside the application tag 
     <uses-permission android:name="com.android.vending.BILLING" />
    
     // Inside the application tag
     <service android:name=".BillingService" />
    
    <receiver android:name=".BillingReceiver">
        <intent-filter>
            <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
            <action android:name="com.android.vending.billing.RESPONSE_CODE" />
            <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />            
        </intent-filter>
    </receiver> 
    
  6. place the following code as mentioned with there places in your activity where purchase is being held.

    //at the starting of your onCreate()
    startService(new Intent(mContext, BillingService.class));
    BillingHelper.setCompletedHandler(mTransactionHandler);
    
    //outside onCreate() Within class
     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()){
                //code here which is to be performed after successful purchase
            }
        };
    
     };
    
     //code to initiate a purchase... can be placed in onClickListener etc
      if(BillingHelper.isBillingSupported()){
            BillingHelper.requestPurchase(mContext, "android.test.purchased"); 
            // where android.test.purchased is test id for fake purchase, when you create products through developer console you can set a code to pass the id(which is given on developer console while creating a product) of the item which is selected for purchase to intiate purchase of that item.
        } else {
            Log.i(TAG,"Can't purchase on this device");
             // Do Anything Heer to show user that purchase not possible on this device
        }
    

Note: To do a test purchase you need to put the public key in BillingSecurity.java as mentioned above secondly you need to upload the apk to the developer console(you can leave it uupublished and unactive) and thirdly you need a real android device(emulator wouldn't work) having updated play store app.

Note: the account needed for in app purchase and described in all above discussion is not just simple publisher account its publisher account embedded with google merchant wallet account. The details can be found in link below.

http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113468

Umar Qureshi
  • 5,985
  • 2
  • 30
  • 40
  • I am not certain what is the BillingHelper class. Where does that come from? Also, I am still not sure what exact code to place inside my purchase button listener. Thanks for your help. – GeekedOut Jun 26 '12 at 11:34
  • BillingHelper is custom class designed to assist in app purchase you can found it in the link as mentioned in link at bottom there is download link for project which contains this class. i also tried to follow the example at developers site but its quiet confusing for a person not familiar with complete structure thats why i suggested you what i have tried. i have written complete instructions to follow that easy example i doesn't answerd about the code for button listener but guided you to a much easier way that i used and successfully implemented – Umar Qureshi Jun 26 '12 at 17:35
2

There are 2 ways

  • Maintain a database on your server end, and create a table for user+purchased list of products+expiry date
  • Or the client app to save a encrypted code on shared preferences (so that it cant be hacked easily)

Android API's dont provide you with any inventory apis to maintain your purchases.

I used the 2nd option.

Siddharth
  • 9,349
  • 16
  • 86
  • 148
  • thank you - those are what I was thinking but was wondering if there are other means to do this. That helped. Would you know also what code to put into my button listener there in my original question to send the user to the store with the correct purchase intent? – GeekedOut Jun 26 '12 at 10:52
  • I have answered a similar question before, refer to it. Hope you find your answer. http://stackoverflow.com/questions/11193754/debug-android-inapp-billing-using-eclipse/11196437#11196437 – Siddharth Jun 26 '12 at 10:57
2

-- single product purchase store.purchase( { "android.test.purchased" } )

-- multi-item purchase
store.purchase( { "android.test.purchased", "android.test.canceled" } )

in reference to Getting Started with Android In-app Billing

this code maybe the one you are looking for

Alexander Ho
  • 503
  • 2
  • 7
  • 18
  • thanks - how do I get the store object to do the store.purchase call? and what is the "android.test.canceled" ? Thanks :) – GeekedOut Jun 26 '12 at 10:50
2

1)download

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download

2)add

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-add-aidl

3)add permission in your android manifest file

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-permission

now your project should look like this...

enter image description here

4) place the public key (you can find it in developer console in the bottom section of edit profile) in the Security.java in line saying String base64EncodedPublicKey = "your public key here"

5) and finally your activity which have button should be look like this

public class YourActivity extends Activity implements OnClickListener { String issueProductId = "Your Product ID";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.updates);
    SetInAppBilling();
    Button donate = (Button) findViewById(R.id.donate);
    donate.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            if (mBillingService.requestPurchase(issueProductId, null)) {

            } else {
                showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                Log.i("tag", "Can't purchase on this device");

            }

        }
    });
}


public void register() {
    ResponseHandler.register(mDungeonsPurchaseObserver);
}

public void unregister() {
    ResponseHandler.unregister(mDungeonsPurchaseObserver);
}

public void close_unbind() {
    if (mPurchaseDatabase != null)
        // mPurchaseDatabase.close();
        if (mBillingService != null)
            mBillingService.unbind();
    // stopService(new Intent(this, BillingService.class));
}

/**
 * Called when this activity becomes visible.
 */
@Override
protected void onStart() {
    super.onStart();

    register();
}

/**
 * Called when this activity is no longer visible.
 */
@Override
protected void onStop() {
    unregister();
    super.onStop();

}

@Override
protected void onDestroy() {
    close_unbind();
    super.onDestroy();

}

private static final String TAG = "YourActivity";

private static final String DB_INITIALIZED = "db_initialized";

// private static final String Dir_Check = "Dir_Check";

private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
private Handler mHandler;

private BillingService mBillingService;
private PurchaseDatabase mPurchaseDatabase;
private static final int DIALOG_CANNOT_CONNECT_ID = 1;
private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
private Cursor mOwnedItemsCursor;

public void SetInAppBilling() {
    mHandler = new Handler();
    mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
    mBillingService = new BillingService();
    mBillingService.setContext(this);

    mPurchaseDatabase = new PurchaseDatabase(this);

    mOwnedItemsCursor = mPurchaseDatabase
            .queryAllPurchasedHistroyTabelItems();
    startManagingCursor(mOwnedItemsCursor);

    SharedPreferences prefs = getPreferences(MODE_PRIVATE);
    boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
    // Check if billing is supported.
    ResponseHandler.register(mDungeonsPurchaseObserver);
    if (!mBillingService.checkBillingSupported()) {
        showDialog(DIALOG_CANNOT_CONNECT_ID);
    }
}

private class DungeonsPurchaseObserver extends PurchaseObserver {
    public DungeonsPurchaseObserver(Handler handler) {
        super(YourActiviy.this, handler);
    }

    @Override
    public void onBillingSupported(boolean supported) {

        Log.i(TAG, "supportedCheck: " + supported);
        if (Consts.DEBUG) {
            Log.i(TAG, "supported: " + supported);
        }
        if (supported) {
            restoreDatabase();
        } else {
            showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
        }
    }

    @Override
    public void onPurchaseStateChange(PurchaseState purchaseState,
            String itemId, int quantity, long purchaseTime,
            String developerPayload) {
        if (Consts.DEBUG) {
            Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " "
                    + purchaseState);
        }

        if (developerPayload == null) {

        } else {

        }

        Log.e(TAG, "onPurchaseStateChangeCheck: " + "onPurchaseStateChange");
        if (purchaseState == PurchaseState.PURCHASED) {

            /** TODO: */
            Toast.makeText(
                    mContext,
                    "You successfully upgraded to the entire Volume One. Enjoy!",
                    Toast.LENGTH_SHORT).show();
            finish();
        }

    }

    @Override
    public void onRequestPurchaseResponse(RequestPurchase request,
            ResponseCode responseCode) {
        if (Consts.DEBUG) {
            Log.d(TAG, request.mProductId + ": " + responseCode);
        }
        if (responseCode == ResponseCode.RESULT_OK) {
            if (Consts.DEBUG) {
                Log.i(TAG, "purchase was successfully sent to server");
            }

        } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
            if (Consts.DEBUG) {
                Log.i(TAG, "user canceled purchase");
            }

        } else {
            if (Consts.DEBUG) {
                Log.i(TAG, "purchase failed");
            }

        }
    }

    @Override
    public void onRestoreTransactionsResponse(RestoreTransactions request,
            ResponseCode responseCode) {
        if (responseCode == ResponseCode.RESULT_OK) {
            if (Consts.DEBUG) {
                Log.d(TAG, "completed RestoreTransactions request");
            }
            // Update the shared preferences so that we don't perform
            // a RestoreTransactions again.

            SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
            SharedPreferences.Editor edit = prefs.edit();
            edit.putBoolean(DB_INITIALIZED, true);
            edit.commit();

            mOwnedItemsCursor = mPurchaseDatabase
                    .queryAllPurchasedHistroyTabelItems();
            Log.d(TAG, String.valueOf(mOwnedItemsCursor.getCount()));
            startManagingCursor(mOwnedItemsCursor);

            if (mOwnedItemsCursor.getCount() > 0) {
                Log.d(TAG, "Updating the DB");
                Toast.makeText(
                        mContext,
                        "You successfully upgraded to the entire Volume One. Enjoy!",
                        Toast.LENGTH_SHORT).show();
                finish();
            }

        } else {
            if (Consts.DEBUG) {
                Log.d(TAG, "RestoreTransactions error: " + responseCode);
            }
        }
    }
}

@Override
protected Dialog onCreateDialog(int id) {
    switch (id) {
    case DIALOG_CANNOT_CONNECT_ID:
        return createDialog(R.string.cannot_connect_title,
                R.string.cannot_connect_message);
    case DIALOG_BILLING_NOT_SUPPORTED_ID:
        return createDialog(R.string.billing_not_supported_title,
                R.string.billing_not_supported_message);
    default:
        return null;
    }
}

private Dialog createDialog(int titleId, int messageId) {
    String helpUrl = replaceLanguageAndRegion(getString(R.string.help_url));
    if (Consts.DEBUG) {
        Log.i(TAG, helpUrl);
    }
    final Uri helpUri = Uri.parse(helpUrl);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(titleId)
            .setIcon(android.R.drawable.stat_sys_warning)
            .setMessage(messageId)
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, null)
            .setNegativeButton(R.string.learn_more,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Intent intent = new Intent(Intent.ACTION_VIEW,
                                    helpUri);
                            startActivity(intent);
                        }
                    });
    return builder.create();
}

/**
 * Replaces the language and/or country of the device into the given string.
 * The pattern "%lang%" will be replaced by the device's language code and
 * the pattern "%region%" will be replaced with the device's country code.
 * 
 * @param str
 *            the string to replace the language/country within
 * @return a string containing the local language and region codes
 */
private String replaceLanguageAndRegion(String str) {
    // Substitute language and or region if present in string
    if (str.contains("%lang%") || str.contains("%region%")) {
        Locale locale = Locale.getDefault();
        str = str.replace("%lang%", locale.getLanguage().toLowerCase());
        str = str.replace("%region%", locale.getCountry().toLowerCase());
    }
    return str;
}

private void restoreDatabase() {
    SharedPreferences prefs = getPreferences(MODE_PRIVATE);
    boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
    if (!initialized) {
        mBillingService.restoreTransactions();
        // Toast.makeText(this, "restoring...", Toast.LENGTH_LONG).show();

    }
}

}

Mohsin Naeem
  • 12,542
  • 3
  • 39
  • 53
  • @M Mohsin Naeem You gave great advice. I have one problem I can't fix right now. On this line: mBillingService.requestPurchase(issueProductIdDonate, null) I get a compile error that The method requestPurchase(String, String, String) in the type BillingService is not applicable for the arguments (String, null) – GeekedOut Jul 01 '12 at 19:27
  • In the implementation of this method, I deleted the @overrides string right above it - I wasn't sure if I needed it. – GeekedOut Jul 01 '12 at 19:28
  • 1
    @Override is required. You need to write this. The error you are facing should not be come. Make sure you are using the latest code from the Goole play.And also check in 'BillingService.java' that you have this piece of code or not. 'public boolean requestPurchase(String productId, String developerPayload) { return new RequestPurchase(productId, developerPayload).runRequest();' } if you still have problem then share you code. – Mohsin Naeem Jul 02 '12 at 06:13
2

The sample in-app billing code from Google is a good start. I have posted a link to a tutorial I gave which is three parts. This link is the first of three. I hope it helps.

http://www.mobileoped.com/2012/04/06/android-google-play-in-app-billing-part-1/

cloudm2
  • 21
  • 2
  • this is a great tutoria by the way. I am going through it now and like your writing style. One thing I am confused with is the spot where the user clicks "purchase" is a seperate activity from where you have the code for handler = new Handler(); ....appPurchaseObserver = new AppPurchaseObserver(handler); ....or its in the same activity? I thought it happens in a mouse clicked event when the user clicks puechase. – GeekedOut Jul 01 '12 at 16:45