From 5e22d60b75875fb67a857bd9ebb3a745a09674ca Mon Sep 17 00:00:00 2001 From: Leonardo Bilck Date: Tue, 21 Jul 2026 01:02:10 -0300 Subject: [PATCH] Fix SubscriptionStatus property dropping entitlements in both directions The C# SubscriptionStatus property lost the entitlement list on both sides of the bridge: - The setter serialized only {"type": "Active"}. Both native bridges (SuperwallUnityBridge.swift / SuperwallUnityBridge.kt) already parse an "entitlements" array from this payload, but C# never sent it, so native received .active([]) - which the native SDKs normalize to inactive and answer with a subscriptionStatusDidChange(to: inactive) echo. - The getter ignored the "entitlements" array native serializes and always returned CreateActive with an empty list. Impact: any app that re-asserts entitlements on subscriptionStatusDidChange (e.g. the documented client-side cross-platform entitlement merge) enters an infinite set -> inactive -> set feedback loop right after a purchase, and the entitlement natively granted by the paywall purchase is wiped. Reproduced on iOS sandbox/StoreKit with SDK 0.2.4. Fix: - Setter serializes the active entitlement ids (the shape both native parsers already read) and lowercases the type to match the native serializers. - Getter reuses BridgeCallbackHandler.DeserializeSubscriptionStatus (private -> internal, same assembly) so entitlements survive the round trip. --- Runtime/Internal/BridgeCallbackHandler.cs | 2 +- Runtime/Superwall.cs | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Runtime/Internal/BridgeCallbackHandler.cs b/Runtime/Internal/BridgeCallbackHandler.cs index 33d1a0c..5bdf53d 100644 --- a/Runtime/Internal/BridgeCallbackHandler.cs +++ b/Runtime/Internal/BridgeCallbackHandler.cs @@ -254,7 +254,7 @@ private static long GetLong(Dictionary data, string key, long de return null; } - private static SubscriptionStatus DeserializeSubscriptionStatus(Dictionary data) + internal static SubscriptionStatus DeserializeSubscriptionStatus(Dictionary data) { if (data == null) return SubscriptionStatus.CreateUnknown(); diff --git a/Runtime/Superwall.cs b/Runtime/Superwall.cs index 0cb96d6..e3743f1 100644 --- a/Runtime/Superwall.cs +++ b/Runtime/Superwall.cs @@ -229,14 +229,9 @@ public SubscriptionStatus SubscriptionStatus if (!string.IsNullOrEmpty(json)) { var dict = Json.Deserialize(json) as Dictionary; - if (dict != null && dict.ContainsKey("type")) + if (dict != null) { - string type = dict["type"] as string; - switch (type) - { - case "active": return SubscriptionStatus.CreateActive(new System.Collections.Generic.List()); - case "inactive": return SubscriptionStatus.CreateInactive(); - } + return BridgeCallbackHandler.DeserializeSubscriptionStatus(dict); } } return SubscriptionStatus.CreateUnknown(); @@ -244,7 +239,17 @@ public SubscriptionStatus SubscriptionStatus set { var data = new Dictionary(); - data["type"] = value.Type.ToString(); + data["type"] = value.Type.ToString().ToLowerInvariant(); + if (value is SubscriptionStatus.ActiveStatus active && active.Entitlements != null) + { + var entitlements = new List(); + foreach (var entitlement in active.Entitlements) + { + if (entitlement == null || !entitlement.IsActive || string.IsNullOrEmpty(entitlement.Id)) continue; + entitlements.Add(new Dictionary { { "id", entitlement.Id } }); + } + data["entitlements"] = entitlements; + } CallNative_SetSubscriptionStatus(Json.Serialize(data)); } }