diff --git a/Assets/PlayroomKit/PlayroomKit.cs b/Assets/PlayroomKit/PlayroomKit.cs index bbdc382..d08f5a2 100644 --- a/Assets/PlayroomKit/PlayroomKit.cs +++ b/Assets/PlayroomKit/PlayroomKit.cs @@ -352,6 +352,12 @@ public void OpenDiscordInviteDialog(Action callback = null) CheckPlayRoomInitialized(); _playroomService.OpenDiscordInviteDialog(callback); } + + public void StartDiscordPurchase(string skuId, Action> responseCallback = null) + { + CheckPlayRoomInitialized(); + _playroomService.StartDiscordPurchase(skuId, responseCallback); + } #endregion } } \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Discord.meta b/Assets/PlayroomKit/modules/Discord.meta new file mode 100644 index 0000000..5abe4a3 --- /dev/null +++ b/Assets/PlayroomKit/modules/Discord.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c2b1971b9c346ec4883ac6ae42b867f0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayroomKit/modules/Discord/Entitlement.cs b/Assets/PlayroomKit/modules/Discord/Entitlement.cs new file mode 100644 index 0000000..8882f36 --- /dev/null +++ b/Assets/PlayroomKit/modules/Discord/Entitlement.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using SimpleJSON; + +public enum EntitlementType +{ + Unhandled = -1, + Purchase = 1, + PremiumSubscription = 2, + DeveloperGift = 3, + TestModePurchase = 4, + FreePurchase = 5, + UserGift = 6, + PremiumPurchase = 7 +} + +[Serializable] +public class Entitlement +{ + public string Id; + public string SkuId; + public string ApplicationId; + public string UserId; + public int GiftCodeFlags; + public EntitlementType Type; + public string? GifterUserId; + public List? Branches; + public DateTime? StartsAt; + public DateTime? EndsAt; + public string? ParentId; + public bool? Consumed; + public bool? Deleted; + public string? GiftCodeBatchId; + + /// + /// Parse a JSONNode (from SimpleJSON) into an Entitlement instance. + /// + public static Entitlement FromJSON(JSONNode n) + { + var e = new Entitlement(); + + e.Id = n["id"] ?? throw new Exception("id is required"); + e.SkuId = n["sku_id"] ?? throw new Exception("sku_id is required"); + e.ApplicationId = n["application_id"] ?? throw new Exception("application_id is required"); + e.UserId = n["user_id"] ?? throw new Exception("user_id is required"); + e.GiftCodeFlags = n["gift_code_flags"].AsInt; + e.Type = (EntitlementType)n["type"].AsInt; + + // optional / nullable + e.GifterUserId = n["gifter_user_id"].IsNull ? null : n["gifter_user_id"]; + + if (n["branches"].IsArray) + { + e.Branches = new List(); + foreach (var item in n["branches"].AsArray) + e.Branches.Add(item.Value); + } + + // parse ISO date strings if present & non-null + string sa = n["starts_at"]; + e.StartsAt = string.IsNullOrEmpty(sa) ? (DateTime?)null : DateTime.Parse(sa, null, System.Globalization.DateTimeStyles.RoundtripKind); + + string ea = n["ends_at"]; + e.EndsAt = string.IsNullOrEmpty(ea) ? (DateTime?)null : DateTime.Parse(ea, null, System.Globalization.DateTimeStyles.RoundtripKind); + + e.ParentId = n["parent_id"].IsNull ? null : n["parent_id"]; + e.Consumed = n["consumed"].IsNull ? (bool?)null : n["consumed"].AsBool; + e.Deleted = n["deleted"].IsNull ? (bool?)null : n["deleted"].AsBool; + e.GiftCodeBatchId = n["gift_code_batch_id"].IsNull ? null : n["gift_code_batch_id"]; + + return e; + } + +} diff --git a/Assets/PlayroomKit/modules/Discord/Entitlement.cs.meta b/Assets/PlayroomKit/modules/Discord/Entitlement.cs.meta new file mode 100644 index 0000000..63d1c13 --- /dev/null +++ b/Assets/PlayroomKit/modules/Discord/Entitlement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0833295798cba8745ac9ad7fd28a02d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayroomKit/modules/Headers.cs b/Assets/PlayroomKit/modules/Headers.cs index 366d58d..78e006c 100644 --- a/Assets/PlayroomKit/modules/Headers.cs +++ b/Assets/PlayroomKit/modules/Headers.cs @@ -169,7 +169,9 @@ private static extern string GetPersistentDataInternal(string key, #region Discord [DllImport("__Internal")] private static extern void OpenDiscordInviteDialogInternal(Action callback = null); - #endregion + [DllImport("__Internal")] + private static extern void StartDiscordPurchaseInternal(string skuId, Action callback); + #endregion } } \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Helpers/CallbackManager.cs b/Assets/PlayroomKit/modules/Helpers/CallbackManager.cs index 31eef17..8dc6440 100644 --- a/Assets/PlayroomKit/modules/Helpers/CallbackManager.cs +++ b/Assets/PlayroomKit/modules/Helpers/CallbackManager.cs @@ -100,6 +100,8 @@ public static void InvokeCallback(string key, TurnData turnData) $"Callback with key {key} is of unsupported type or incorrect number of arguments: {turnData}!"); } } + + public static void InvokeCallback(string key, List turnData) { @@ -111,6 +113,17 @@ public static void InvokeCallback(string key, List turnData) $"Callback with key {key} is of unsupported type or incorrect number of arguments: {turnData}!"); } } + + public static void InvokeCallback(string key, List Entitlement) + { + if (callbacks.TryGetValue(key, out Delegate callback)) + { + if (callback is Action> action) action?.Invoke(Entitlement); + else + Debug.LogError( + $"Callback with key {key} is of unsupported type or incorrect number of arguments: {Entitlement}!"); + } + } public static bool CheckCallback(string key) { diff --git a/Assets/PlayroomKit/modules/Helpers/InternalFunctions/IInternalFunctions.cs b/Assets/PlayroomKit/modules/Helpers/InternalFunctions/IInternalFunctions.cs index c5e423c..b5c8b65 100644 --- a/Assets/PlayroomKit/modules/Helpers/InternalFunctions/IInternalFunctions.cs +++ b/Assets/PlayroomKit/modules/Helpers/InternalFunctions/IInternalFunctions.cs @@ -114,9 +114,9 @@ void RpcCallWrapper(string name, string data, RpcMode mode, #endregion #region Discord + void OpenDiscordInviteDialogInternalWrapper(Action callback = null); #endregion - } } } \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Helpers/InternalFunctions/InterlopWrapper.cs b/Assets/PlayroomKit/modules/Helpers/InternalFunctions/InterlopWrapper.cs index 0dd41dc..03ee8bd 100644 --- a/Assets/PlayroomKit/modules/Helpers/InternalFunctions/InterlopWrapper.cs +++ b/Assets/PlayroomKit/modules/Helpers/InternalFunctions/InterlopWrapper.cs @@ -288,6 +288,8 @@ public void OpenDiscordInviteDialogInternalWrapper(Action callback = null) { OpenDiscordInviteDialogInternal(callback); } + + #endregion } } diff --git a/Assets/PlayroomKit/modules/Interfaces/IPlayroomBase.cs b/Assets/PlayroomKit/modules/Interfaces/IPlayroomBase.cs index 5966499..3ef1f7a 100644 --- a/Assets/PlayroomKit/modules/Interfaces/IPlayroomBase.cs +++ b/Assets/PlayroomKit/modules/Interfaces/IPlayroomBase.cs @@ -72,6 +72,7 @@ public void InsertCoin(InitOptions options = null, Action onLaunchCallBack = nul #region Discord public void OpenDiscordInviteDialog(Action callback = null); + public void StartDiscordPurchase(string skuId, Action> callback = null); #endregion diff --git a/Assets/PlayroomKit/modules/MockMode/BrowserMode/BrowserMockService.cs b/Assets/PlayroomKit/modules/MockMode/BrowserMode/BrowserMockService.cs index d4032ea..f6ac26a 100644 --- a/Assets/PlayroomKit/modules/MockMode/BrowserMode/BrowserMockService.cs +++ b/Assets/PlayroomKit/modules/MockMode/BrowserMode/BrowserMockService.cs @@ -319,6 +319,11 @@ public void OpenDiscordInviteDialog(Action callback = null) callback?.Invoke(); } + public void StartDiscordPurchase(string skuId, Action> callback = null) + { + DebugLogger.LogWarning("[MockMode] Discord purchase is currently not supported in browser mock mode!"); + callback?.Invoke(new List()); + } #endregion } #endif diff --git a/Assets/PlayroomKit/modules/MockMode/LocalPlayroomService.cs b/Assets/PlayroomKit/modules/MockMode/LocalPlayroomService.cs index ffddc01..2e08f35 100644 --- a/Assets/PlayroomKit/modules/MockMode/LocalPlayroomService.cs +++ b/Assets/PlayroomKit/modules/MockMode/LocalPlayroomService.cs @@ -219,6 +219,12 @@ public void OpenDiscordInviteDialog(Action callback = null) DebugLogger.LogWarning("[MockMode] Discord invite dialog is currently not supported in local mode!"); callback?.Invoke(); } + + public void StartDiscordPurchase(string skuId, Action> callback = null) + { + DebugLogger.LogWarning("[MockMode] Discord purchase is currently not supported in local mode!"); + callback?.Invoke(new List()); + } #endregion } } \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/PlayroomBuildService.cs b/Assets/PlayroomKit/modules/PlayroomBuildService.cs index 3b70f72..1e2c691 100644 --- a/Assets/PlayroomKit/modules/PlayroomBuildService.cs +++ b/Assets/PlayroomKit/modules/PlayroomBuildService.cs @@ -438,6 +438,32 @@ public void OpenDiscordInviteDialog(Action callback = null) CallbackManager.RegisterCallback(callback, "discordInviteDialog"); _interop.OpenDiscordInviteDialogInternalWrapper(OpenDiscordInviteDialogCallbackInvoker); } + + [MonoPInvokeCallback(typeof(Action))] + private static void DiscordPurchaseCallback(string skuId, string result) + { + JSONNode root = JSON.Parse(result); + + List entitlements = new List(); + + if (root != null && root.IsArray) + { + foreach (JSONNode item in root.AsArray) + { + Entitlement e = Entitlement.FromJSON(item); + entitlements.Add(e); + } + } + + CallbackManager.InvokeCallback(skuId, entitlements); + } + + public void StartDiscordPurchase(string skuId, Action> callback = null) + { + CheckPlayRoomInitialized(); + CallbackManager.RegisterCallback(callback, skuId); + StartDiscordPurchaseInternal(skuId, DiscordPurchaseCallback); + } #endregion #region Callbacks diff --git a/Assets/PlayroomKit/src/index.js b/Assets/PlayroomKit/src/index.js index 4316efb..ddbad14 100644 --- a/Assets/PlayroomKit/src/index.js +++ b/Assets/PlayroomKit/src/index.js @@ -1089,6 +1089,35 @@ mergeInto(LibraryManager.library, { console.error("Failed to open Discord invite dialog:", error); }); }, + + StartDiscordPurchaseInternal: function (skuId, callback) { + if (!window.Playroom) { + console.error( + "Playroom library is not loaded. Please make sure to call InsertCoin first." + ); + return; + } + + try { + // startPurchase internal… + Playroom.getDiscordClient().commands.startPurchase({sku_id: UTF8ToString(skuId)}).then((response) => { + console.log("[JSLIB]: Purchase started successfully."); + var keyPtr = stringToNewUTF8(skuId); + var returnData = stringToNewUTF8(JSON.stringify(response)); + + console.log("[JSLIB]: Purchase response: ", response); + console.warn("[JSLIB]: Purchase data json: ", JSON.stringify(response)); + + {{{ makeDynCall('vii', 'callback') }}}(keyPtr, dataStrPtr); + }) + .catch((error) => { + console.error("[JSLIB]: Failed to start purchase:", error); + }); + } catch (error) { + console.error("[JSLIB]: Error starting purchase:", error); + } + }, + //#endregion diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 9e75f93..6d64826 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -1,23 +1,27 @@ +using System; +using System.Collections.Generic; using Playroom; using TMPro; using UnityEngine; public class GameManager : MonoBehaviour { - private PlayroomKit _kit; + private PlayroomKit playroomKit; public TextMeshProUGUI text; bool coinInserted = false; + string skuId = "123456789"; + private void Awake() { - _kit = new PlayroomKit(); + playroomKit = new PlayroomKit(); } private void Start() { - _kit.InsertCoin(new InitOptions() + playroomKit.InsertCoin(new InitOptions() { gameId = "cW0r8UJ1aXnZ8v5TPYmv", maxPlayersPerRoom = 2, @@ -27,7 +31,7 @@ private void Start() private void OnLaunchCallBack() { - _kit.OnPlayerJoin(CreatePlayer); + playroomKit.OnPlayerJoin(CreatePlayer); coinInserted = true; } @@ -40,16 +44,29 @@ private void Update() { if (Input.GetKeyDown(KeyCode.T)) { - Debug.Log("Token: " + _kit.GetPlayroomToken()); - text.text = _kit.GetPlayroomToken(); + Debug.Log("Token: " + playroomKit.GetPlayroomToken()); + text.text = playroomKit.GetPlayroomToken(); } - + if (Input.GetKeyDown(KeyCode.I)) { - _kit.OpenDiscordInviteDialog(()=> + playroomKit.OpenDiscordInviteDialog(() => { text.text = "Discord invite dialog opened!"; }); } + + if (Input.GetKeyDown(KeyCode.P)) + { + // After InsertCoin has fully invoked + playroomKit.StartDiscordPurchase(skuId, (response) => + { + foreach (var entitlement in response) + { + Debug.Log($"Entitlement: {entitlement}"); + text.text += $"\nEntitlement: {entitlement}"; + } + }); + } } } \ No newline at end of file