Skip to content

Add QDeferredPurchasesListener with QDeferredTransaction model#785

Open
NickSxti wants to merge 2 commits intodevelopfrom
feature/deferred-purchases-listener
Open

Add QDeferredPurchasesListener with QDeferredTransaction model#785
NickSxti wants to merge 2 commits intodevelopfrom
feature/deferred-purchases-listener

Conversation

@NickSxti
Copy link

Summary

  • New QDeferredPurchasesListener interface with deferredPurchaseCompleted(transaction) callback
  • New QDeferredTransaction data class with productId, transactionId, originalTransactionId, type, value, currency
  • Deprecated QEntitlementsUpdateListener interface, config builder method, and Qonversion setter
  • Both listeners fire in deferred purchase paths (API success + local fallback)
  • Public API on Qonversion interface + QonversionConfig.Builder

Architecture

Deferred purchase completes (pending -> purchased)
  -> API validation success: entitlementsUpdateListener fires (backward compat) + deferredPurchasesListener fires
  -> API validation failure (fallback): same dual notification

Files changed

  • QDeferredPurchasesListener.kt - new interface
  • QDeferredTransaction.kt - new data class
  • QEntitlementsUpdateListener.kt - deprecated
  • Qonversion.kt - new setter + deprecated old
  • QonversionConfig.kt - new builder method + deprecated old
  • InternalConfig.kt - new property
  • QProductCenterManager.kt - new setter + callback invocation at both deferred paths
  • QonversionInternal.kt - delegates to product center manager
  • DeferredPurchasesListenerTest.kt - 6 unit tests

Test plan

  • 6 unit tests written (setter, null, coexistence, isolation, data class validation)
  • Tests could not run locally (Java 8 only, needs 11+) - CI should validate

Note

value and currency are 0.0/null for Android because Google Play Purchase object doesn't expose price. These fields will be populated at the Sandwich/cross-platform layer using product catalog data.

DEV-643

Generated with Claude Code

- New QDeferredPurchasesListener interface with deferredPurchaseCompleted callback
- New QDeferredTransaction data class with productId, transactionId,
  originalTransactionId, type, value, currency
- Deprecated QEntitlementsUpdateListener (interface + config builder + setter)
- Both listeners fire in deferred purchase paths (API success + fallback)
- Public API on Qonversion + QonversionConfig.Builder
- 6 unit tests

DEV-643

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NickSxti NickSxti requested a review from SpertsyanKM March 17, 2026 16:22
@NickSxti
Copy link
Author

LEVER Self-Review

Logic

  • Callback invocation at two deferred purchase paths:
    1. API success (line ~1067): when purchaseCallback == null (deferred), notifies both listeners
    2. Local fallback (line ~572): when callback is null after failed API + local permission grant, notifies both
  • QDeferredTransaction constructed from Google Play Purchase object: productId from purchase, purchaseToken as transactionId, orderId as originalTransactionId
  • value/currency are 0.0/null because Google Play Purchase doesn't expose price data. These will be enriched at the Sandwich/cross-platform layer using product catalog.

Edge Cases

  • If deferredPurchasesListener is null, the call is silently skipped (Kotlin null-safe ?.)
  • Both listeners are independent - setting one doesn't affect the other
  • The deprecated @Suppress("DEPRECATION") annotation prevents build warnings when we call the old listener internally
  • QDeferredTransaction is a data class - provides equals/hashCode/copy for free

Verify

  • New interface follows the pattern of QEntitlementsUpdateListener
  • Config builder follows existing setEntitlementsUpdateListener pattern
  • QonversionInternal delegates to QProductCenterManager
  • Deprecation annotations on interface, builder, and Qonversion setter
  • 6 unit tests written

Risks

  • Tests could not run locally (only Java 8 available, needs 11+). CI should validate.
  • Purchase.productId returns the first product ID for multi-product purchases (edge case for bundles)

Linear: DEV-665

…back)

- Create EntitlementsUpdateListenerAdapter that wraps QEntitlementsUpdateListener
  as QDeferredPurchasesListener, extracting entitlements from purchaseResult
- Change deferredPurchaseCompleted signature to include QPurchaseResult
  (a deferred purchase is a purchase, so the callback provides both
  transaction details and the purchase result with entitlements)
- Remove entitlementsUpdateListener from InternalConfig - only
  deferredPurchasesListener remains
- Delete unused EntitlementsUpdateListenerProvider interface
- Deprecated setters now wrap legacy listener via adapter
- Both deferred call sites in QProductCenterManager use single
  deferredPurchasesListener invocation (no more dual listener logic)
- Update tests for adapter behavior and removed properties

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
callback.onSuccess(user)
}

// Review feedback (Task 6): deprecated setter wraps legacy listener in adapter.
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to mention review feedback in comments

* including product ID, transaction ID, type, value, and currency.
* @param purchaseResult the purchase result containing entitlements granted by this purchase.
*/
fun deferredPurchaseCompleted(transaction: QDeferredTransaction, purchaseResult: QPurchaseResult)
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the necessity of the DeferredTransaction here, if there is the PurchaseResult? I think all the information about the purchase can be taken from there

internal val primaryConfig: PrimaryConfig,
internal val cacheConfig: CacheConfig,
internal val entitlementsUpdateListener: QEntitlementsUpdateListener?
// Review feedback (Task 6): removed entitlementsUpdateListener from config.
Copy link
Contributor

Choose a reason for hiding this comment

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

here either

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.

2 participants