It seems that it is quite complicated to implement In-App Billing in an Android app. How could I do this? The sample app from the SDK only has one Activity, which kind of ov
There is a full example of Android In-App Billing v3 step by step is given here with screenshot. Please check the tutorial: Android In-App Billing v3 using ServiceConnection Class
Hope it will help.
For more clarification, go through this tutorial: Implementing In-app Billing in Version 3 API
Steps to follow to Integrate In-app Billing library in our project
Update your AndroidManifest.xml file.
Create a ServiceConnection and bind it to IInAppBillingService.
Send In-app Billing requests from your application to IInAppBillingService.
Handle In-app Billing responses from Google Play.
Update AndroidManifest.xml
Add the permissions in Manifest.xml file
Build your application. You should see a generated file named IInAppBillingService.java in the /gen directory of your project.
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.inducesmile.androidinapppurchase"
minSdkVersion 14
targetSdkVersion 24
versionCode 2
versionName "1.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.intuit.sdp:sdp-android:1.0.3'
compile 'com.android.support:support-annotations:24.1.1'
compile 'org.jetbrains:annotations-java5:15.0'
}
InAppPurchaseActivity.java and activity_in_app_purchase.xml
This is where will offer our app users the opportunity to to make in-app purchase. In the layout file, we will give the user the opportunity to make purchase in different denominations.
InAppPurchaseActivity.java
Note: getAllUserPurchase() and itemPurchaseAvailability() methods should be called in non UI Thread to avoid app crashing.
public class InAppPurchaseActivity extends AppCompatActivity {
private static final String TAG = InAppPurchaseActivity.class.getSimpleName();
private IInAppBillingService mService;
private CustomSharedPreference customSharedPreference;
String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID};
private ImageView buyOneButton, buyTwoButton, buyThreeButton;
private static final char[] symbols = new char[36];
static {
for (int idx = 0; idx < 10; ++idx)
symbols[idx] = (char) ('0' + idx);
for (int idx = 10; idx < 36; ++idx)
symbols[idx] = (char) ('a' + idx - 10);
}
private String appPackageName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_app_purchase);
appPackageName = this.getPackageName();
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this);
buyOneButton = (ImageView)findViewById(R.id.buy_one);
buyOneButton.setVisibility(View.GONE);
assert buyOneButton != null;
buyOneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_ONE_ID);
}
});
buyTwoButton = (ImageView)findViewById(R.id.buy_two);
buyTwoButton.setVisibility(View.GONE);
assert buyTwoButton != null;
buyTwoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_TWO_ID);
}
});
buyThreeButton = (ImageView)findViewById(R.id.buy_three);
buyThreeButton.setVisibility(View.GONE);
assert buyThreeButton != null;
buyThreeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_THREE_ID);
}
});
}
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);
AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName);
mAsyncTask.execute();
}
};
private void purchaseItem(String sku){
String generatedPayload = getPayLoad();
customSharedPreference.setDeveloperPayLoad(generatedPayload);
try {
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload);
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
try {
startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Helper.RESPONSE_CODE) {
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 purchaseJsonObject = new JSONObject(purchaseData);
String sku = purchaseJsonObject.getString("productId");
String developerPayload = purchaseJsonObject.getString("developerPayload");
String purchaseToken = purchaseJsonObject.getString("purchaseToken");
//the developerPayload value is better stored in remote database but in this tutorial
//we will use a shared preference
for(int i = 0; i < productIds.length; i++){
if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){
customSharedPreference.setPurchaseToken(purchaseToken);
//access to private content
Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class);
startActivity(contentIntent);
}
}
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
}
private String getPayLoad(){
RandomString randomString = new RandomString(36);
String payload = randomString.nextString();
return payload;
}
public class RandomString {
private final Random random = new Random();
private final char[] buf;
public RandomString(int length) {
if (length < 1)
throw new IllegalArgumentException("length < 1: " + length);
buf = new char[length];
}
public String nextString() {
for (int idx = 0; idx < buf.length; ++idx)
buf[idx] = symbols[random.nextInt(symbols.length)];
return new String(buf);
}
}
public final class SessionIdentifierGenerator {
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(130, random).toString(32);
}
}
private class AvailablePurchaseAsyncTask extends AsyncTask {
String packageName;
public AvailablePurchaseAsyncTask(String packageName){
this.packageName = packageName;
}
@Override
protected Bundle doInBackground(Void... voids) {
ArrayList skuList = new ArrayList();
skuList.add(Helper.ITEM_ONE_ID);
skuList.add(Helper.ITEM_TWO_ID);
skuList.add(Helper.ITEM_THREE_ID);
Bundle query = new Bundle();
query.putStringArrayList(Helper.ITEM_ID_LIST, skuList);
Bundle skuDetails = null;
try {
skuDetails = mService.getSkuDetails(3, packageName, "inapp", query);
} catch (RemoteException e) {
e.printStackTrace();
}
return skuDetails;
}
@Override
protected void onPostExecute(Bundle skuDetails) {
List canPurchase = new ArrayList();
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
ArrayList responseList = skuDetails.getStringArrayList("DETAILS_LIST");
if(responseList != null){
for (String thisResponse : responseList) {
JSONObject object = null;
try {
object = new JSONObject(thisResponse);
String sku = object.getString("productId");
String price = object.getString("price");
canPurchase.add(new AvailablePurchase(sku, price));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){
buyOneButton.setVisibility(View.VISIBLE);
}else{
buyOneButton.setVisibility(View.GONE);
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){
buyTwoButton.setVisibility(View.VISIBLE);
}else{
buyTwoButton.setVisibility(View.GONE);
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){
buyThreeButton.setVisibility(View.VISIBLE);
}else{
buyThreeButton.setVisibility(View.GONE);
}
}
}
@org.jetbrains.annotations.Contract("null, _ -> false")
private boolean checkIfPurchaseIsAvailable(List all, String productId){
if(all == null){ return false;}
for(int i = 0; i < all.size(); i++){
if(all.get(i).getSku().equals(productId)){
return true;
}
}
return false;
}
public boolean isBillingSupported(){
int response = 1;
try {
response = mService.isBillingSupported(3, getPackageName(), "inapp");
} catch (RemoteException e) {
e.printStackTrace();
}
if(response > 0){
return false;
}
return true;
}
public void consumePurchaseItem(String purchaseToken){
try {
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
if(response != 0){
return;
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
public Bundle getAllUserPurchase(){
Bundle ownedItems = null;
try {
ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
} catch (RemoteException e) {
e.printStackTrace();
}
return ownedItems;
}
public List extractAllUserPurchase(Bundle ownedItems){
List mUserItems = new ArrayList();
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
ArrayList purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");
if(purchaseDataList != null){
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
assert signatureList != null;
String signature = signatureList.get(i);
assert ownedSkus != null;
String sku = ownedSkus.get(i);
UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature);
mUserItems.add(allItems);
}
}
}
return mUserItems;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mService != null) {
unbindService(mServiceConn);
}
}
}
Create Helper Package Directory
Create a new package folder and name it helpers. Inside the package, create a new java file Helper.java.
Helper.java
public class Helper {
public static final String ITEM_ID_LIST = "ITEM_ID_LIST";
public static final String ITEM_ONE_ID = "productone";
public static final String ITEM_TWO_ID = "producttwo";
public static final String ITEM_THREE_ID = "productthree";
public static final int RESPONSE_CODE = 1001;
public static final String SHARED_PREF = "shared_pref";
public static final String DEVELOPER_PAYLOAD = "developer_payload";
public static final String PURCHASE_TOKEN = "purchase_token";
public static void displayMessage(Context context, String message){
Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
}
Testing In-App Billing Purchase
Errors You might encounter during In-App purchase testing
the item you requested is not available for purchase
Solution – According to AndreiBogdan in Stackoverflow,
All credit goes to Inducesmile for his tutorial
Android Developer Blog also recommends a training class on Selling In-app Products. To see a complete implementation and learn how to test the application, Please check this tutorial: Selling In-app Products