Skip to content

Serialize every Entitlement field on both native bridges - #8

Merged
ianrumac merged 2 commits into
superwall:mainfrom
bilck:fix/entitlement-serialization
Sep 9, 2026
Merged

ianrumac merged 2 commits into
superwall:mainfrom
bilck:fix/entitlement-serialization

Conversation

@bilck

@bilck bilck commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Problem

serializeEntitlement on iOS and Android emitted four keys: id, a hardcoded type, isActive and
productIds. The C# Entitlement model has thirteen fields and BridgeCallbackHandler.DeserializeEntitlement
already reads all of them, so LatestProductId, Store, StartsAt, RenewedAt, ExpiresAt, IsLifetime,
WillRenew, State and OfferType were permanently null. A consumer could not tell a trial from a paid
subscription, nor read an expiry or renewal state, from the entitlement it was handed.

Fix

Both bridges emit the full model with one vocabulary:

  • enum values as the case names the C# enums parse case-insensitively (playStore, inGracePeriod, trial)
  • dates as epoch milliseconds
  • optional fields omitted rather than sent as null

On Android the SDK's GRACE_PERIOD / BILLING_RETRY names map to the C# InGracePeriod /
InBillingRetryPeriod; UNKNOWN is dropped. The Android-only offer types SUBSCRIPTION and REVOKED are
sent under their own names and parse to null on the C# side, which has no member for them.

Verification

iOS device, sandbox trial: OfferType == Trial, ExpiresAt and LatestProductId populated on the
SubscriptionStatus entitlement. Android device: Store == PlayStore, State == Active.

Field names checked against SuperwallKit 4.16.3 (Entitlement.swift) and superwall-android 2.7.23
(Entitlement.kt).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LTHPGB4qyAwDVYmQHW87ex

bilck and others added 2 commits September 3, 2026 22:18
`serializeEntitlement` on iOS and Android emitted four keys: `id`, a hardcoded
`type`, `isActive` and `productIds`. The C# `Entitlement` model has thirteen
fields and `BridgeCallbackHandler.DeserializeEntitlement` already reads all of
them, so `LatestProductId`, `Store`, `StartsAt`, `RenewedAt`, `ExpiresAt`,
`IsLifetime`, `WillRenew`, `State` and `OfferType` were permanently null. A
consumer could not tell a trial from a paid subscription, nor read an expiry or
renewal state, from the entitlement it was handed.

Both bridges now emit the full model with one vocabulary: enum values as the
case names the C# enums parse case-insensitively (`playStore`, `inGracePeriod`,
`trial`), dates as epoch milliseconds, and optional fields omitted rather than
sent as null. On Android the SDK's `GRACE_PERIOD` / `BILLING_RETRY` names are
mapped to the C# `InGracePeriod` / `InBillingRetryPeriod`; `UNKNOWN` is dropped.
The Android-only offer types `SUBSCRIPTION` and `REVOKED` are sent under their own
names and parse to null on the C# side, which has no member for them.

Field names were checked against SuperwallKit 4.16.3 (`Entitlement.swift`) and
superwall-android 2.7.23 (`Entitlement.kt`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ianrumac
ianrumac merged commit ab4ff78 into superwall:main Sep 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants