Android Tutorial Android InApp Purchase Tutorial + Source Code

Hi,

in this tutorial i will show you how to add an in app product to your android app.

There is an official tutorial by @Erel so you should also check that out: https://b4x.com/android/forum/threads/android-in-app-billing-v3-tutorial.29997/

Step 1:

First we need to create an in_app product in our developer console (google play)
but to do that we need to upload an .apk that include InAppBilling3 lib.
so just create an app and check that lib and upload it to the dev console (without adding any code)
after your dev console will recognize you have uploaded an apk that include the in_app library it will allow you to create an in_app product.

Step2:

when you add a new in_app product you can choose between a "Managed Product" and "Subscription"

a "Managed Product" is a product that the user purchase only once like: remove ads, skip level, coins,..

a "Subscription" is a product that will charge the user every x period like: news letter subscription,...

in this tutorial we will create a "Managed Product" (no_Ads).

So create your Product and then copy your In_App ID that you can find here (write also the Product ID down, you will need it later):

inapp.jpg


Step 3: (coding)

Ok now we are ready for some code but first you need to know that your app will probably crash on an emulator so you should test it on a real device.

i will explain how i do it in my apps (but it doesn't mean that this is the only (or the right :rolleyes:) way)

in android on every app start (if firsttime = true) we check if user has purchased one of our products.

First we will intialize the "manager" and this will automaticly call the sub "manager_BillingSupported" and check if billing is supported.
if billing is supported we will ask for a list of all owned products by that user and this is done in sub "manager_OwnedProducts".

then we will check if the "no_ads" product is included in that list (map).

if "no_ads" product has been purchased then we should turn off ads, right?

but what happens when the user purchase the "no_ads" product and the "BillingSupported" request fail to run on app create?
(lets say the user had no internet connection at that moment)

if adtimer will run on every app start that means that ads will be shown even if he has purchased the "no_ads" product.

so the user could be very mad if he see ads after he has purchased the "no_ads" product, so what should we do?

the timer (that request ad's) is in default enabled on every app_create (start on every app start).so how can we avoid a situation where the user see ads even if he has purchased the "no_ads: product?

what i do is, i create a txt file called "ad.txt" after user has purchased the "no_ads" product and then on every app start i will check for that file. if it exists then i disable the adtimer until i am able to check again if user has purchased the "no_ads" product. (on next app start)

you will probably ask now why should i check on every app start if the user has purchased the "no_ads" product? lets create that file on "app purchase" and if file.exist ("ad.txt") then skip the "Get_ownedproducts" process, right?

well that is wrong, because if he purchase the product you create that file and then if he cancel his purchase that file will still exists and no ads will be shown. so the "ad.txt" file just change the default settings of the adtimer from "enable/disable on every activity_create"

ok enough talking, everything should be clear enough from the code bellow. if you have questions don't hesitate to ask and if you have a better solution you are welcome to post it here.

regards, ilan :)

Activity Code:

B4X:
#Region  Project Attributes
    #ApplicationLabel: InApp Example
    #VersionCode: 1
    #VersionName: 1.0
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: portrait
    #CanInstallToExternalStorage: False
    #AdditionalRes: C:\Android SDK\extras\google\google_play_services\libproject\google-play-services_lib\res, com.google.android.gms
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: false
#End Region

Sub Process_Globals
    Dim adtimer As Timer
    Dim manager As BillingManager3
    Private inappkey As String = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiIEJEB/JNzDSn8YoWUE/dq7/q8HDUjUL5np0tSmk3pdauYO2rRMvDNxXO4e+FqgYhYjswBKdFeIlzLVF8+y2J49G85eaznP8ZYbt0vexH6lpk0iJxrfOxeGo8BY3IvUaCm0PXwdM9gos+XwZo14lr+pFSfpR/qd7PzXBqtdZ9NxvAyAnlLGrpzHuTApRCp8EIMc1kYZHd/pGtTcRyLuCGxFW0cbsrpKUIMHzDe1S/pp7bRqs3tMjagySWx0Kzic0ufxxa6v/XzbBDQPeSpyIcZLFxNuDBrZElu83cC39JZz1kKiGSfcMmsz/eegJo/AdII8CVkJjY5ov6CG64i+cPQIDAQAB"
End Sub

Sub Globals
    Dim b As Button
    Dim mwAdInterstitial As mwAdmobInterstitial 'interstitial ad (admob)
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.Color = Colors.White

    adtimer.Initialize("adtimer",25000) '25sec to show ad

    b.Initialize("Button")
    b.Text = "No Ads"
    b.Color = Colors.DarkGray
    b.TextColor = Colors.White

    Activity.AddView(b,20dip,20dip,150dip,50dip)

    mwAdInterstitial.Initialize("mwadi","pub-8081096xxxxxxxxxxxxxxxxxxx")
    mwAdInterstitial.LoadAd

    If FirstTime Then
        manager.Initialize("manager", inappkey)
        manager.DebugLogging = True
    End If

    If File.Exists(File.DirInternal,"ad.txt") Then adtimer.Enabled = False 'change default ad support to off
End Sub

Sub Button_Click
    manager.RequestPayment("remove_ads","inapp","remove_ads") 'request purchase with ID "remove_ads"
End Sub

Sub manager_BillingSupported (Supported As Boolean, Message As String) 'First we check if Billing is supported
   Log(Supported & ", " & Message)
   Log("Subscriptions supported: " & manager.SubscriptionsSupported)
   If Supported Then manager.GetOwnedProducts 'if support true then get owend product by this user
End Sub

Sub manager_OwnedProducts (Success As Boolean, purchases As Map)
   Log(Success)
   Dim purchased_noAds_from_store As Boolean = False  'set a boolean to false and if item with specific ID was purchased change to true

   If Success Then
      Log(purchases)
      For Each p As Purchase In purchases.Values
         Log(p.ProductId & ", Purchased? " & (p.PurchaseState = p.STATE_PURCHASED))
         If p.ProductId = "remove_ads" And p.PurchaseState = p.STATE_PURCHASED Then 'check if item NoAds was purchased
            purchased_noAds_from_store = True 'set boolean to true
            Purchased_NoAds(True)
        End If
      Next

      If purchased_noAds_from_store = False Then Purchased_NoAds(False) 'delete file and start timer
   End If
End Sub

Sub manager_PurchaseCompleted (Success As Boolean, Product As Purchase)
    Log(Product)
    Log(Success)

    If Success Then
        If Product.ProductId = "remove_ads" Then Purchased_NoAds(True)
    End If
End Sub

Sub Purchased_NoAds(purchased As Boolean)
    If purchased Then
        adtimer.Enabled = False 'stop timer if was on
        If File.Exists(File.DirInternal,"ad.txt") = False Then  'create no ad file
           Dim writer1 As TextWriter
           writer1.Initialize(File.OpenOutput(File.DirInternal,"ad.txt", False))
           writer1.WriteLine("1")
           writer1.Close                
        End If
    Else
        If File.Exists(File.DirInternal,"ad.txt") Then File.Delete(File.DirInternal,"ad.txt") 'delete file
        adtimer.Enabled = True  'start timer
    End If
End Sub

Sub adtimer_tick
    If mwAdInterstitial.Status=mwAdInterstitial.Status_AdReadyToShow Then
         adtimer.Interval = 240000 'change the time between ads (so people will also see your app and not only ads ;))
         mwAdInterstitial.Show
    Else
        mwAdInterstitial.LoadAd
        adtimer.Interval = 15000 'if fail - load again and show in 15 sec
    End If
End Sub

Sub mwadi_AdLoaded
    Log("ad loaded")
End Sub

Sub mwadi_AdFailedToLoad (ErrorMessage As String)
    Log("failed to load ad: " & ErrorMessage)
End Sub

Sub mwadi_AdClosed '*******************
    Log("ad closed")
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub
 

Attachments

  • InApp.zip
    3.2 KB · Views: 914
Last edited:

sorex

Expert
Licensed User
Longtime User
When or by what is Sub manager_OwnedProducts supposed to be triggered?
 

ilan

Expert
Licensed User
Longtime User
When you intialize manager (at app_create)
Sub manager_billingsupported will be called and if it will be true then manager_ownedproducts will be called.
 

sorex

Expert
Licensed User
Longtime User
I guess I'll need to publish the app as alpha version first as it doesn't list my in-app option in the log.
 

ilan

Expert
Licensed User
Longtime User
As i wrote above in step 1. You need to upload first an apk that include the inappbilling lib to be able to create an in app product in your dev console.

No need to be an alpha and also you dont need to include any code only the lib.
 

sorex

Expert
Licensed User
Longtime User
Well, I did upload my apk before I could add the in-app option. maybe it needs some time.

I also read somewhere that it will only work when installed via the store and not over usb/bridge.
 

ilan

Expert
Licensed User
Longtime User
I also read somewhere that it will only work when installed via the store and not over usb/bridge.

It will work also if you install your app via usb/bridge
You only need to wait until your app was updated in the store. Actually everytime you upload a new build you will need to wait because you are running a newer version on your phone (since you installed it via usb or bridge) and the in app product is from an older version.
So need to wait...
 

sorex

Expert
Licensed User
Longtime User
the app was published for testing but still nothing in the logs exept this

Billing service connected.
Checking for in-app billing 3 support.
In-app billing version 3 supported for com.sorex.test
Subscriptions AVAILABLE.
true, Setup successful. (response: 0:OK)
Subscriptions supported: true

a log line in the

Sub manager_OwnedProducts (Success As Boolean, purchases As Map)

sub doesn't get executed either.
 

sorex

Expert
Licensed User
Longtime User
wait a second... didn't someone write in the other tutorial thread that it doesn't work when using the same google account as the one used for publishing the app?

Edit: I added the call to purchase and that gives "The publisher cannot purchase this item." :confused:
 

ilan

Expert
Licensed User
Longtime User
where are you calling: manager.GetOwnedProducts

wait a second... didn't someone write in the other tutorial thread that it doesn't work when using the same google account as the one used for publishing the app?

Edit: I added the call to purchase and that gives "The publisher cannot purchase this item." :confused:

thats true you cannot purchase your own products but you can login as another user and try

but if you get the msg: The publisher cannot purchase this item that means it is working
 

ilan

Expert
Licensed User
Longtime User
there is only one more last step todo

Step 4:

open a new bank account in Switzerland and ask for a big safe that will be able to store all your $$$ ;)
 

sorex

Expert
Licensed User
Longtime User
where are you calling: manager.GetOwnedProducts

sorry about that one, it appears that the if/then line that had this call was lost somehow.

now I get the stuff below but nothing mentioned about that remove_ads purchase

Starting async operation: refresh inventory
Querying owned items, item type: inapp
Package name: com.sorex.test
Calling getPurchases with continuation token: null
Owned items response: 0
Continuation token: null
Querying SKU details.
queryPrices: nothing to do because there are no SKUs.
Querying owned items, item type: subs
Package name: com.sorex.test
Calling getPurchases with continuation token: null
Owned items response: 0
Continuation token: null
Querying SKU details.
queryPrices: nothing to do because there are no SKUs.
Ending async operation: refresh inventory


I have problems to let the app use my new apptest account tho, it still uses the original one maybe that's why it doesn't list anything.
 

ilan

Expert
Licensed User
Longtime User
it will list only if you did a purchase.you can see that Owned items response = 0

i also tried ones to use a test account but could not get it to work.
i recommend you to take another phone with another user and make a real purchase

you can cancel it anyway and even if you missed the allowed period to cancel you will get 70% from that sale ;)
so if you set it to 0.99$ you pay only 0.3$ for the test (if you forget to cancel)

give it a try
 

sorex

