فهرست منبع

Rewriting Mono Android IAP to match with GDScript
Version.

Ricardo Alcantara 5 سال پیش
والد
کامیت
6e98353ce3

+ 0 - 19
mono/android_iap/Android IAP with C#.sln

@@ -1,19 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android IAP with C#", "Android IAP with C#.csproj", "{FA89D4C3-B45B-49F1-A975-BF059CA82D86}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-	Debug|Any CPU = Debug|Any CPU
-	ExportDebug|Any CPU = ExportDebug|Any CPU
-	ExportRelease|Any CPU = ExportRelease|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
-		{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
-	EndGlobalSection
-EndGlobal

+ 5 - 5
mono/android_iap/Android IAP with C#.csproj → mono/android_iap/Android in-app purchases with C#.csproj

@@ -3,11 +3,11 @@
   <PropertyGroup>
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{FA89D4C3-B45B-49F1-A975-BF059CA82D86}</ProjectGuid>
+    <ProjectGuid>{480953C3-B1FD-42F6-8A07-51A3F69024D7}</ProjectGuid>
     <OutputType>Library</OutputType>
     <OutputType>Library</OutputType>
     <OutputPath>.mono\temp\bin\$(Configuration)</OutputPath>
     <OutputPath>.mono\temp\bin\$(Configuration)</OutputPath>
-    <RootNamespace>AndroidIAPwithC</RootNamespace>
-    <AssemblyName>Android IAP with C#</AssemblyName>
+    <RootNamespace>AndroidInAppPurchasesWithCSharp</RootNamespace>
+    <AssemblyName>Android in-app purchases with C#</AssemblyName>
     <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <GodotProjectGeneratorVersion>1.0.0.0</GodotProjectGeneratorVersion>
     <GodotProjectGeneratorVersion>1.0.0.0</GodotProjectGeneratorVersion>
     <BaseIntermediateOutputPath>.mono\temp\obj</BaseIntermediateOutputPath>
     <BaseIntermediateOutputPath>.mono\temp\obj</BaseIntermediateOutputPath>
@@ -47,12 +47,12 @@
       <PrivateAssets>All</PrivateAssets>
       <PrivateAssets>All</PrivateAssets>
     </PackageReference>
     </PackageReference>
     <Reference Include="GodotSharp">
     <Reference Include="GodotSharp">
-      <HintPath>$(ProjectDir)\.mono\assemblies\$(ApiConfiguration)\GodotSharp.dll</HintPath>
       <Private>False</Private>
       <Private>False</Private>
+      <HintPath>$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/GodotSharp.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
     <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
-      <HintPath>$(ProjectDir)\.mono\assemblies\$(ApiConfiguration)\GodotSharpEditor.dll</HintPath>
       <Private>False</Private>
       <Private>False</Private>
+      <HintPath>$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/GodotSharpEditor.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
   </ItemGroup>
   </ItemGroup>

+ 19 - 0
mono/android_iap/Android in-app purchases with C#.sln

@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android in-app purchases with C#", "Android in-app purchases with C#.csproj", "{480953C3-B1FD-42F6-8A07-51A3F69024D7}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+	Debug|Any CPU = Debug|Any CPU
+	ExportDebug|Any CPU = ExportDebug|Any CPU
+	ExportRelease|Any CPU = ExportRelease|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+		{480953C3-B1FD-42F6-8A07-51A3F69024D7}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+	EndGlobalSection
+EndGlobal

+ 1 - 1
mono/android_iap/GodotGooglePlayBilling/BillingResult.cs

@@ -1,7 +1,7 @@
 using Godot;
 using Godot;
 using Godot.Collections;
 using Godot.Collections;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     // https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode
     // https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode
     public enum BillingResponseCode
     public enum BillingResponseCode

+ 25 - 75
mono/android_iap/GodotGooglePlayBilling/GooglePlayBilling.cs

@@ -1,7 +1,7 @@
 using Godot.Collections;
 using Godot.Collections;
 using Godot;
 using Godot;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     public enum PurchaseType
     public enum PurchaseType
     {
     {
@@ -21,18 +21,20 @@ namespace Android_Iap.GodotGooglePlayBilling
         [Signal] public delegate void PurchaseAcknowledged(string purchaseToken);
         [Signal] public delegate void PurchaseAcknowledged(string purchaseToken);
         [Signal] public delegate void PurchaseAcknowledgementError(int code, string message);
         [Signal] public delegate void PurchaseAcknowledgementError(int code, string message);
         [Signal] public delegate void PurchaseConsumed(string purchaseToken);
         [Signal] public delegate void PurchaseConsumed(string purchaseToken);
-        [Signal] public delegate void PurchaseConsumption_error(int code, string message, string purchaseToken);
+        [Signal] public delegate void PurchaseConsumptionError(int code, string message, string purchaseToken);
 
 
         [Export] public bool AutoReconnect { get; set; }
         [Export] public bool AutoReconnect { get; set; }
         [Export] public bool AutoConnect { get; set; }
         [Export] public bool AutoConnect { get; set; }
 
 
+        public bool IsAvailable { get; private set; }
+
         private Object _payment;
         private Object _payment;
 
 
         public override void _Ready()
         public override void _Ready()
         {
         {
             if (Engine.HasSingleton("GodotGooglePlayBilling"))
             if (Engine.HasSingleton("GodotGooglePlayBilling"))
             {
             {
-                GD.Print("GodotGooglePlayBilling HasSingleton");
+                IsAvailable = true;
                 _payment = Engine.GetSingleton("GodotGooglePlayBilling");
                 _payment = Engine.GetSingleton("GodotGooglePlayBilling");
                 // These are all signals supported by the API
                 // These are all signals supported by the API
                 // You can drop some of these based on your needs
                 // You can drop some of these based on your needs
@@ -47,12 +49,10 @@ namespace Android_Iap.GodotGooglePlayBilling
                 _payment.Connect("purchase_acknowledgement_error", this, nameof(OnGodotGooglePlayBilling_purchase_acknowledgement_error)); // Response ID (int), Debug message (string), Purchase token (string)
                 _payment.Connect("purchase_acknowledgement_error", this, nameof(OnGodotGooglePlayBilling_purchase_acknowledgement_error)); // Response ID (int), Debug message (string), Purchase token (string)
                 _payment.Connect("purchase_consumed", this, nameof(OnGodotGooglePlayBilling_purchase_consumed)); // Purchase token (string)
                 _payment.Connect("purchase_consumed", this, nameof(OnGodotGooglePlayBilling_purchase_consumed)); // Purchase token (string)
                 _payment.Connect("purchase_consumption_error", this, nameof(OnGodotGooglePlayBilling_purchase_consumption_error)); // Response ID (int), Debug message (string), Purchase token (string)
                 _payment.Connect("purchase_consumption_error", this, nameof(OnGodotGooglePlayBilling_purchase_consumption_error)); // Response ID (int), Debug message (string), Purchase token (string)
-
-                if (AutoConnect) StartConnection();
             }
             }
             else
             else
             {
             {
-                GD.Print("GPB: Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and the GodotGooglePlayBilling plugin in your Android export settings! IAP will not work.");
+                IsAvailable = false;
             }
             }
         }
         }
 
 
@@ -62,6 +62,14 @@ namespace Android_Iap.GodotGooglePlayBilling
 
 
         public void EndConnection() => _payment?.Call("endConnection");
         public void EndConnection() => _payment?.Call("endConnection");
 
 
+        public void QuerySkuDetails(string[] querySkuDetails, PurchaseType type) => _payment?.Call("querySkuDetails", querySkuDetails, $"{type}".ToLower());
+
+        public bool IsReady() => (_payment?.Call("isReady") as bool?) ?? false;
+
+        public void AcknowledgePurchase(string purchaseToken) => _payment?.Call("acknowledgePurchase", purchaseToken);
+
+        public void ConsumePurchase(string purchaseToken) => _payment?.Call("consumePurchase", purchaseToken);
+
         public BillingResult Purchase(string sku)
         public BillingResult Purchase(string sku)
         {
         {
             if (_payment == null) return null;
             if (_payment == null) return null;
@@ -69,10 +77,6 @@ namespace Android_Iap.GodotGooglePlayBilling
             return new BillingResult(result);
             return new BillingResult(result);
         }
         }
 
 
-        public void QuerySkuDetails(string[] querySkuDetails, PurchaseType type) => _payment?.Call("querySkuDetails", querySkuDetails, $"{type}".ToLower());
-
-        public bool IsReady() => (_payment?.Call("isReady") as bool?) ?? false;
-
         public PurchasesResult QueryPurchases(PurchaseType purchaseType)
         public PurchasesResult QueryPurchases(PurchaseType purchaseType)
         {
         {
             if (_payment == null) return null;
             if (_payment == null) return null;
@@ -80,85 +84,31 @@ namespace Android_Iap.GodotGooglePlayBilling
             return new PurchasesResult(result);
             return new PurchasesResult(result);
         }
         }
 
 
-        public void AcknowledgePurchase(string purchaseToken) => _payment?.Call("acknowledgePurchase", purchaseToken);
-
-        public void ConsumePurchase(string purchaseToken) => _payment?.Call("consumePurchase", purchaseToken);
-
         #endregion
         #endregion
 
 
         #region GodotGooglePlayBilling Signals
         #region GodotGooglePlayBilling Signals
 
 
-        private void OnGodotGooglePlayBilling_connected()
-        {
-            GD.Print("GodotGooglePlayBilling Connected");
-            EmitSignal(nameof(Connected));
-        }
+        private void OnGodotGooglePlayBilling_connected() => EmitSignal(nameof(Connected));
 
 
-        private async void OnGodotGooglePlayBilling_disconnected()
-        {
-            GD.Print("GodotGooglePlayBilling Disconnected");
-            EmitSignal(nameof(Disconnected));
+        private void OnGodotGooglePlayBilling_disconnected() => EmitSignal(nameof(Disconnected));
 
 
-            if (AutoReconnect)
-            {
-                await ToSignal(GetTree().CreateTimer(10), "timeout");
-                StartConnection();
-            }
-        }
+        private void OnGodotGooglePlayBilling_connect_error(int code, string message) => EmitSignal(nameof(ConnectError), code, message);
 
 
-        private void OnGodotGooglePlayBilling_connect_error(int code, string message)
-        {
-            GD.Print($"GodotGooglePlayBilling ConnectError {code}: {message}");
-            EmitSignal(nameof(ConnectError), code, message);
-        }
+        private void OnGodotGooglePlayBilling_sku_details_query_completed(Array skuDetails) => EmitSignal(nameof(SkuDetailsQueryCompleted), skuDetails);
 
 
-        private void OnGodotGooglePlayBilling_sku_details_query_completed(Array skuDetails)
-        {
-            GD.Print($"GodotGooglePlayBilling SkuDetailsQueryCompleted {skuDetails}");
-            EmitSignal(nameof(SkuDetailsQueryCompleted), skuDetails);
-        }
+        private void OnGodotGooglePlayBilling_sku_details_query_error(int code, string message) => EmitSignal(nameof(SkuDetailsQueryError), code, message);
 
 
-        private void OnGodotGooglePlayBilling_sku_details_query_error(int code, string message)
-        {
-            GD.Print($"SkuDetailsQueryError error {code}: {message}");
-            EmitSignal(nameof(SkuDetailsQueryError), code, message);
-        }
+        private void OnGodotGooglePlayBilling_purchases_updated(Array purchases) => EmitSignal(nameof(PurchasesUpdated), purchases);
 
 
-        private void OnGodotGooglePlayBilling_purchases_updated(Array purchases)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchasesUpdated {purchases}");
-            EmitSignal(nameof(PurchasesUpdated), purchases);
-        }
+        private void OnGodotGooglePlayBilling_purchase_error(int code, string message) => EmitSignal(nameof(PurchaseError), code, message);
 
 
-        private void OnGodotGooglePlayBilling_purchase_error(int code, string message)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchaseError {code}: {message}");
-            EmitSignal(nameof(PurchaseError), code, message);
-        }
+        private void OnGodotGooglePlayBilling_purchase_acknowledged(string purchaseToken) => EmitSignal(nameof(PurchaseAcknowledged), purchaseToken);
 
 
-        private void OnGodotGooglePlayBilling_purchase_acknowledged(string purchaseToken)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchaseAcknowledged {purchaseToken}");
-            EmitSignal(nameof(PurchaseAcknowledged), purchaseToken);
-        }
-
-        private void OnGodotGooglePlayBilling_purchase_acknowledgement_error(int code, string message)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchaseAcknowledgementError error {code}: {message}");
-            EmitSignal(nameof(PurchaseAcknowledgementError), code, message);
-        }
+        private void OnGodotGooglePlayBilling_purchase_acknowledgement_error(int code, string message) => EmitSignal(nameof(PurchaseAcknowledgementError), code, message);
 
 
-        private void OnGodotGooglePlayBilling_purchase_consumed(string purchaseToken)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchaseConsumed successfully: {purchaseToken}");
-            EmitSignal(nameof(PurchaseConsumed), purchaseToken);
-        }
+        private void OnGodotGooglePlayBilling_purchase_consumed(string purchaseToken) => EmitSignal(nameof(PurchaseConsumed), purchaseToken);
 
 
-        private void OnGodotGooglePlayBilling_purchase_consumption_error(int code, string message, string purchaseToken)
-        {
-            GD.Print($"GodotGooglePlayBilling PurchaseConsumption_error error {code}: {message}, purchase token: {purchaseToken}");
-            EmitSignal(nameof(PurchaseConsumption_error), code, message, purchaseToken);
-        }
+        private void OnGodotGooglePlayBilling_purchase_consumption_error(int code, string message, string purchaseToken) => EmitSignal(nameof(PurchaseConsumptionError), code, message, purchaseToken);
 
 
         #endregion
         #endregion
     }
     }

+ 1 - 1
mono/android_iap/GodotGooglePlayBilling/GooglePlayBillingUtils.cs

@@ -1,7 +1,7 @@
 using Godot;
 using Godot;
 using Godot.Collections;
 using Godot.Collections;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     public static class GooglePlayBillingUtils
     public static class GooglePlayBillingUtils
     {
     {

+ 1 - 1
mono/android_iap/GodotGooglePlayBilling/Purchase.cs

@@ -2,7 +2,7 @@ using System;
 using Godot;
 using Godot;
 using Godot.Collections;
 using Godot.Collections;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     // https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
     // https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
     public enum PurchaseState
     public enum PurchaseState

+ 1 - 1
mono/android_iap/GodotGooglePlayBilling/PurchasesResult.cs

@@ -1,7 +1,7 @@
 using Godot;
 using Godot;
 using Godot.Collections;
 using Godot.Collections;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     public class PurchasesResult : BillingResult
     public class PurchasesResult : BillingResult
     {
     {

+ 1 - 1
mono/android_iap/GodotGooglePlayBilling/SkuDetails.cs

@@ -2,7 +2,7 @@ using System;
 using Godot;
 using Godot;
 using Godot.Collections;
 using Godot.Collections;
 
 
-namespace Android_Iap.GodotGooglePlayBilling
+namespace AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling
 {
 {
     public class SkuDetails
     public class SkuDetails
     {
     {

+ 124 - 166
mono/android_iap/Main.cs

@@ -1,232 +1,190 @@
-using Android_Iap.GodotGooglePlayBilling;
+using AndroidInAppPurchasesWithCSharp.GodotGooglePlayBilling;
 using Godot;
 using Godot;
-using CoreGeneric = System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System;
 using System;
 
 
-namespace Android_Iap
+namespace AndroidInAppPurchasesWithCSharp
 {
 {
-    /*
-    test skus
-    android.test.purchased
-    android.test.canceled
-    android.test.refunded
-    android.test.item_unavailable
-    */
-    public class Main : Node2D
+    public class Main : Control
     {
     {
-        private readonly string[] ArrInAppProductsSKUs = new string[]
-        {
-            "android.test.purchased",
-            "android.test.canceled",
-            "android.test.refunded",
-            "android.test.item_unavailable"
-        };
-
-
-        private Button _buyPotionButton;
-        private Label _totalPotionsLabel;
+        const string TestItemSku = "my_in_app_purchase_sku";
 
 
-        private Panel _panel;
-        private Label _processLabel;
-        private Label _thanksLabel;
+        private AcceptDialog _alertDialog;
+        private Label _label;
 
 
-        private ProgressBar _playerLife;
-        private StyleBoxFlat _playerLifeStyleBoxFlat;
+        private GooglePlayBilling _payment;
 
 
-        private GooglePlayBilling _googlePlayBilling;
-        private int _totalPotion = 5;
-
-        CoreGeneric.Dictionary<string, string> _purchases = new CoreGeneric.Dictionary<string, string>();
+        private string _testItemPurchaseToken;
 
 
         public override void _Ready()
         public override void _Ready()
         {
         {
-            _googlePlayBilling = GetNode<GooglePlayBilling>("GooglePlayBilling");
-
-            _buyPotionButton = GetNode<Button>("VBoxContainer2/BuyPotionButton");
-            _totalPotionsLabel = GetNode<Label>("VBoxContainer/Label");
-
-            _panel = GetNode<Panel>("Panel");
-            _processLabel = GetNode<Label>("Panel/ProcessLabel");
-            _thanksLabel = GetNode<Label>("Panel/ThanksLabel");
-
-            _playerLife = GetNode<ProgressBar>("Sprite/ProgressBar");
-
-            _playerLifeStyleBoxFlat = _playerLife.Get("custom_styles/fg") as StyleBoxFlat;
-            _playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, 1);
-
-            _playerLife.Value = 1;
-
-            _panel.Hide();
-            _processLabel.Hide();
-            _thanksLabel.Hide();
-            _buyPotionButton.Hide();
-            _totalPotionsLabel.Text = $"{_totalPotion} Potions";
-        }
-
-        public override void _Process(float delta)
-        {
-            if (_playerLife.Value > 0.5)
-            {
-                _playerLife.Value -= delta;
-            }
-            else if (_playerLife.Value > 0.2)
-            {
-                _playerLife.Value -= delta / 2;
+            _payment = GetNode<GooglePlayBilling>("GooglePlayBilling");
+            _alertDialog = GetNode<AcceptDialog>("AlertDialog");
+            _label = GetNode<Label>("Label");
+
+            if (_payment.IsAvailable)
+            {
+                _label.Text += $"\n\n\nTest item SKU: {TestItemSku}";
+
+                // No params.
+                _payment.Connect(nameof(GooglePlayBilling.Connected), this, nameof(OnConnected));
+                // No params.
+                _payment.Connect(nameof(GooglePlayBilling.Disconnected), this, nameof(OnDisconnected));
+                // Response ID (int), Debug message (string).
+                _payment.Connect(nameof(GooglePlayBilling.ConnectError), this, nameof(OnConnectError));
+                // Purchases (Dictionary[]).
+                _payment.Connect(nameof(GooglePlayBilling.PurchasesUpdated), this, nameof(OnPurchasesUpdated));
+                // Response ID (int), Debug message (string).
+                _payment.Connect(nameof(GooglePlayBilling.PurchaseError), this, nameof(OnPurchaseError));
+                // SKUs (Dictionary[]).
+                _payment.Connect(nameof(GooglePlayBilling.SkuDetailsQueryCompleted), this, nameof(OnSkuDetailsQueryCompleted));
+                // Response ID (int), Debug message (string), Queried SKUs (string[]).
+                _payment.Connect(nameof(GooglePlayBilling.SkuDetailsQueryError), this, nameof(OnSkuDetailsQueryError));
+                // Purchase token (string).
+                _payment.Connect(nameof(GooglePlayBilling.PurchaseAcknowledged), this, nameof(OnPurchaseAcknowledged));
+                // Response ID (int), Debug message (string), Purchase token (string).
+                _payment.Connect(nameof(GooglePlayBilling.PurchaseAcknowledgementError), this, nameof(OnPurchaseAcknowledgementError));
+                // Purchase token (string).
+                _payment.Connect(nameof(GooglePlayBilling.PurchaseConsumed), this, nameof(OnPurchaseConsumed));
+                // Response ID (int), Debug message (string), Purchase token (string).
+                _payment.Connect(nameof(GooglePlayBilling.PurchaseConsumptionError), this, nameof(OnPurchaseConsumptionError));
+                _payment.StartConnection();
             }
             }
-            else if (_playerLife.Value > 0.1)
+            else
             {
             {
-                _playerLife.Value -= delta / 4;
+                ShowAlert("Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and installed and enabled the GodotGooglePlayBilling plugin in your Android export settings! This application will not work.");
             }
             }
-
-            _playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, Convert.ToSingle(_playerLife.Value));
         }
         }
 
 
-        private void OnUsePotionButton_pressed()
+        private void ShowAlert(string text)
         {
         {
-            if (_totalPotion > 0)
-            {
-                _totalPotion -= 1;
-                _totalPotionsLabel.Text = $"{_totalPotion} Potions";
-                _playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, Convert.ToSingle(_playerLife.Value));
-
-                _playerLife.Value += 20;
-            }
+            _alertDialog.DialogText = text;
+            _alertDialog.PopupCentered();
         }
         }
 
 
-        private void OnBuyPotionButton_pressed()
+        private void OnConnected()
         {
         {
-            var result = _googlePlayBilling.Purchase("android.test.purchased");
-            if (result != null && result.Status == (int)Error.Ok)
+            GD.Print("PurchaseManager connected");
+
+            // We must acknowledge all puchases.
+            // See https://developer.android.com/google/play/billing/integrate#process for more information
+            var purchasesResult = _payment.QueryPurchases(PurchaseType.InApp);
+            if (purchasesResult.Status == (int)Error.Ok)
             {
             {
-                GD.Print("Purchase Requested");
+                foreach (var purchase in purchasesResult.Purchases)
+                {
+                    if (!purchase.IsAcknowledged)
+                    {
+                        GD.Print($"Purchase {purchase.Sku} has not been acknowledged. Acknowledging...");
+                        _payment.AcknowledgePurchase(purchase.PurchaseToken);
+                    }
+                }
             }
             }
             else
             else
             {
             {
-                GD.Print($"Purchase Failed {result.ResponseCode} {result.DebugMessage}");
+                GD.Print($"Purchase query failed: {purchasesResult.ResponseCode} - {purchasesResult.DebugMessage}");
             }
             }
         }
         }
 
 
-        private void OnButton1_pressed()
+        private async void OnDisconnected()
         {
         {
-            var result = _googlePlayBilling.Purchase("android.test.canceled");
-            if (result != null && result.Status == (int)Error.Ok)
-            {
-                GD.Print("Purchase Requested");
-            }
-            else
-            {
-                GD.Print($"Purchase Failed {result.ResponseCode} {result.DebugMessage}");
-            }
+            ShowAlert("GodotGooglePlayBilling disconnected. Will try to reconnect in 10s...");
+            await ToSignal(GetTree().CreateTimer(10), "timeout");
+            _payment.StartConnection();
         }
         }
-        private void OnButton2_pressed()
+
+        private void OnConnectError()
         {
         {
-            var result = _googlePlayBilling.Purchase("android.test.refunded");
-            if (result != null && result.Status == (int)Error.Ok)
-            {
-                GD.Print("Purchase Requested");
-            }
-            else
-            {
-                GD.Print($"Purchase Failed {result.ResponseCode} {result.DebugMessage}");
-            }
+            ShowAlert("PurchaseManager connect error");
         }
         }
-        private void OnButton3_pressed()
+
+        private void OnPurchasesUpdated(Godot.Collections.Array arrPurchases)
         {
         {
-            var result = _googlePlayBilling.Purchase("android.test.item_unavailable");
-            if (result != null && result.Status == (int)Error.Ok)
+            GD.Print($"Purchases updated: {JSON.Print(arrPurchases)}");
+
+            // See OnConnected
+            var purchases = GooglePlayBillingUtils.ConvertPurchaseDictionaryArray(arrPurchases);
+
+            foreach (var purchase in purchases)
             {
             {
-                GD.Print("Purchase Requested");
+                if (!purchase.IsAcknowledged)
+                {
+                    GD.Print($"Purchase {purchase.Sku} has not been acknowledged. Acknowledging...");
+                    _payment.AcknowledgePurchase(purchase.PurchaseToken);
+                }
             }
             }
-            else
+
+            if (purchases.Length > 0)
             {
             {
-                GD.Print($"Purchase Failed {result.ResponseCode} {result.DebugMessage}");
+                _testItemPurchaseToken = purchases.Last().PurchaseToken;
             }
             }
         }
         }
 
 
-        private void OnOkButton_pressed()
+        private void OnPurchaseError(int code, string message)
         {
         {
-            _panel.Hide();
-            _processLabel.Hide();
-            _thanksLabel.Hide();
+            ShowAlert($"Purchase error {code}: {message}");
         }
         }
 
 
-        private void OnGooglePlayBilling_Connected()
+        private void OnSkuDetailsQueryCompleted(Godot.Collections.Array arrSkuDetails)
         {
         {
-            _googlePlayBilling.QuerySkuDetails(ArrInAppProductsSKUs, PurchaseType.InApp);
+            ShowAlert(JSON.Print(arrSkuDetails));
 
 
-            var purchasesResult = _googlePlayBilling.QueryPurchases(PurchaseType.InApp);
-            if (purchasesResult.Status == (int)Error.Ok)
-            {
-                foreach (var purchase in purchasesResult.Purchases)
-                {
-                    _purchases.Add(purchase.PurchaseToken, purchase.Sku);
-                    // We only expect this SKU
-                    if (purchase.Sku == "android.test.purchased")
-                    {
-                        _googlePlayBilling.AcknowledgePurchase(purchase.PurchaseToken);
-                    }
-                }
-            }
-            else
+            var skuDetails = GooglePlayBillingUtils.ConvertSkuDetailsDictionaryArray(arrSkuDetails);
+            foreach (var skuDetail in skuDetails)
             {
             {
-                GD.Print($"Purchase query failed: {purchasesResult.ResponseCode} - {purchasesResult.DebugMessage}");
+                GD.Print($"Sku {skuDetail.Sku}");
             }
             }
         }
         }
 
 
-        private void OnGooglePlayBilling_SkuDetailsQueryCompleted(Godot.Collections.Array arrSkuDetails)
+        private void OnSkuDetailsQueryError(int code, string message)
         {
         {
-            var skuDetails = GooglePlayBillingUtils.ConvertSkuDetailsDictionaryArray(arrSkuDetails);
-            foreach (var sku in skuDetails)
-            {
-                switch (sku.Sku)
-                {
-                    // our fake potion
-                    case "android.test.purchased":
-                        _buyPotionButton.Text = $"Buy {sku.Price}";
-                        _buyPotionButton.Show();
-                        break;
-                }
-            }
+            ShowAlert($"SKU details query error {code}: {message}");
         }
         }
 
 
-        private void OnGooglePlayBilling_PurchasesUpdated(Godot.Collections.Array arrPurchases)
+        private void OnPurchaseAcknowledged(string purchaseToken)
         {
         {
-            _panel.Show();
-            _processLabel.Show();
-            _thanksLabel.Hide();
+            ShowAlert($"Purchase acknowledged: {purchaseToken}");
+        }
 
 
-            var purchases = GooglePlayBillingUtils.ConvertPurchaseDictionaryArray(arrPurchases);
+        private void OnPurchaseAcknowledgementError(int code, string message)
+        {
+            ShowAlert($"Purchase acknowledgement error {code}: {message}");
+        }
 
 
-            foreach (var purchase in purchases)
-            {
-                _purchases.Add(purchase.PurchaseToken, purchase.Sku);
-                // We only expect this SKU
-                if (purchase.Sku == "android.test.purchased")
-                {
-                    _googlePlayBilling.AcknowledgePurchase(purchase.PurchaseToken);
-                }
-            }
+        private void OnPurchaseConsumed(string purchaseToken)
+        {
+            ShowAlert($"Purchase consumed successfully: {purchaseToken}");
         }
         }
 
 
-        private void OnGooglePlayBilling_PurchaseAcknowledged(string purchaseToken)
+        private void OnPurchaseConsumptionError(int code, string message)
         {
         {
-            _googlePlayBilling.ConsumePurchase(purchaseToken);
+            ShowAlert($"Purchase acknowledgement error {code}: {message}");
         }
         }
 
 
-        private void OnGooglePlayBilling_PurchaseConsumed(string purchaseToken)
+        // GUI
+        private void OnQuerySkuDetailsButton_pressed()
         {
         {
-            if (_purchases[purchaseToken] == "android.test.purchased")
-            {
-                _totalPotion += 5;
-                _totalPotionsLabel.Text = $"{_totalPotion} Potions";
-                _purchases.Remove(purchaseToken);
+            _payment.QuerySkuDetails(new string[] { TestItemSku }, PurchaseType.InApp); // Use "subs" for subscriptions.
+        }
 
 
-                _processLabel.Hide();
-                _thanksLabel.Show();
+        private void OnPurchaseButton_pressed()
+        {
+            var response = _payment.Purchase(TestItemSku);
+            if (response != null && response.Status != (int)Error.Ok)
+            {
+                ShowAlert($"Purchase error {response.ResponseCode} {response.DebugMessage}");
+            }
+        }
 
 
+        private void OnConsumeButton_pressed()
+        {
+            if (string.IsNullOrEmpty(_testItemPurchaseToken))
+            {
+                ShowAlert("You need to set 'test_item_purchase_token' first! (either by hand or in code)");
+                return;
             }
             }
-            GD.Print("OnGooglePlayBilling_PurchaseConsumed ", purchaseToken);
+
+            _payment.ConsumePurchase(_testItemPurchaseToken);
         }
         }
     }
     }
 }
 }

+ 0 - 215
mono/android_iap/Main.tscn

@@ -1,215 +0,0 @@
-[gd_scene load_steps=6 format=2]
-
-[ext_resource path="res://icon.png" type="Texture" id=1]
-[ext_resource path="res://Main.cs" type="Script" id=2]
-[ext_resource path="res://GodotGooglePlayBilling/GooglePlayBilling.cs" type="Script" id=3]
-
-[sub_resource type="StyleBoxFlat" id=1]
-bg_color = Color( 1, 0, 0, 1 )
-
-[sub_resource type="StyleBoxFlat" id=2]
-bg_color = Color( 0, 0, 0, 1 )
-
-[node name="Main" type="Node2D"]
-script = ExtResource( 2 )
-
-[node name="GooglePlayBilling" type="Node" parent="."]
-script = ExtResource( 3 )
-AutoConnect = true
-
-[node name="Sprite" type="Sprite" parent="."]
-position = Vector2( 239.092, 225.037 )
-scale = Vector2( 2, 2 )
-texture = ExtResource( 1 )
-
-[node name="ProgressBar" type="ProgressBar" parent="Sprite"]
-margin_left = -57.9025
-margin_top = -47.7944
-margin_right = 57.0975
-margin_bottom = -33.7944
-custom_styles/fg = SubResource( 1 )
-custom_styles/bg = SubResource( 2 )
-max_value = 1.0
-value = 0.5
-percent_visible = false
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="VBoxContainer" type="VBoxContainer" parent="."]
-margin_left = 65.0538
-margin_top = 30.4056
-margin_right = 185.054
-margin_bottom = 88.4056
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="Label" type="Label" parent="VBoxContainer"]
-margin_right = 120.0
-margin_bottom = 14.0
-text = "1 Potion"
-align = 1
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="UsePotionButton" type="Button" parent="VBoxContainer"]
-margin_top = 18.0
-margin_right = 120.0
-margin_bottom = 58.0
-rect_min_size = Vector2( 120, 40 )
-text = "Use Potion"
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="VBoxContainer2" type="VBoxContainer" parent="."]
-margin_left = 340.826
-margin_top = 29.6986
-margin_right = 460.826
-margin_bottom = 87.6986
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="Label" type="Label" parent="VBoxContainer2"]
-margin_right = 121.0
-margin_bottom = 14.0
-text = "Get 5 Potions now!"
-align = 1
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="BuyPotionButton" type="Button" parent="VBoxContainer2"]
-margin_top = 18.0
-margin_right = 121.0
-margin_bottom = 58.0
-rect_min_size = Vector2( 120, 40 )
-text = "Buy"
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="VBoxContainer3" type="VBoxContainer" parent="."]
-margin_left = 373.353
-margin_top = 143.543
-margin_right = 494.353
-margin_bottom = 289.543
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="Label" type="Label" parent="VBoxContainer3"]
-margin_right = 121.0
-margin_bottom = 14.0
-text = "Other Items"
-align = 1
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="Button1" type="Button" parent="VBoxContainer3"]
-margin_top = 18.0
-margin_right = 121.0
-margin_bottom = 58.0
-rect_min_size = Vector2( 120, 40 )
-text = "Canceled Item"
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="Button2" type="Button" parent="VBoxContainer3"]
-margin_top = 62.0
-margin_right = 121.0
-margin_bottom = 102.0
-rect_min_size = Vector2( 120, 40 )
-text = "Refunded Item"
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="Button3" type="Button" parent="VBoxContainer3"]
-margin_top = 106.0
-margin_right = 121.0
-margin_bottom = 146.0
-rect_min_size = Vector2( 120, 40 )
-text = "Unavailable Item"
-__meta__ = {
-"_edit_lock_": true,
-"_edit_use_anchors_": false
-}
-
-[node name="Panel" type="Panel" parent="."]
-visible = false
-self_modulate = Color( 1, 1, 1, 0.666667 )
-margin_left = 114.414
-margin_top = 38.6777
-margin_right = 379.414
-margin_bottom = 235.678
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="ProcessLabel" type="Label" parent="Panel"]
-anchor_left = 0.5
-anchor_top = 0.5
-anchor_right = 0.5
-anchor_bottom = 0.5
-margin_left = -88.0
-margin_top = -8.5
-margin_right = 88.0
-margin_bottom = 8.5
-text = "Processing..."
-align = 1
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="ThanksLabel" type="Label" parent="Panel"]
-anchor_left = 0.5
-anchor_top = 0.5
-anchor_right = 0.5
-anchor_bottom = 0.5
-margin_left = -88.0
-margin_top = -8.5
-margin_right = 88.0
-margin_bottom = 8.5
-text = "Thanks for your purchase"
-align = 1
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="OkButton" type="Button" parent="Panel"]
-anchor_left = 0.5
-anchor_top = 1.0
-anchor_right = 0.5
-anchor_bottom = 1.0
-margin_left = -48.0
-margin_top = -52.0919
-margin_right = 48.0
-margin_bottom = -19.0919
-text = "Ok"
-__meta__ = {
-"_edit_use_anchors_": false
-}
-[connection signal="Connected" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_Connected"]
-[connection signal="PurchaseAcknowledged" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchaseAcknowledged"]
-[connection signal="PurchaseConsumed" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchaseConsumed"]
-[connection signal="PurchasesUpdated" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchasesUpdated"]
-[connection signal="SkuDetailsQueryCompleted" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_SkuDetailsQueryCompleted"]
-[connection signal="pressed" from="VBoxContainer/UsePotionButton" to="." method="OnUsePotionButton_pressed"]
-[connection signal="pressed" from="VBoxContainer2/BuyPotionButton" to="." method="OnBuyPotionButton_pressed"]
-[connection signal="pressed" from="VBoxContainer3/Button1" to="." method="OnButton1_pressed"]
-[connection signal="pressed" from="VBoxContainer3/Button2" to="." method="OnButton2_pressed"]
-[connection signal="pressed" from="VBoxContainer3/Button3" to="." method="OnButton3_pressed"]
-[connection signal="pressed" from="Panel/OkButton" to="." method="OnOkButton_pressed"]

+ 1 - 1
mono/android_iap/Properties/AssemblyInfo.cs

@@ -3,7 +3,7 @@ using System.Reflection;
 // Information about this assembly is defined by the following attributes.
 // Information about this assembly is defined by the following attributes.
 // Change them to the values specific to your project.
 // Change them to the values specific to your project.
 
 
-[assembly: AssemblyTitle("Android IAP with C#")]
+[assembly: AssemblyTitle("Android in-app purchases with C#")]
 [assembly: AssemblyDescription("")]
 [assembly: AssemblyDescription("")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyCompany("")]

+ 4 - 14
mono/android_iap/README.md

@@ -1,21 +1,11 @@
 # Android IAP with C#
 # Android IAP with C#
 
 
-A simple Android IAP game. This demo shows how to buy a product and consume it for game development in Godot, including
-[Android in-app purchases](https://docs.godotengine.org/en/latest/tutorials/platform/android_in_app_purchases.html).
+These demos depend on features only available in mobile phones or tablets.
+
+The Android IAP demo only runs on Android.
 
 
 Language: [C#](https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html)
 Language: [C#](https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html)
 
 
 Renderer: GLES 2
 Renderer: GLES 2
 
 
-Note: There is a GDScript version available, but it's a bit simpler [here](https://github.com/godotengine/godot-demo-projects/tree/master/mobile/android_iap).
-
-## How does it work?
-
-You have to use potions to heal the fake Player and you have only 5 potions left, you can just buy more to keep healing the Player. The purchase is fake and won't charge anything, it uses the fake ids provided by Google.
-
-## Screenshots
-
-![Screenshot](./screenshots/1.jpg)
-![Screenshot](./screenshots/2.jpg)
-![Screenshot](./screenshots/3.jpg)
-![Screenshot](./screenshots/4.jpg)
+Note: There is a GDScript version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/mobile/AndroidInAppPurchasesWithCSharp).

+ 87 - 0
mono/android_iap/main.tscn

@@ -0,0 +1,87 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://Main.cs" type="Script" id=1]
+[ext_resource path="res://GodotGooglePlayBilling/GooglePlayBilling.cs" type="Script" id=2]
+
+[node name="Main" type="Control"]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -512.711
+margin_top = -300.0
+margin_right = 511.289
+margin_bottom = 300.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="AlertDialog" type="AcceptDialog" parent="."]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = 64.0
+margin_top = 64.0
+margin_right = -64.0
+margin_bottom = -64.0
+grow_horizontal = 2
+grow_vertical = 2
+rect_min_size = Vector2( 400, 0 )
+size_flags_vertical = 4
+popup_exclusive = true
+dialog_autowrap = true
+
+[node name="Label" type="Label" parent="."]
+margin_left = 300.0
+margin_top = 40.0
+margin_right = 996.0
+margin_bottom = 156.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "To test in-app purchase on android device,
+
+1. Make sure you have enabled \"Custom Build\" and the GodotPayment plugin in your Android export settings
+2. Export APK and upload it as alpha or beta stage to Google Play Developer Console and publish it.
+    (It's not published to public, but you and other testers can access it.)
+3. There should be an activate in-app item. Copy its SKU into the TEST_ITEM_SKU constant in iap_demo.gd
+4. Changes you make in the Play Console may take some time before taking effect"
+
+[node name="QuerySkuDetailsButton" type="Button" parent="."]
+margin_left = 40.5697
+margin_top = 39.9347
+margin_right = 221.57
+margin_bottom = 91.9347
+text = "Query SKU details"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="PurchaseButton" type="Button" parent="."]
+margin_left = 40.5697
+margin_top = 101.203
+margin_right = 221.57
+margin_bottom = 153.203
+text = "Purchase"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ConsumeButton" type="Button" parent="."]
+margin_left = 40.5697
+margin_top = 162.142
+margin_right = 221.57
+margin_bottom = 214.142
+text = "Consume"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="GooglePlayBilling" type="Node" parent="."]
+script = ExtResource( 2 )
+[connection signal="pressed" from="QuerySkuDetailsButton" to="." method="OnQuerySkuDetailsButton_pressed"]
+[connection signal="pressed" from="PurchaseButton" to="." method="OnPurchaseButton_pressed"]
+[connection signal="pressed" from="ConsumeButton" to="." method="OnConsumeButton_pressed"]

+ 11 - 8
mono/android_iap/project.godot

@@ -15,21 +15,24 @@ _global_script_class_icons={
 
 
 [application]
 [application]
 
 
-config/name="Android IAP with C#"
-config/description="A simple Android IAP game. This demo shows how to buy a product and consume it."
-run/main_scene="res://Main.tscn"
+config/name="Android in-app purchases with C#"
+run/main_scene="res://main.tscn"
 config/icon="res://icon.png"
 config/icon="res://icon.png"
 
 
+[debug]
+
+gdscript/warnings/return_value_discarded=false
+
 [display]
 [display]
 
 
-window/size/width=512
-window/size/height=300
 window/stretch/mode="2d"
 window/stretch/mode="2d"
-window/stretch/aspect="keep"
+window/stretch/aspect="expand"
+
+[gdnative]
+
+singletons=[  ]
 
 
 [rendering]
 [rendering]
 
 
 quality/driver/driver_name="GLES2"
 quality/driver/driver_name="GLES2"
 vram_compression/import_etc=true
 vram_compression/import_etc=true
-vram_compression/import_etc2=false
-environment/default_environment="res://default_env.tres"

+ 0 - 1
mono/android_iap/screenshots/.gdignore

@@ -1 +0,0 @@
-

BIN
mono/android_iap/screenshots/1.jpg


BIN
mono/android_iap/screenshots/2.jpg


BIN
mono/android_iap/screenshots/3.jpg


BIN
mono/android_iap/screenshots/4.jpg