com.amanotes.gdk 0.2.14 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/Editor/EmbedRemoteConfigAssetInspector.cs +1 -1
- package/Editor/MatchSDKVersion.cs +148 -0
- package/Editor/MatchSDKVersion.cs.meta +11 -0
- package/Editor/Utils/AmaGDKEditor.ExtractVersion.cs +1 -1
- package/Editor/Utils/AmaGDKEditor.Utils.cs +33 -30
- package/Runtime/AmaGDK.AmaPassport.cs +1 -0
- package/Runtime/AmaGDK.IAP.cs +423 -0
- package/Runtime/AmaGDK.IAP.cs.meta +11 -0
- package/Runtime/AmaGDK.cs +2 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using UnityEditor;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
using System.IO;
|
|
5
|
+
using System.Linq;
|
|
6
|
+
using System;
|
|
7
|
+
using System.Text.RegularExpressions;
|
|
8
|
+
using Amanotes.Core.Internal;
|
|
9
|
+
using Amanotes.Editor;
|
|
10
|
+
|
|
11
|
+
namespace Amanotes.Core
|
|
12
|
+
{
|
|
13
|
+
internal class MatchSDKVersion
|
|
14
|
+
{
|
|
15
|
+
private static Dictionary<string, string> sdkToClassMap = new Dictionary<string, string>
|
|
16
|
+
{
|
|
17
|
+
{ "FirebaseAnalytics", "FirebaseAnalyticsAdapter" },
|
|
18
|
+
{ "FirebaseRemoteConfig", "FirebaseRemoteConfigAdapter" },
|
|
19
|
+
{ "appsflyer-unity-plugin", "AppsFlyerAdapter" },
|
|
20
|
+
{ "IronSource_IntegrationManager", "IronSourceAdapter" },
|
|
21
|
+
{ "Adjust", "AdjustAdapter" },
|
|
22
|
+
{ "Purchases", "RevenueCatAdapter" }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
private static FileInfo[] files = null;
|
|
26
|
+
|
|
27
|
+
[InitializeOnLoadMethod]
|
|
28
|
+
private static void Initialize()
|
|
29
|
+
{
|
|
30
|
+
AssetDatabase.importPackageCompleted += UpdateAdapter;
|
|
31
|
+
}
|
|
32
|
+
private static void UncommentDefineAtLine(string filePath, int lineIndex)
|
|
33
|
+
{
|
|
34
|
+
string[] lines = File.ReadAllLines(filePath);
|
|
35
|
+
|
|
36
|
+
for (int i = 0; i < lines.Length; i++)
|
|
37
|
+
{
|
|
38
|
+
if (i == lineIndex)
|
|
39
|
+
{
|
|
40
|
+
lines[i] = lines[i].TrimStart(new[] { '/', ' ' });
|
|
41
|
+
}
|
|
42
|
+
else if (Regex.IsMatch(lines[i], @"^#define\s+"))
|
|
43
|
+
{
|
|
44
|
+
lines[i] = "//" + lines[i];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
File.WriteAllLines(filePath, lines);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static KeyValuePair<int, SemVer> FindNearestLowerVersion(SemVer inputVersion, Dictionary<int, SemVer> defineLines)
|
|
52
|
+
{
|
|
53
|
+
KeyValuePair<int, SemVer> versionLessAndNearest = default;
|
|
54
|
+
SemVer bestMatch = SemVer.ZERO;
|
|
55
|
+
|
|
56
|
+
foreach (var kvp in defineLines)
|
|
57
|
+
{
|
|
58
|
+
if(inputVersion.CompareTo(kvp.Value) < 0)
|
|
59
|
+
continue;
|
|
60
|
+
if(kvp.Value.CompareTo(bestMatch) <= 0)
|
|
61
|
+
continue;
|
|
62
|
+
|
|
63
|
+
bestMatch = kvp.Value;
|
|
64
|
+
versionLessAndNearest = kvp;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return versionLessAndNearest;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static Dictionary<int, SemVer> GetSemVersDefine(string code)
|
|
71
|
+
{
|
|
72
|
+
Dictionary<int, SemVer> defineLines = new Dictionary<int, SemVer>();
|
|
73
|
+
|
|
74
|
+
string[] lines = code.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
|
75
|
+
for (int i = 0; i < lines.Length; i++)
|
|
76
|
+
{
|
|
77
|
+
string line = lines[i].Trim();
|
|
78
|
+
|
|
79
|
+
if (Regex.IsMatch(line, @"^(\/\/\s*)?#define\s+"))
|
|
80
|
+
{
|
|
81
|
+
defineLines.Add(i, new SemVer(GetVersionNumber(line)));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return defineLines;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static string GetVersionNumber(string defineString)
|
|
89
|
+
{
|
|
90
|
+
string pattern = @"VER_(\d+)_(\d+)_(\d+)";
|
|
91
|
+
|
|
92
|
+
Match match = Regex.Match(defineString, pattern);
|
|
93
|
+
|
|
94
|
+
if (match.Success)
|
|
95
|
+
{
|
|
96
|
+
string major = match.Groups[1].Value;
|
|
97
|
+
string minor = match.Groups[2].Value;
|
|
98
|
+
string patch = match.Groups[3].Value;
|
|
99
|
+
string versionNumber = $"{major}.{minor}.{patch}";
|
|
100
|
+
|
|
101
|
+
return versionNumber;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return string.Empty;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static void UpdateAdapter(string packageName)
|
|
108
|
+
{
|
|
109
|
+
if (files == null)
|
|
110
|
+
{
|
|
111
|
+
string dataPath = Path.Combine(Application.dataPath, "AmaGDK/Adapters");
|
|
112
|
+
if (!Directory.Exists(dataPath))
|
|
113
|
+
{
|
|
114
|
+
Debug.Log($"{dataPath} is not found");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
DirectoryInfo directory = new DirectoryInfo(dataPath);
|
|
118
|
+
files = directory.GetFiles("*Adapter.cs", SearchOption.AllDirectories);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (sdkToClassMap.TryGetValue(GetSDKName(packageName), out string adapterClass))
|
|
122
|
+
{
|
|
123
|
+
var version = SDKStatus.Get(adapterClass.Replace("Adapter", "")).sdkVersion;
|
|
124
|
+
FileInfo file = files.FirstOrDefault(x => x.FullName.Contains($"{adapterClass}.cs"));
|
|
125
|
+
|
|
126
|
+
if (file != null)
|
|
127
|
+
{
|
|
128
|
+
Dictionary<int, SemVer> defineLines = GetSemVersDefine(File.ReadAllText(file.FullName));
|
|
129
|
+
|
|
130
|
+
KeyValuePair<int, SemVer> r = FindNearestLowerVersion(version, defineLines);
|
|
131
|
+
|
|
132
|
+
if (r.Value.isValid)
|
|
133
|
+
{
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
UncommentDefineAtLine(file.FullName, r.Key);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
AssetDatabase.Refresh();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private static string GetSDKName(string packageName)
|
|
144
|
+
{
|
|
145
|
+
return sdkToClassMap.Keys.FirstOrDefault(sdk => packageName.Contains(sdk));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -44,40 +44,43 @@ namespace Amanotes.Editor
|
|
|
44
44
|
|
|
45
45
|
private static IEnumerator GetCoroutine(string url, Dictionary<string, string> headers = null, Action<float> onProgress = null, Action<string> onSuccess = null, Action<string> onError = null)
|
|
46
46
|
{
|
|
47
|
-
using var request = UnityWebRequest.Get(url)
|
|
48
|
-
// Add headers to the request
|
|
49
|
-
if (headers != null)
|
|
47
|
+
using (var request = UnityWebRequest.Get(url))
|
|
50
48
|
{
|
|
51
|
-
|
|
49
|
+
// Add headers to the request
|
|
50
|
+
if (headers != null)
|
|
52
51
|
{
|
|
53
|
-
|
|
52
|
+
foreach (var header in headers)
|
|
53
|
+
{
|
|
54
|
+
request.SetRequestHeader(header.Key, header.Value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Send the request
|
|
59
|
+
request.timeout = 60;
|
|
60
|
+
yield return request.SendWebRequest();
|
|
61
|
+
|
|
62
|
+
while (request.isDone == false)
|
|
63
|
+
{
|
|
64
|
+
onProgress?.Invoke(request.downloadProgress);
|
|
65
|
+
yield return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for errors
|
|
69
|
+
#pragma warning disable CS0618
|
|
70
|
+
if (request.isNetworkError || request.isHttpError)
|
|
71
|
+
#pragma warning restore CS0618
|
|
72
|
+
{
|
|
73
|
+
Debug.LogError("Error: " + request.error);
|
|
74
|
+
onError?.Invoke(request.error);
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
{
|
|
78
|
+
// Process the response
|
|
79
|
+
string responseText = request.downloadHandler.text;
|
|
80
|
+
onSuccess?.Invoke(responseText);
|
|
54
81
|
}
|
|
55
82
|
}
|
|
56
|
-
|
|
57
|
-
// Send the request
|
|
58
|
-
request.timeout = 60;
|
|
59
|
-
yield return request.SendWebRequest();
|
|
60
|
-
|
|
61
|
-
while (request.isDone == false)
|
|
62
|
-
{
|
|
63
|
-
onProgress?.Invoke(request.downloadProgress);
|
|
64
|
-
yield return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check for errors
|
|
68
|
-
#pragma warning disable CS0618
|
|
69
|
-
if (request.isNetworkError || request.isHttpError)
|
|
70
|
-
#pragma warning restore CS0618
|
|
71
|
-
{
|
|
72
|
-
Debug.LogError("Error: " + request.error);
|
|
73
|
-
onError?.Invoke(request.error);
|
|
74
|
-
}
|
|
75
|
-
else
|
|
76
|
-
{
|
|
77
|
-
// Process the response
|
|
78
|
-
string responseText = request.downloadHandler.text;
|
|
79
|
-
onSuccess?.Invoke(responseText);
|
|
80
|
-
}
|
|
83
|
+
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -336,6 +336,7 @@ namespace Amanotes.Core
|
|
|
336
336
|
public static string IpAddress => User.amaPassport.device.ipAddress;
|
|
337
337
|
public static string Country => User.amaPassport.device.country;
|
|
338
338
|
public static string DeviceUniqueId => User.amaPassport.device.deviceUniqueId;
|
|
339
|
+
public static string AppsflyerId => User.amaPassport.device.appsflyerId;
|
|
339
340
|
|
|
340
341
|
internal static void Init()
|
|
341
342
|
{
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
using Amanotes.Core.Internal;
|
|
6
|
+
|
|
7
|
+
using static Amanotes.Core.AmaGDK.IAP;
|
|
8
|
+
using static Amanotes.Core.Internal.Logging;
|
|
9
|
+
|
|
10
|
+
namespace Amanotes.Core
|
|
11
|
+
{
|
|
12
|
+
public partial class AmaGDK //In-app purchase
|
|
13
|
+
{
|
|
14
|
+
public partial class IAP //Public
|
|
15
|
+
{
|
|
16
|
+
public static bool IsVIP => !string.IsNullOrEmpty(localData.activeSubscriptionId);
|
|
17
|
+
|
|
18
|
+
public static string ActiveSubscriptionId => localData.activeSubscriptionId;
|
|
19
|
+
|
|
20
|
+
public static Dictionary<string, ProductInfo> AllProducts => dicProducts;
|
|
21
|
+
|
|
22
|
+
public static ISetupBuilder GetProducts()
|
|
23
|
+
{
|
|
24
|
+
var result = new SetupContext();
|
|
25
|
+
ExecuteInNextFrame(result.GetProducts);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static IPurchaseBuilder Purchase(string productId)
|
|
30
|
+
{
|
|
31
|
+
var result = new PurchaseContext();
|
|
32
|
+
ExecuteInNextFrame(() => result.Purchase(productId));
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static IPurchaseBuilder RestorePurchase()
|
|
37
|
+
{
|
|
38
|
+
var result = new PurchaseContext();
|
|
39
|
+
ExecuteInNextFrame(result.RestorePurchase);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static ProductInfo GetProduct(string productId)
|
|
44
|
+
{
|
|
45
|
+
if (!dicProducts.ContainsKey(productId))
|
|
46
|
+
{
|
|
47
|
+
LogWarning($"[IAP] Not found product with id <{productId}>. Make sure you have called Setup() first.");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return dicProducts[productId];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static List<string> GetActiveSubscriptions()
|
|
55
|
+
{
|
|
56
|
+
return IAPAdapter?.GetActiveSubscriptions();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static List<string> GetPurchasedProductIds()
|
|
60
|
+
{
|
|
61
|
+
return localData.purchasedProducts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static bool IsPurchased(string productId)
|
|
65
|
+
{
|
|
66
|
+
var purchasedProductIds = GetPurchasedProductIds();
|
|
67
|
+
return purchasedProductIds != null && purchasedProductIds.Contains(productId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public partial class IAP //Internal
|
|
72
|
+
{
|
|
73
|
+
internal static IIAPAdapter adapter;
|
|
74
|
+
internal static readonly IAPData localData = new IAPData().LoadJsonFromFile();
|
|
75
|
+
internal static IIAPAdapter IAPAdapter
|
|
76
|
+
{
|
|
77
|
+
get
|
|
78
|
+
{
|
|
79
|
+
if (adapter != null) return adapter;
|
|
80
|
+
LogWarning("[IAP] Adapter is not available.");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static IAPAdapterConfig _adapterConfig;
|
|
86
|
+
internal static IAPAdapterConfig AdapterConfig
|
|
87
|
+
{
|
|
88
|
+
get
|
|
89
|
+
{
|
|
90
|
+
if (_adapterConfig != null) return _adapterConfig;
|
|
91
|
+
LogWarning("[IAP] Adapter config is not available.");
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
internal static Dictionary<string, ProductInfo> dicProducts = new Dictionary<string, ProductInfo>();
|
|
97
|
+
|
|
98
|
+
internal static void Init()
|
|
99
|
+
{
|
|
100
|
+
adapter = Adapter.GetSingleAdapter<IIAPAdapter>();
|
|
101
|
+
var adapterContext = adapter as AdapterContext;
|
|
102
|
+
|
|
103
|
+
if (adapterContext == null) return;
|
|
104
|
+
_adapterConfig = adapterContext?.GetAdapterConfig<IAPAdapterConfig>();
|
|
105
|
+
|
|
106
|
+
if (!adapterContext.IsReady) return;
|
|
107
|
+
localData.Sync();
|
|
108
|
+
if (_adapterConfig.autoGetProducts)
|
|
109
|
+
GetProducts();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
internal static void ExecuteInNextFrame(Action method)
|
|
113
|
+
{
|
|
114
|
+
_instance.StartCoroutine(ExecuteRoutine(method));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static IEnumerator ExecuteRoutine(Action method)
|
|
118
|
+
{
|
|
119
|
+
yield return null;
|
|
120
|
+
method?.Invoke();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public partial class IAP //Persistent
|
|
125
|
+
{
|
|
126
|
+
[Serializable]
|
|
127
|
+
internal class IAPData : IJsonFile
|
|
128
|
+
{
|
|
129
|
+
public string activeSubscriptionId;
|
|
130
|
+
public List<string> purchasedProducts;
|
|
131
|
+
|
|
132
|
+
public void Sync()
|
|
133
|
+
{
|
|
134
|
+
activeSubscriptionId = adapter.GetActiveSubscriptionId();
|
|
135
|
+
purchasedProducts = adapter.GetPurchasedProducts();
|
|
136
|
+
this.SaveJsonToFile();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public enum ProductType
|
|
143
|
+
{
|
|
144
|
+
Subscription,
|
|
145
|
+
Consumable,
|
|
146
|
+
NonConsumable
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[Serializable]
|
|
150
|
+
public class ProductInfo
|
|
151
|
+
{
|
|
152
|
+
public string id;
|
|
153
|
+
public ProductType type;
|
|
154
|
+
[HideInInspector] public string title;
|
|
155
|
+
[HideInInspector] public string description;
|
|
156
|
+
[HideInInspector] public float price;
|
|
157
|
+
[HideInInspector] public string currencyCode;
|
|
158
|
+
[HideInInspector] public string[] discountIds;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public class PurchaseSuccess
|
|
162
|
+
{
|
|
163
|
+
public string productId;
|
|
164
|
+
public string transactionId;
|
|
165
|
+
public string receipt;
|
|
166
|
+
public string purchaseToken;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public class PurchaseError
|
|
170
|
+
{
|
|
171
|
+
public string productId;
|
|
172
|
+
public bool isCancelled;
|
|
173
|
+
public string error;
|
|
174
|
+
|
|
175
|
+
public PurchaseError()
|
|
176
|
+
{
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public PurchaseError(string errorMessage)
|
|
180
|
+
{
|
|
181
|
+
error = errorMessage;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public static class IAPExtension
|
|
186
|
+
{
|
|
187
|
+
//GetProducts extensions
|
|
188
|
+
public static ISetupBuilder SetSubscriptions(this ISetupBuilder sc, params string[] subs)
|
|
189
|
+
{
|
|
190
|
+
if (sc == null) return null;
|
|
191
|
+
|
|
192
|
+
foreach (var pId in subs)
|
|
193
|
+
{
|
|
194
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.Subscription });
|
|
195
|
+
}
|
|
196
|
+
return sc;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public static ISetupBuilder SetConsumables(this ISetupBuilder sc, params string[] cons)
|
|
200
|
+
{
|
|
201
|
+
if (sc == null) return null;
|
|
202
|
+
|
|
203
|
+
foreach (var pId in cons)
|
|
204
|
+
{
|
|
205
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.Consumable });
|
|
206
|
+
}
|
|
207
|
+
return sc;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public static ISetupBuilder SetNonComsumables(this ISetupBuilder sc, params string[] nons)
|
|
211
|
+
{
|
|
212
|
+
if (sc == null) return null;
|
|
213
|
+
|
|
214
|
+
foreach (var pId in nons)
|
|
215
|
+
{
|
|
216
|
+
AdapterConfig?.products.Add(new ProductInfo() { id = pId, type = ProductType.NonConsumable });
|
|
217
|
+
}
|
|
218
|
+
return sc;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public static ISetupBuilder OnSuccess(this ISetupBuilder sc, Action<Dictionary<string, ProductInfo>> callback)
|
|
222
|
+
{
|
|
223
|
+
if (sc == null) return null;
|
|
224
|
+
|
|
225
|
+
var setupContext = sc as SetupContext;
|
|
226
|
+
setupContext.onSuccess = callback;
|
|
227
|
+
return sc;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public static ISetupBuilder OnFail(this ISetupBuilder sc, Action<string> callback)
|
|
231
|
+
{
|
|
232
|
+
if (sc == null) return null;
|
|
233
|
+
|
|
234
|
+
var setupContext = sc as SetupContext;
|
|
235
|
+
setupContext.onFail = callback;
|
|
236
|
+
return sc;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//Purchase extensions
|
|
240
|
+
public static IPurchaseBuilder OnSuccess(this IPurchaseBuilder pb, Action<PurchaseSuccess> callback)
|
|
241
|
+
{
|
|
242
|
+
if (pb == null) return null;
|
|
243
|
+
|
|
244
|
+
var purchaseContext = pb as PurchaseContext;
|
|
245
|
+
purchaseContext.onSuccess = callback;
|
|
246
|
+
return pb;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public static IPurchaseBuilder OnFail(this IPurchaseBuilder pb, Action<PurchaseError> callback)
|
|
250
|
+
{
|
|
251
|
+
if (pb == null) return null;
|
|
252
|
+
|
|
253
|
+
var purchaseContext = pb as PurchaseContext;
|
|
254
|
+
purchaseContext.onFail = callback;
|
|
255
|
+
return pb;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public static IPurchaseBuilder SetDiscount(this IPurchaseBuilder pb, string discountId)
|
|
259
|
+
{
|
|
260
|
+
if (pb == null) return null;
|
|
261
|
+
|
|
262
|
+
var purchaseContext = pb as PurchaseContext;
|
|
263
|
+
purchaseContext.discountId = discountId;
|
|
264
|
+
return pb;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
namespace Amanotes.Core.Internal
|
|
270
|
+
{
|
|
271
|
+
[Serializable]
|
|
272
|
+
public class IAPAdapterConfig
|
|
273
|
+
{
|
|
274
|
+
[SerializeField] internal bool allPurchaseSuccessInEditor;
|
|
275
|
+
[SerializeField] internal List<ProductInfo> products;
|
|
276
|
+
[SerializeField] internal bool autoGetProducts;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public interface ISetupBuilder { }
|
|
280
|
+
|
|
281
|
+
public class SetupContext: ISetupBuilder
|
|
282
|
+
{
|
|
283
|
+
public Action<Dictionary<string, ProductInfo>> onSuccess;
|
|
284
|
+
public Action<string> onFail;
|
|
285
|
+
|
|
286
|
+
private readonly Queue<(List<string> pIds, ProductType pType)> getProdQueue = new Queue<(List<string> pIds, ProductType pType)>();
|
|
287
|
+
|
|
288
|
+
public void GetProducts()
|
|
289
|
+
{
|
|
290
|
+
if (AdapterConfig?.products.Count == 0)
|
|
291
|
+
{
|
|
292
|
+
LogWarning("There is no IAP product");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getProdQueue.Clear();
|
|
297
|
+
var subs = new List<string>();
|
|
298
|
+
var cons = new List<string>();
|
|
299
|
+
var nons = new List<string>();
|
|
300
|
+
|
|
301
|
+
foreach (var p in AdapterConfig.products)
|
|
302
|
+
{
|
|
303
|
+
switch (p.type)
|
|
304
|
+
{
|
|
305
|
+
case ProductType.Subscription:
|
|
306
|
+
subs.Add(p.id);
|
|
307
|
+
break;
|
|
308
|
+
case ProductType.Consumable:
|
|
309
|
+
cons.Add(p.id);
|
|
310
|
+
break;
|
|
311
|
+
case ProductType.NonConsumable:
|
|
312
|
+
nons.Add(p.id);
|
|
313
|
+
break;
|
|
314
|
+
default:
|
|
315
|
+
LogWarning($"[IAP] Product <{p.id}> has not specified ProductType.");
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
getProdQueue.Enqueue((subs, ProductType.Subscription));
|
|
321
|
+
getProdQueue.Enqueue((cons, ProductType.Consumable));
|
|
322
|
+
getProdQueue.Enqueue((nons, ProductType.NonConsumable));
|
|
323
|
+
GetNextProductsInQueue();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
void GetNextProductsInQueue()
|
|
327
|
+
{
|
|
328
|
+
if (getProdQueue.Count <= 0)
|
|
329
|
+
{
|
|
330
|
+
onSuccess?.Invoke(dicProducts);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
var item = getProdQueue.Dequeue();
|
|
335
|
+
GetProductsByType(item.pIds, item.pType);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
void GetProductsByType(List<string> lsProduct, ProductType type)
|
|
339
|
+
{
|
|
340
|
+
try
|
|
341
|
+
{
|
|
342
|
+
IAPAdapter?.GetProducts(lsProduct.ToArray(), type, (pList) =>
|
|
343
|
+
{
|
|
344
|
+
foreach (var pI in pList)
|
|
345
|
+
{
|
|
346
|
+
dicProducts.Add(pI.id, pI);
|
|
347
|
+
}
|
|
348
|
+
ExecuteInNextFrame(GetNextProductsInQueue);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
catch (Exception e)
|
|
352
|
+
{
|
|
353
|
+
LogWarning($"[IAP] Error on getting products of type <{type}>. Error detail:\n{e.Message}");
|
|
354
|
+
onFail?.Invoke(e.Message);
|
|
355
|
+
throw;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public interface IPurchaseBuilder { }
|
|
361
|
+
|
|
362
|
+
public class PurchaseContext : IPurchaseBuilder
|
|
363
|
+
{
|
|
364
|
+
public Action<PurchaseSuccess> onSuccess;
|
|
365
|
+
public Action<PurchaseError> onFail;
|
|
366
|
+
public string discountId;
|
|
367
|
+
|
|
368
|
+
public void Purchase(string productId)
|
|
369
|
+
{
|
|
370
|
+
if (!dicProducts.ContainsKey(productId))
|
|
371
|
+
{
|
|
372
|
+
LogWarning($"[IAP] Product {productId} is not found");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#if UNITY_EDITOR
|
|
377
|
+
if (AdapterConfig.allPurchaseSuccessInEditor)
|
|
378
|
+
{
|
|
379
|
+
DoOnSuccess(new PurchaseSuccess
|
|
380
|
+
{
|
|
381
|
+
productId = productId
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
#endif
|
|
386
|
+
|
|
387
|
+
if (!string.IsNullOrWhiteSpace(discountId))
|
|
388
|
+
{
|
|
389
|
+
IAPAdapter?.PurchaseDiscount(productId, dicProducts[productId].type, discountId, DoOnSuccess, DoOnFail);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
IAPAdapter?.Purchase(productId, dicProducts[productId].type, DoOnSuccess, DoOnFail);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public void RestorePurchase()
|
|
397
|
+
{
|
|
398
|
+
IAPAdapter?.RestorePurchase(DoOnSuccess, DoOnFail);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
void DoOnSuccess(PurchaseSuccess success)
|
|
402
|
+
{
|
|
403
|
+
localData.Sync();
|
|
404
|
+
onSuccess?.Invoke(success);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
void DoOnFail(PurchaseError error)
|
|
408
|
+
{
|
|
409
|
+
onFail?.Invoke(error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
public interface IIAPAdapter
|
|
414
|
+
{
|
|
415
|
+
void GetProducts(string[] productIds, ProductType productType, Action<ProductInfo[]> onComplete);
|
|
416
|
+
void Purchase(string productId, ProductType productType, Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
417
|
+
void PurchaseDiscount(string productId, ProductType productType, string discountId, Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
418
|
+
void RestorePurchase(Action<PurchaseSuccess> onSuccess, Action<PurchaseError> onFail);
|
|
419
|
+
List<string> GetActiveSubscriptions();
|
|
420
|
+
List<string> GetPurchasedProducts();
|
|
421
|
+
string GetActiveSubscriptionId();
|
|
422
|
+
}
|
|
423
|
+
}
|
package/Runtime/AmaGDK.cs
CHANGED
|
@@ -109,6 +109,7 @@ namespace Amanotes.Core
|
|
|
109
109
|
AmaPassport.Init();
|
|
110
110
|
Analytics.Init();
|
|
111
111
|
Ads.Init();
|
|
112
|
+
IAP.Init();
|
|
112
113
|
|
|
113
114
|
_status = Status.Ready;
|
|
114
115
|
_onReadyCallback?.Invoke();
|
|
@@ -242,7 +243,7 @@ namespace Amanotes.Core
|
|
|
242
243
|
public void ChangeDevMode()
|
|
243
244
|
{
|
|
244
245
|
string variableName = "GDK_HOME";
|
|
245
|
-
string output = RunShellCommand($"source ~/.zshrc && echo
|
|
246
|
+
string output = RunShellCommand($"source ~/.zshrc && echo file:${variableName}");
|
|
246
247
|
output = output.Trim();
|
|
247
248
|
|
|
248
249
|
if (string.IsNullOrEmpty(output))
|
package/package.json
CHANGED