Skip to content

feat: add QPurchaseResult to QONEntitlementsUpdateListener (SUP-292)#644

Closed
NickSxti wants to merge 1 commit into
developfrom
nch/sup-292-add-qpurchaseresult-to-qonentitlementsupdatelistener-ios
Closed

feat: add QPurchaseResult to QONEntitlementsUpdateListener (SUP-292)#644
NickSxti wants to merge 1 commit into
developfrom
nch/sup-292-add-qpurchaseresult-to-qonentitlementsupdatelistener-ios

Conversation

@NickSxti
Copy link
Copy Markdown
Contributor

@NickSxti NickSxti commented Mar 5, 2026

Summary

  • Add @optional method didReceiveUpdatedEntitlements:purchaseResult: to QONEntitlementsUpdateListener protocol
  • Update 2 call sites in QNProductCenterManager to dispatch via respondsToSelector: — new method gets QONPurchaseResult, old method works unchanged
  • Update sample app to demonstrate the new listener method
  • Add unit tests for protocol conformance and selector dispatch

Context

iOS parity with Android SDK PR #769. For deferred/background purchases (consumables, SCA, Ask to Buy), the listener now receives the QONPurchaseResult alongside entitlements, allowing developers to access purchase details even when entitlements are empty.

Non-breaking: Uses @optional protocol method — existing implementations continue to work without code changes.

Linear: SUP-292
Related: Android PR #769

Test plan

  • Unit tests for old listener (backward compat) and new listener (with purchaseResult)
  • Framework builds (Qonversion-iOS scheme)
  • CI: full test suite passes
  • Verify Sample app entitlements listener works with new method

🤖 Generated with Claude Code

Add an @optional method to the entitlements update listener protocol
that includes QPurchaseResult, providing purchase details for deferred
and background purchases (consumables, SCA, Ask to Buy).

Uses respondsToSelector: dispatch for full backward compatibility —
existing implementations continue to work without changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@NickSxti NickSxti requested a review from SpertsyanKM March 5, 2026 12:41
@NickSxti
Copy link
Copy Markdown
Contributor Author

NickSxti commented Mar 5, 2026

Self-Review: SUP-292

Protocol Design (QONEntitlementsUpdateListener.h)

Approach: @optional method — correct choice for ObjC. Unlike Android's method overloading with @Deprecated default, ObjC protocols don't support that pattern. Using @optional means:

  • Old implementations compile and work unchanged (no breaking change)
  • New implementations can opt-in by implementing didReceiveUpdatedEntitlements:purchaseResult:
  • respondsToSelector: dispatch at call sites handles both cases

Forward declaration (@class QONPurchaseResult) instead of #import — correct, avoids circular dependency in the header.

Call Site Changes (QNProductCenterManager.m)

notifyEntitlementsUpdateListener:purchaseResult: helper — Clean extraction. The respondsToSelector: check is idiomatic ObjC for @optional protocol methods. Both call sites (line 1058 for fallback entitlements, line 1063 for success) now construct a QONPurchaseResult and use the helper.

Consistency with _purchasingBlock path: The purchase result construction mirrors the existing logic in the _purchasingBlock branch (lines 1035-1046):

  • Fallback: successFromFallbackWithEntitlements:transaction:
  • Success: successWithEntitlements:transaction:

This is correct — both paths now produce equivalent QONPurchaseResult objects.

Sample App (EntitlementsView.swift)

Good demonstration of the pattern: the new method receives purchaseResult and shows a specific toast for consumable background purchases (empty entitlements + successful result). The old method is kept for backward compat demonstration.

Tests (EntitlementsUpdateListenerTests.m)

6 tests covering:

  • Protocol conformance for both old and new listener
  • Old listener receives entitlements via required method
  • Old listener does NOT respond to new selector (critical for backward compat validation)
  • New listener responds to new selector and receives both parameters
  • Delegate assignment via setEntitlementsUpdateListener:

Note: Tests couldn't run locally due to missing watchOS simulator runtime (the Sample scheme requires it). They will run in CI.

One consideration

The notifyEntitlementsUpdateListener: helper calls the delegate on whatever thread the API callback runs on. This matches the existing behavior (pre-change, the delegate was also called from the callback thread). If we want to guarantee main thread delivery in the future, we'd wrap with dispatch_async(dispatch_get_main_queue(), ...) — but that's a separate concern and not part of this change.

Verdict

LGTM — clean, non-breaking addition with proper @optional pattern and good test coverage.

Copy link
Copy Markdown
Contributor Author

@NickSxti NickSxti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review (LEVER)

Logic: iOS parity with android-sdk#769. Deferred/background purchase flows (consumables, SCA, Ask-to-Buy) previously couldn't surface QONPurchaseResult alongside an entitlements update. Adds @optional method didReceiveUpdatedEntitlements:purchaseResult: to QONEntitlementsUpdateListener; 2 QNProductCenterManager call sites dispatch via respondsToSelector: so the new signature only fires when the consumer implements it.

Edge cases:

  • Consumer implements only the old method: works unchanged (existing tests assert this).
  • Consumer implements both: old one still fires via dispatch, but new one is preferred — confirm the dispatch logic doesn't double-invoke; the respondsToSelector: guard selects one path per call site.
  • Empty entitlements + non-nil purchaseResult: primary reason this was added — covered by unit test.
  • Nil purchaseResult: listener should still be invoked with whatever entitlements exist.

Verification:

  • Unit tests for protocol conformance (backward compat) and new listener invocation with purchaseResult.
  • Framework builds (Qonversion-iOS scheme).
  • Sample app updated to demonstrate new method.
  • Full CI run + sample-app smoke test pending (tickboxes in PR still open).

Effects / blast radius:

  • Public API surface grows by one @optional method — non-breaking.
  • 2 call sites in QNProductCenterManager gain dispatch logic.
  • ABI stable; consumers without the new method continue to link against existing API.

Risks / rollback:

  • Revert removes the new method; sample app revert would restore prior example; no consumer would break.
  • If QONPurchaseResult struct ever changes shape, this listener signature has to stay in lockstep with the Android equivalent.
  • Ensure Android (#769) and iOS (#644) ship together to keep cross-SDK parity.

@NickSxti
Copy link
Copy Markdown
Contributor Author

Superseded by #648 (QONDeferredPurchasesListener, shipped in iOS SDK 6.10.0 on 2026-04-06). The overload approach was replaced with a dedicated protocol; QONEntitlementsUpdateListener method is now marked __attribute__((deprecated)) on main pointing to QONDeferredPurchasesListener. Closing - no longer the intended direction.

@NickSxti NickSxti closed this Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant