Android Tutorial [outdated] Perform GooglePlayBilling Subscription Up/Downgrade

This solution is deprecated and applied to the previous billing version.

Please do not use it.






--------------------------------------------------------

Based on Erel's solution to the question "Howto GooglePlayBilling Subscription Up/Downgrade?" a test project was created. It should enable interested developers to explore the variants of Google Play In-App subscriptions.

2019-11-06_14-14-23.png
B4X:
' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]' MANIFEST[/INDENT]
[INDENT]' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]'This code will be applied to the manifest file during compilation.[/INDENT]
[INDENT]'You do not need to modify it in most cases.[/INDENT]
[INDENT]'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136[/INDENT]
[INDENT]AddManifestText([/INDENT]
[INDENT]<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28"/>[/INDENT]
[INDENT]<supports-screens android:largeScreens="true"[/INDENT]
[INDENT]    android:normalScreens="true"[/INDENT]
[INDENT]    android:smallScreens="true"[/INDENT]
[INDENT]    android:anyDensity="true"/>)[/INDENT]
[INDENT]SetApplicationAttribute(android:icon, "@drawable/icon")[/INDENT]
[INDENT]SetApplicationAttribute(android:label, "$LABEL$")[/INDENT]
[INDENT]CreateResourceFromFile(Macro, Themes.DarkTheme)[/INDENT]
[INDENT]'End of default text.[/INDENT]
[INDENT]CreateResourceFromFile(Macro, GooglePlayBilling.GooglePlayBilling)[/INDENT]
[INDENT][/INDENT]
[INDENT][/INDENT]
[INDENT][/INDENT]
[INDENT][/INDENT]
[INDENT]' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]' MAIN MODULE[/INDENT]
[INDENT]' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]#Region  Project Attributes[/INDENT]
[INDENT]    #ApplicationLabel: a B4A Billing Subscriptions Example[/INDENT]
[INDENT]    #VersionCode: 12[/INDENT]
[INDENT]    #VersionName: v0.1[/INDENT]
[INDENT]    'SupportedOrientations possible values: unspecified, landscape or portrait.[/INDENT]
[INDENT]    #SupportedOrientations: unspecified[/INDENT]
[INDENT]    #CanInstallToExternalStorage: False[/INDENT]
[INDENT]#End Region[/INDENT]
[INDENT][/INDENT]
[INDENT]' Testers Playstore-Link --> https://play.google.com/apps/testing/b4a.example.biltest001[/INDENT]
[INDENT][/INDENT]
[INDENT]' Google's howto --> https://developer.android.com/google/play/billing/billing_testing[/INDENT]
[INDENT][/INDENT]
[INDENT]' User-test a Google Play Billing app --> https://developer.android.com/google/play/billing/billing_testing#test-purchases-sandbox[/INDENT]
[INDENT][/INDENT]
[INDENT]' Google Test subscriptions-specific features --> https://developer.android.com/google/play/billing/billing_testing.html#testing-subscriptions[/INDENT]
[INDENT]#Region  Activity Attributes[/INDENT]
[INDENT]    #FullScreen: true  [/INDENT]
[INDENT]    #IncludeTitle: false[/INDENT]
[INDENT]#End Region[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub Process_Globals[/INDENT]
[INDENT]    Dim BillingMapOfAvailableProducts As Map[/INDENT]
[INDENT]    Dim BillingActiveProductId As String = ""[/INDENT]
[INDENT]    Dim BillingActiveCostLevel As Int = 0[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT]Sub Globals[/INDENT]
[INDENT]    Private LabelAskProducts As B4XView[/INDENT]
[INDENT]    Private BillingProductsClv As CustomListView[/INDENT]
[INDENT]    Private clvitemBackPanel As B4XView[/INDENT]
[INDENT]    Private clvitemLabel1 As B4XView[/INDENT]
[INDENT]    Private clvitemLabel3 As B4XView[/INDENT]
[INDENT]    Private clvitemLabel4 As B4XView[/INDENT]
[INDENT]    Private clvitemButton1 As B4XView[/INDENT]
[INDENT]    Private StateOnline As B4XView[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT]Sub Activity_Create(FirstTime As Boolean)[/INDENT]
[INDENT]    Activity.LoadLayout("Layout1")[/INDENT]
[INDENT]    Activity.Color = Colors.White[/INDENT]
[INDENT]    StateOnline.Text = ""[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    BillingMapOfAvailableProducts = GetBillingSubProducts[/INDENT]
[INDENT]   [/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT]Sub Activity_Resume[/INDENT]
[INDENT]    wait for(BillingAskProductsAndFillClv) complete(nix As Object)[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT]Sub Activity_Pause (UserClosed As Boolean)[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]' -------------------------------------[/INDENT]
[INDENT][/INDENT]
[INDENT]private Sub GetBillingSubProducts As Map[/INDENT]
[INDENT]    ' These are the App's Subsciption products as they are implemented in Google Playstore[/INDENT]
[INDENT]    ' Path: GooglePlay console/ Store presence/ In-App products/ Subsriptions[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    ' The items should be ordered from lowest price to highest, so that[/INDENT]
[INDENT]    ' an "up/downgrade" option can be implemented[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim MapOfBillingSubscritions As Map[/INDENT]
[INDENT]    MapOfBillingSubscritions.Initialize[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim BillingSubProduct As BillingSubProduct[/INDENT]
[INDENT]    BillingSubProduct.Initialize[/INDENT]
[INDENT]    BillingSubProduct.ProductID = "free"[/INDENT]
[INDENT]    BillingSubProduct.OrderId = ""[/INDENT]
[INDENT]    BillingSubProduct.Title = "Free Testversion"[/INDENT]
[INDENT]    BillingSubProduct.Description = $"Basic functions. ${CRLF}Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."$[/INDENT]
[INDENT]    BillingSubProduct.Price = "0.00"[/INDENT]
[INDENT]    BillingSubProduct.CostLevel = 0[/INDENT]
[INDENT]    BillingSubProduct.FreeTrialPeriod = 0[/INDENT]
[INDENT]    BillingSubProduct.BillingPeriod = "free"[/INDENT]
[INDENT]    BillingSubProduct.DeveloperPayload = ""[/INDENT]
[INDENT]    MapOfBillingSubscritions.Put(BillingSubProduct.ProductID, BillingSubProduct)[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim BillingSubProduct As BillingSubProduct[/INDENT]
[INDENT]    BillingSubProduct.Initialize[/INDENT]
[INDENT]    BillingSubProduct.ProductID = "subsprod1"[/INDENT]
[INDENT]    BillingSubProduct.OrderId = ""[/INDENT]
[INDENT]    BillingSubProduct.Title = "Prod1 SubWeekly"[/INDENT]
[INDENT]    BillingSubProduct.Description = $"Testproduct for SUBS "weekly". ${CRLF}Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."$[/INDENT]
[INDENT]    BillingSubProduct.Price = "0.50"[/INDENT]
[INDENT]    BillingSubProduct.CostLevel = 1[/INDENT]
[INDENT]    BillingSubProduct.FreeTrialPeriod = 3[/INDENT]
[INDENT]    BillingSubProduct.BillingPeriod = "week"[/INDENT]
[INDENT]    BillingSubProduct.DeveloperPayload = ""[/INDENT]
[INDENT]    MapOfBillingSubscritions.Put(BillingSubProduct.ProductID, BillingSubProduct)[/INDENT]
[INDENT][/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim BillingSubProduct As BillingSubProduct[/INDENT]
[INDENT]    BillingSubProduct.Initialize[/INDENT]
[INDENT]    BillingSubProduct.ProductID = "subsprod2"[/INDENT]
[INDENT]    BillingSubProduct.OrderId = ""[/INDENT]
[INDENT]    BillingSubProduct.Title = "Prod2 SubMonth"[/INDENT]
[INDENT]    BillingSubProduct.Description = $"Testproduct for SUBS "monthly". ${CRLF}Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."$[/INDENT]
[INDENT]    BillingSubProduct.Price = "1.00"[/INDENT]
[INDENT]    BillingSubProduct.CostLevel = 2[/INDENT]
[INDENT]    BillingSubProduct.FreeTrialPeriod = 3[/INDENT]
[INDENT]    BillingSubProduct.BillingPeriod = "month"[/INDENT]
[INDENT]    BillingSubProduct.DeveloperPayload = ""[/INDENT]
[INDENT]    MapOfBillingSubscritions.Put(BillingSubProduct.ProductID, BillingSubProduct)[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Return MapOfBillingSubscritions[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub BillingAskProductsAndFillClv As ResumableSub[/INDENT]
[INDENT]    Log("#-Sub main.AskProducts")[/INDENT]
[INDENT]    LabelAskProducts.Text = "Working..."[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    StateOnline.Text = Chr(0xE2C1) ' cloud OFF[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim bspNull As BillingSubProduct[/INDENT]
[INDENT]    bspNull.Initialize[/INDENT]
[INDENT]    BillingActiveCostLevel = 0[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Dim sb As StringBuilder[/INDENT]
[INDENT]    sb.Initialize[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Wait For (Starter.billing.ConnectIfNeeded) Billing_Connected (Result As BillingResult)[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    If Result.IsSuccess Then[/INDENT]
[INDENT]        StateOnline.Text = Chr(0xE2C2) ' cloud ON[/INDENT]
[INDENT]        If Starter.billing.SubscriptionsSupported Then[/INDENT]
[INDENT]            sb.Append("SUBS supported, ")[/INDENT]
[INDENT]        Else[/INDENT]
[INDENT]            sb.Append("SUBS not supported, ")[/INDENT]
[INDENT]        End If[/INDENT]
[INDENT]       [/INDENT]
[INDENT]        Wait For (Starter.billing.QueryPurchases("subs")) Billing_PurchasesQueryCompleted (Result As BillingResult, Purchases As List)[/INDENT]
[INDENT]        If Result.IsSuccess Then[/INDENT]
[INDENT]            Log("#-")[/INDENT]
[INDENT]            Log("#-  x106, Purchases.size=" & Purchases.Size)[/INDENT]
[INDENT]            If Purchases.Size = 0 Then[/INDENT]
[INDENT]                Dim bspdefault As BillingSubProduct = BillingMapOfAvailableProducts.Get("free")[/INDENT]
[INDENT]                BillingActiveProductId = bspdefault.ProductID[/INDENT]
[INDENT]                BillingActiveCostLevel = bspdefault.CostLevel[/INDENT]
[INDENT]                Starter.BillingActiveSubsProductTitle = bspdefault.Title[/INDENT]
[INDENT]            Else[/INDENT]
[INDENT]           [/INDENT]
[INDENT]                For Each p As Purchase In Purchases[/INDENT]
[INDENT]                    Log("#-    x134, p.OrderId          = " & p.OrderId)[/INDENT]
[INDENT]                    Log("#-    x135, p.PurchaseState    = " & p.PurchaseState & " --> " & Starter.BillingPurchState(p.PurchaseState))[/INDENT]
[INDENT]                    Log("#-    x136, p.PurchaseTime     = " & p.PurchaseTime & $" --> $DateTime{p.PurchaseTime}"$)[/INDENT]
[INDENT]                    Log("#-    x137, p.Sku              = " & p.Sku )[/INDENT]
[INDENT]                    Log("#-    x138, p.IsAcknowledged   = " & p.IsAcknowledged)[/INDENT]
[INDENT]                    Log("#-    x139, p.DeveloperPayload = " & p.DeveloperPayload)[/INDENT]
[INDENT]                    Log("#-    x140, p.IsAutoRenewing   = " & p.IsAutoRenewing)[/INDENT]
[INDENT]                   [/INDENT]
[INDENT]                    sb.Append(p.Sku).Append(", ")[/INDENT]
[INDENT]               [/INDENT]
[INDENT]                    If p.Sku <> Null Then[/INDENT]
[INDENT]                        Dim bspx As BillingSubProduct = BillingMapOfAvailableProducts.GetDefault(p.Sku, bspNull)[/INDENT]
[INDENT]                        If bspx.ProductID = p.Sku Then[/INDENT]
[INDENT]                            BillingActiveProductId    = p.Sku                      [/INDENT]
[INDENT]                            bspx.DeveloperPayload = p.DeveloperPayload[/INDENT]
[INDENT]                            BillingMapOfAvailableProducts.put(p.Sku, bspx)[/INDENT]
[INDENT]                            BillingActiveCostLevel = bspx.CostLevel[/INDENT]
[INDENT]                            Starter.BillingActiveSubsProductTitle = bspx.Title[/INDENT]
[INDENT]                        End If[/INDENT]
[INDENT]                    End If[/INDENT]
[INDENT]                   [/INDENT]
[INDENT]                Next[/INDENT]
[INDENT]            End If[/INDENT]
[INDENT]            LabelAskProducts.Text = sb.ToString[/INDENT]
[INDENT]        End If[/INDENT]
[INDENT]    Else[/INDENT]
[INDENT]        LabelAskProducts.Text = "Not connected"[/INDENT]
[INDENT]        Log("#-  x139, not connected")[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    Log("#-")[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    FillClv_Products[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Return Null[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]private Sub FillClv_Products[/INDENT]
[INDENT]    BillingProductsClv.Clear[/INDENT]
[INDENT]    Dim i As Int = 0[/INDENT]
[INDENT]    For Each k As String In BillingMapOfAvailableProducts.Keys[/INDENT]
[INDENT]        Dim bsp As BillingSubProduct = BillingMapOfAvailableProducts.Get(k)[/INDENT]
[INDENT]        BillingProductsClv.Add(BillingProductsClv_CreateItem(bsp, i), i)      [/INDENT]
[INDENT]        i = i +1[/INDENT]
[INDENT]    Next[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]private Sub BillingProductsClv_CreateItem(bsp As BillingSubProduct, Index As Int) As B4XView[/INDENT]
[INDENT]    'Log($"#-Sub main.BillingProductsClv_CreateItem, Index=${Index}, bsp.ProductID="${bsp.ProductID}"$ )[/INDENT]
[INDENT]    Dim PanelHeight As Int = 216dip[/INDENT]
[INDENT]    Dim p As Panel[/INDENT]
[INDENT]    p.Initialize("")[/INDENT]
[INDENT]    p.SetLayout(0, 0, BillingProductsClv.AsView.Width, PanelHeight)[/INDENT]
[INDENT]    p.LoadLayout("billing_clvitem")[/INDENT]
[INDENT]    clvitemBackPanel.SetLayoutAnimated(0, 8dip, 0, BillingProductsClv.AsView.Width -16dip, PanelHeight -8dip)[/INDENT]
[INDENT]    clvitemLabel1.SetLayoutAnimated(0, 0, 0, BillingProductsClv.AsView.Width -16dip, 40dip)[/INDENT]
[INDENT]    clvitemLabel1.Text = bsp.Title[/INDENT]
[INDENT]    clvitemLabel3.Text = bsp.Description[/INDENT]
[INDENT]    clvitemLabel4.Text = bsp.Price[/INDENT]
[INDENT]    clvitemLabel4.SetColorAndBorder(Starter.xui.Color_White, 2dip, Starter.xui.Color_LightGray, 5dip)[/INDENT]
[INDENT]    clvitemButton1.Tag = bsp.ProductID[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    'Log("#-  x205, BillingActiveProductId=" & BillingActiveProductId & ", BillingActiveCostLevel=" & BillingActiveCostLevel)[/INDENT]
[INDENT]    If BillingActiveProductId = bsp.ProductID Then[/INDENT]
[INDENT]        clvitemLabel4.Text = "Activated"[/INDENT]
[INDENT]        clvitemButton1.Text = "Show my Subscriptions"[/INDENT]
[INDENT]    Else[/INDENT]
[INDENT]        If BillingActiveProductId = "free" Then[/INDENT]
[INDENT]            clvitemButton1.Text = "BUY"[/INDENT]
[INDENT]        Else If Index > BillingActiveCostLevel Then[/INDENT]
[INDENT]            clvitemButton1.Text = "UPgrade"[/INDENT]
[INDENT]        else If Index < BillingActiveCostLevel Then[/INDENT]
[INDENT]           [/INDENT]
[INDENT]            If bsp.ProductID = "free" Then[/INDENT]
[INDENT]                clvitemButton1.Text = "Cancel Subscription"[/INDENT]
[INDENT]            Else[/INDENT]
[INDENT]                clvitemButton1.Text = "DOWNgrade"[/INDENT]
[INDENT]            End If[/INDENT]
[INDENT]           [/INDENT]
[INDENT]        Else[/INDENT]
[INDENT]            clvitemButton1.Text = "BUY"[/INDENT]
[INDENT]        End If[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    If bsp.ProductID = "free" Then[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    Return p[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub clvitemButton1_Click As ResumableSub[/INDENT]
[INDENT]    Log("#-")[/INDENT]
[INDENT]    Dim ButtonX As Button = Sender[/INDENT]
[INDENT]    Dim bspSelectedByUser As BillingSubProduct = BillingMapOfAvailableProducts.Get(ButtonX.Tag)[/INDENT]
[INDENT]    Log("#-Sub main.clvitemButton1_Click, bspSelectedByUser.ProductID=" & bspSelectedByUser.ProductID )[/INDENT]
[INDENT]    If bspSelectedByUser.ProductID = "free" Then[/INDENT]
[INDENT]        ' Cancel subscription[/INDENT]
[INDENT]       [/INDENT]
[INDENT]    Dim sf As Object = Msgbox2Async($"The subscription cancellation takes place in the Playstore. All important information about the termination date will be displayed there."$, "Cancel Subscription" , "OK", "", "", Null, True )[/INDENT]
[INDENT]    Wait For (sf) Msgbox_Result (intIlResult As Int)[/INDENT]
[INDENT][/INDENT]
[INDENT]        StartActivity (Starter.pi.OpenBrowser ("https://play.google.com/store/account/subscriptions") )[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    else If bspSelectedByUser.ProductID = BillingActiveProductId Then[/INDENT]
[INDENT]        StartActivity (Starter.pi.OpenBrowser ("https://play.google.com/store/account/subscriptions") )[/INDENT]
[INDENT]       [/INDENT]
[INDENT]    Else[/INDENT]
[INDENT]        CallSubDelayed2(Me, "BuyProductBySku", bspSelectedByUser)[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    Return Null[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub BuyProductBySku(bsp As BillingSubProduct) As ResumableSub[/INDENT]
[INDENT]    Dim ProductToBuy As String = bsp.ProductID[/INDENT]
[INDENT]    Log("#-Sub main.BuyProduct, ProductToBuy=" & ProductToBuy & ", " & $"$DateTime{DateTime.Now}"$)[/INDENT]
[INDENT]    LabelAskProducts.Text = $"Buy product "${ProductToBuy}" in progress"$[/INDENT]
[INDENT]    Log("#-  x239, BillingActiveProductId=" & BillingActiveProductId)[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Wait For (Starter.billing.ConnectIfNeeded) Billing_Connected (Result As BillingResult)[/INDENT]
[INDENT]    Log("#-  x241, Result.DebugMessage=" & Result.DebugMessage)[/INDENT]
[INDENT]    If Result.IsSuccess Then[/INDENT]
[INDENT]       [/INDENT]
[INDENT]        Dim sf As Object = Starter.billing.QuerySkuDetails("subs", Array(ProductToBuy))[/INDENT]
[INDENT]        Wait For (sf) Billing_SkuQueryCompleted (Result As BillingResult, SkuDetails As List)[/INDENT]
[INDENT]        If Result.IsSuccess And SkuDetails.Size = 1 Then[/INDENT]
[INDENT][/INDENT]
[INDENT]            Log("#-  x249, BillingActiveProductId=" & BillingActiveProductId)[/INDENT]
[INDENT]            If BillingActiveProductId <> "free" Then[/INDENT]
[INDENT]                Result = LaunchBillingFlow2(SkuDetails.Get(0), BillingActiveProductId)[/INDENT]
[INDENT]            Else[/INDENT]
[INDENT]                'start the billing process. The PurchasesUpdated event will be raised in the starter service[/INDENT]
[INDENT]                Result = Starter.billing.LaunchBillingFlow(SkuDetails.Get(0))[/INDENT]
[INDENT]            End If[/INDENT]
[INDENT]            Log("#-  x78, Result.IsSuccess=" & Result.IsSuccess)[/INDENT]
[INDENT][/INDENT]
[INDENT]            If Result.IsSuccess Then[/INDENT]
[INDENT]                LabelAskProducts.Text = $"Product "${ProductToBuy}" bought ok"$[/INDENT]
[INDENT]                Return Null[/INDENT]
[INDENT]            Else[/INDENT]
[INDENT]                LabelAskProducts.Text = $"Failed to buy product "${ProductToBuy}" "$[/INDENT]
[INDENT]            End If[/INDENT]
[INDENT]        End If[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    ToastMessageShow("#-  x74, Error starting billing process", True)[/INDENT]
[INDENT]    Return Null[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub LaunchBillingFlow2(sku As SkuDetails, OldSku As String) As BillingResult[/INDENT]
[INDENT]    ' Google --> https://developer.android.com/google/play/billing/billing_subscriptions.html#Allow-upgrade[/INDENT]
[INDENT]    ' Erel --> https://www.b4x.com/android/forum/threads/howto-googleplaybilling-subscription-up-downgrade.111090/#post-693109[/INDENT]
[INDENT]    '   should be called from the activity[/INDENT]
[INDENT]    Dim jo As JavaObject = Starter.billing[/INDENT]
[INDENT]    Dim BillingClient As JavaObject = jo.GetField("client")[/INDENT]
[INDENT]    Dim context As JavaObject[/INDENT]
[INDENT]    context.InitializeContext[/INDENT]
[INDENT]    Dim BillingFlowParams As JavaObject[/INDENT]
[INDENT]    BillingFlowParams = BillingFlowParams.InitializeStatic("com.android.billingclient.api.BillingFlowParams") _[/INDENT]
[INDENT]               .RunMethodJO("newBuilder", Null).RunMethodJO("setSkuDetails", Array(sku)) _[/INDENT]
[INDENT]               .RunMethodJO("setOldSku", Array(OldSku)).RunMethod("build", Null)[/INDENT]
[INDENT]    Return BillingClient.RunMethod("launchBillingFlow", Array(context, BillingFlowParams))[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub AskPurchState_Click[/INDENT]
[INDENT]    CallSubDelayed(Me, "BillingAskProductsAndFillClv")[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]' -------------------------------------[/INDENT]
[INDENT][/INDENT]
[INDENT][/INDENT]
[INDENT]' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]' STARTER SERVICE MODULE[/INDENT]
[INDENT]' ~~~~~~~~~~~~~~~~~~~~[/INDENT]
[INDENT]#Region  Service Attributes[/INDENT]
[INDENT]    #StartAtBoot: False[/INDENT]
[INDENT]    #ExcludeFromLibrary: True[/INDENT]
[INDENT]#End Region[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub Process_Globals[/INDENT]
[INDENT]    Type BillingSubProduct(ProductID As String, Title As String, Description As String, Price As String, BillingPeriod As String, FreeTrialPeriod As Int, OrderId As String, CostLevel As Int, DeveloperPayload As String)[/INDENT]
[INDENT]    Public xui As XUI[/INDENT]
[INDENT]    Public billing As BillingClient[/INDENT]
[INDENT]    Public PurchSubsOk As Boolean[/INDENT]
[INDENT]    ' ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~[/INDENT]
[INDENT]   [/INDENT]
[INDENT]   [/INDENT]
[INDENT]    Public const BILLING_KEY As String = "MIIBI..."  ' <-- YOUR "licence key for this application" in GooglePlaystore developer console/ thisapp/ Development tools/ Services & APIs[/INDENT]
[INDENT]   [/INDENT]
[INDENT]   [/INDENT]
[INDENT]    ' ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~ ~~~~[/INDENT]
[INDENT]    Public const BILLINGPROD1 As String = "subsprod1"[/INDENT]
[INDENT]    Public const BILLINGPROD2 As String = "subsprod2"[/INDENT]
[INDENT]    Public BillingPurchState(3) As String[/INDENT]
[INDENT]    Public BillingActiveSubsProductTitle As String = ""[/INDENT]
[INDENT]    Public pi As PhoneIntents[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub Service_Create[/INDENT]
[INDENT]    billing.Initialize("billing")[/INDENT]
[INDENT]    BillingPurchState(0) = "STATE_UNSPECIFIED"[/INDENT]
[INDENT]    BillingPurchState(1) = "STATE_PURCHASED"[/INDENT]
[INDENT]    BillingPurchState(2) = "STATE_PENDING"[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]' --- --- --- --- --- --- --- --- --- --- --- --- ---[/INDENT]
[INDENT]Sub billing_PurchasesUpdated (Result As BillingResult, Purchases As List)[/INDENT]
[INDENT]    Log("#-Sub starter.Sub billing_PurchasesUpdated, Result.IsSuccess=" & Result.IsSuccess)[/INDENT]
[INDENT]    'This event will be raised when the status of one or more of the purchases has changed.[/INDENT]
[INDENT]    'It will usually happen as a result of calling LaunchBillingFlow however it can be called in other cases as well.[/INDENT]
[INDENT]    If Result.IsSuccess Then[/INDENT]
[INDENT]        For Each p As Purchase In Purchases[/INDENT]
[INDENT]            Log("#-    x39, p.Sku = " & p.Sku)[/INDENT]
[INDENT]            If p.Sku.StartsWith("subsprod") Then[/INDENT]
[INDENT]                HandleSubscriptionPurchase(p)[/INDENT]
[INDENT]            Else[/INDENT]
[INDENT]                Log("#-    x43, Unexpected product...")[/INDENT]
[INDENT]            End If[/INDENT]
[INDENT]        Next[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Private Sub HandleSubscriptionPurchase (p As Purchase) As ResumableSub[/INDENT]
[INDENT]    Log("#-Sub starter.Sub HandleSubscriptionPurchase, p.sku=" & p.Sku)[/INDENT]
[INDENT]    If p.PurchaseState <> p.STATE_PURCHASED Then Return Null[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    ' --> https://developer.android.com/google/play/billing/billing_testing.html#testing-renewals[/INDENT]
[INDENT]   [/INDENT]
[INDENT]    'Verify the purchase signature.[/INDENT]
[INDENT]    'This cannot be done with the test id.[/INDENT]
[INDENT]    If Not(p.Sku.StartsWith("subsprod")) And billing.VerifyPurchase(p, BILLING_KEY) = False Then [/INDENT]
[INDENT]        Log("#-  x55, Invalid purchase")[/INDENT]
[INDENT]        Return Null[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    If p.IsAcknowledged = False Then[/INDENT]
[INDENT]        'we either acknowledge the product or consume it.[/INDENT]
[INDENT]        Dim Intval As Int = 3 ' Testdays[/INDENT]
[INDENT]        Select Case p.Sku[/INDENT]
[INDENT]            Case "subsprod1" ' Weekly subscription[/INDENT]
[INDENT]                Intval= 7[/INDENT]
[INDENT]            Case "subsprod2" ' Monthly subscription[/INDENT]
[INDENT]                Intval= 30[/INDENT]
[INDENT]        End Select[/INDENT]
[INDENT]        Dim SubsEndTime As Long = p.PurchaseTime + (DateTime.TicksPerDay *Intval)[/INDENT]
[INDENT]        Dim mypayload As String = "mp~" & p.Sku & "~" & p.PurchaseTime & "~" & Intval & "~" & SubsEndTime[/INDENT]
[INDENT]        Wait For (billing.AcknowledgePurchase(p.PurchaseToken, mypayload)) Billing_AcknowledgeCompleted (Result As BillingResult)[/INDENT]
[INDENT]        Log("#-  x61, Acknowledged: " & Result.IsSuccess & ", p.OrderId=" & p.OrderId)[/INDENT]
[INDENT]    End If[/INDENT]
[INDENT]    PurchSubsOk = True[/INDENT]
[INDENT]    Log("#-  x64, Purchase OK!")[/INDENT]
[INDENT]    CallSub(Main, "BillingAskProductsAndFillClv")[/INDENT]
[INDENT]    Return Null[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub Service_Start (StartingIntent As Intent)[/INDENT]
[INDENT]    Service.StopAutomaticForeground 'Starter service can start in the foreground state in some edge cases.[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]'Return true to allow the OS default exceptions handler to handle the uncaught exception.[/INDENT]
[INDENT]Sub Application_Error (Error As Exception, StackTrace As String) As Boolean[/INDENT]
[INDENT]    Return True[/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]Sub Service_Destroy[/INDENT]
[INDENT][/INDENT]
[INDENT]End Sub[/INDENT]
[INDENT][/INDENT]
[INDENT]

Prerequisites for use:
1. Reading and understanding the "GooglePlayBilling - In App Purchases" library​
2. Compile the app and create an entry for the APK in the Playstore (suitable images for "Feature" and "Promo" can be found in the Assets folder for your convenience).​
3. Create a "Closed Track" for the app under "App releases" and set up at least one tester.​
4. Make sure that all requirements for publishing are met (note the checkmarks at the edge of the "Store presence" menu. Most fail by ignoring the small warnings.)​
5. Under "Store presence" activate "FREE" and "PUBLISH". Don't worry, because there are only "Closed" releases, it won't appear in the real Playstore.​
6. If a named entry is not visible or disabled, then somewhere in the whole settings a prerequisite is missing.​
7. Create subscriptions in Google's Developer console. Use the names "subsprod1" and "subsprod2" as ProductId. They are used in the source code. Enter a title and a description and then ACTIVATE the product.​
8. In order to simplify the whole payment process there is the possibility to work with "License Testing" credit cards. Here you can test good and bad payment situations. How the setup works can be found here
Attached screenshots may be helpful for orientation.
 

Attachments

  • BillingTestApp011.zip
    75.2 KB · Views: 666
  • 2019-11-06_14-41-00.png
    2019-11-06_14-41-00.png
    34.5 KB · Views: 467
  • 2019-11-06_14-38-27.png
    2019-11-06_14-38-27.png
    75.1 KB · Views: 422
  • 2019-11-06_14-58-16.png
    2019-11-06_14-58-16.png
    60.2 KB · Views: 402
  • 2019-11-06_14-56-50.png
    2019-11-06_14-56-50.png
    62 KB · Views: 405
  • 2019-11-06_14-51-52.png
    2019-11-06_14-51-52.png
    51.6 KB · Views: 398
  • 2019-11-06_14-51-00.png
    2019-11-06_14-51-00.png
    37.9 KB · Views: 396
Last edited:

fredo

Well-Known Member
Licensed User
Longtime User
Thank you for your interest in my old sample project. I appreciate your enthusiasm for the topic. Unfortunately, I currently do not have the available time to delve back into the subject.

I think, opening a new thread for your question could reach a lot of people who could help

If, over time, you come up with a solution or make progress, it would be great if you could provide a link to it here.
 
Top