Expert
Licensed User
Longtime User
Will try on a virgin device tomorrow, I'll keep you posted.
 

sorex

Expert
Licensed User
Longtime User
It seems that it ain't my lucky day today.

I succeeded in the payment but the ads still appeared.

So I closed the app and restarted it again to see if it shows up in the purchased items list but then I get the stuff below

Billing service connected.
Checking for in-app billing 3 support.
In-app billing version 3 supported for com.sorex.test
Subscriptions AVAILABLE.
true, Setup successful. (response: 0:OK)
Subscriptions supported: true
Starting async operation: refresh inventory
Querying owned items, item type: inapp
Package name: com.sorex.test
Calling getPurchases with continuation token: null
Owned items response: 0
java.lang.IllegalArgumentException: anywheresoftware.b4a.objects.Base64DecoderException: Bad Base64 input character at 0: 39(decimal)
at anywheresoftware.b4a.objects.IBSecurity.generatePublicKey(IBSecurity.java:91)
at anywheresoftware.b4a.objects.IBSecurity.verifyPurchase(IBSecurity.java:67)
at anywheresoftware.b4a.objects.IbHelper.queryPurchases(IbHelper.java:863)
at anywheresoftware.b4a.objects.IbHelper.queryInventory(IbHelper.java:544)
at anywheresoftware.b4a.objects.IbHelper.queryInventory(IbHelper.java:523)
at anywheresoftware.b4a.objects.IbHelper$3.run(IbHelper.java:616)
at java.lang.Thread.run(Thread.java:856)
Caused by: anywheresoftware.b4a.objects.Base64DecoderException: Bad Base64 input character at 0: 39(decimal)
at anywheresoftware.b4a.objects.B64.decode(B64.java:549)
at anywheresoftware.b4a.objects.B64.decode(B64.java:477)
at anywheresoftware.b4a.objects.B64.decode(B64.java:423)
at anywheresoftware.b4a.objects.IBSecurity.generatePublicKey(IBSecurity.java:81)
... 6 more
** Activity (main) Pause, UserClosed = true **


this happends even before the manager_OwnedProducts callback sub gets triggered
 

sorex

Expert
Licensed User
Longtime User
the error is gone.

If someone else is gettings this check your KEY again that it doesn't contain an extra space, quote or any other char that got there due to copy pasting.
 

sorex

Expert
Licensed User
Longtime User
Everything works fine, Ilan.

I removed the filewriting as @Bill Kantz suggested and it still works fine.

The only small issue with it is that the button only gets removed when the app is already visible because the in-app calls are async.

A splash screen solved this ofcourse.

Thanks for the tutorial and hopefully my misery helps others aswell ;)
 

ilan

Expert
Licensed User
Longtime User
I removed the filewriting as @Bill Kantz suggested and it still works fine.

sorry but i dont know what you mean. cant see any post from @Bill Kantz here, what file writing do you mean? "ad.txt"?

The only small issue with it is that the button only gets removed when the app is already visible because the in-app calls are async.

what button? a purchase the full app button?
 
Last edited:
Top