Skip to content

Commit 9edcf90

Browse files
authored
Merge pull request #423 from qonversion/feature/dev-643-deferred-purchases-listener
Add DeferredPurchasesListener, deprecate EntitlementsUpdateListener
2 parents 13a6038 + 01bb578 commit 9edcf90

File tree

12 files changed

+372
-12
lines changed

12 files changed

+372
-12
lines changed

android/src/main/java/com/qonversion/reactnativesdk/QonversionModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ class QonversionModule(reactContext: ReactApplicationContext) : NativeQonversion
299299
emitOnEntitlementsUpdated(mappedEntitlements)
300300
}
301301

302+
override fun onDeferredPurchaseCompleted(purchaseResult: BridgeData) {
303+
val mappedPurchaseResult = EntitiesConverter.convertMapToWritableMap(purchaseResult)
304+
emitOnDeferredPurchaseCompleted(mappedPurchaseResult)
305+
}
306+
302307
companion object {
303308
const val NAME = "RNQonversion"
304309
}

ios/RNQonversion.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ - (void)qonversionDidReceiveUpdatedEntitlements:(NSDictionary<NSString *,id> * _
317317
}
318318
}
319319

320+
- (void)qonversionDidCompleteDeferredPurchase:(NSDictionary<NSString *,id> * _Nonnull)purchaseResult {
321+
@try {
322+
[self emitOnDeferredPurchaseCompleted:purchaseResult];
323+
} @catch (NSException *exception) {
324+
QNR_LOG_EXCEPTION("qonversionDidCompleteDeferredPurchase", exception);
325+
}
326+
}
327+
320328
- (void)shouldPurchasePromoProductWith:(NSString * _Nonnull)productId {
321329
@try {
322330
[self emitOnPromoPurchaseReceived:productId];

ios/RNQonversionImpl.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import React
1313
public protocol QonversionEventDelegate {
1414
func shouldPurchasePromoProduct(with productId: String)
1515
func qonversionDidReceiveUpdatedEntitlements(_ entitlements: [String: Any])
16+
func qonversionDidCompleteDeferredPurchase(_ purchaseResult: [String: Any])
1617
}
1718

1819
class QonversionEventHandler: QonversionEventListener {
@@ -25,6 +26,10 @@ class QonversionEventHandler: QonversionEventListener {
2526
func qonversionDidReceiveUpdatedEntitlements(_ entitlements: [String: Any]) {
2627
delegate?.qonversionDidReceiveUpdatedEntitlements(entitlements)
2728
}
29+
30+
func qonversionDidCompleteDeferredPurchase(_ purchaseResult: [String: Any]) {
31+
delegate?.qonversionDidCompleteDeferredPurchase(purchaseResult)
32+
}
2833
}
2934

3035
@objc

src/QonversionApi.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Offerings from './dto/Offerings';
55
import IntroEligibility from './dto/IntroEligibility';
66
import User from './dto/User';
77
import type {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
8+
import type {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';
89
import type {PromoPurchasesListener} from './dto/PromoPurchasesListener';
910
import RemoteConfig from "./dto/RemoteConfig";
1011
import RemoteConfigList from "./dto/RemoteConfigList";
@@ -239,9 +240,28 @@ export interface QonversionApi {
239240
* with {@link Qonversion.initialize}.
240241
*
241242
* @param listener listener to be called when entitlements update
243+
* @deprecated Use {@link setDeferredPurchasesListener} instead.
242244
*/
243245
setEntitlementsUpdateListener(listener: EntitlementsUpdateListener): void;
244246

247+
/**
248+
* Provide a listener to be notified about deferred purchase completions.
249+
*
250+
* Deferred purchases happen when transactions require additional steps to complete,
251+
* such as SCA (Strong Customer Authentication), Ask to Buy, or other pending transactions.
252+
* This listener will be called when such purchases are finalized.
253+
*
254+
* Make sure you provide this listener for being up-to-date with deferred purchase completions.
255+
* Also, please, consider that this listener should live for the whole lifetime of the application.
256+
*
257+
* You may set deferred purchases listener both *after* Qonversion SDK initializing
258+
* with {@link QonversionApi.setDeferredPurchasesListener} and *while* Qonversion initializing
259+
* with {@link Qonversion.initialize}.
260+
*
261+
* @param listener listener to be called when a deferred purchase completes
262+
*/
263+
setDeferredPurchasesListener(listener: DeferredPurchasesListener): void;
264+
245265
/**
246266
* iOS only. Does nothing if called on Android.
247267
*

src/QonversionConfig.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums';
22
import type {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
3+
import type {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';
34

45
class QonversionConfig {
56
readonly projectKey: string;
67
readonly launchMode: LaunchMode;
78
readonly environment: Environment;
89
readonly entitlementsCacheLifetime: EntitlementsCacheLifetime;
10+
/** @deprecated Use {@link deferredPurchasesListener} instead. */
911
readonly entitlementsUpdateListener: EntitlementsUpdateListener | undefined;
12+
readonly deferredPurchasesListener: DeferredPurchasesListener | undefined;
1013
readonly proxyUrl: string | undefined;
1114
readonly kidsMode: boolean;
1215

@@ -16,14 +19,16 @@ class QonversionConfig {
1619
environment: Environment = Environment.PRODUCTION,
1720
entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH,
1821
entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined,
19-
proxyUrl: string | undefined,
20-
kidsMode: boolean = false
22+
deferredPurchasesListener: DeferredPurchasesListener | undefined = undefined,
23+
proxyUrl: string | undefined = undefined,
24+
kidsMode: boolean = false,
2125
) {
2226
this.projectKey = projectKey;
2327
this.launchMode = launchMode;
2428
this.environment = environment;
2529
this.entitlementsCacheLifetime = entitlementsCacheLifetime;
2630
this.entitlementsUpdateListener = entitlementsUpdateListener;
31+
this.deferredPurchasesListener = deferredPurchasesListener;
2732
this.proxyUrl = proxyUrl;
2833
this.kidsMode = kidsMode;
2934
}

src/QonversionConfigBuilder.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums';
22
import type {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener';
3+
import type {DeferredPurchasesListener} from './dto/DeferredPurchasesListener';
34
import QonversionConfig from './QonversionConfig';
45

56
class QonversionConfigBuilder {
@@ -14,9 +15,11 @@ class QonversionConfigBuilder {
1415
private environment: Environment = Environment.PRODUCTION;
1516
private entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH;
1617
private entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined;
18+
private deferredPurchasesListener: DeferredPurchasesListener | undefined = undefined;
1719
private proxyUrl: string | undefined = undefined;
1820
private kidsMode: boolean = false;
1921

22+
2023
/**
2124
* Set current application {@link Environment}. Used to distinguish sandbox and production users.
2225
*
@@ -51,12 +54,31 @@ class QonversionConfigBuilder {
5154
*
5255
* @param entitlementsUpdateListener listener to be called when entitlements update.
5356
* @return builder instance for chain calls.
57+
* @deprecated Use {@link setDeferredPurchasesListener} instead.
5458
*/
5559
setEntitlementsUpdateListener(entitlementsUpdateListener: EntitlementsUpdateListener): QonversionConfigBuilder {
5660
this.entitlementsUpdateListener = entitlementsUpdateListener;
5761
return this;
5862
}
5963

64+
/**
65+
* Provide a listener to be notified about deferred purchase completions.
66+
*
67+
* Deferred purchases happen when transactions require additional steps to complete,
68+
* such as SCA (Strong Customer Authentication), Ask to Buy, or other pending transactions.
69+
* This listener will be called when such purchases are finalized.
70+
*
71+
* Make sure you provide this listener for being up-to-date with deferred purchase completions.
72+
* Also, please, consider that this listener should live for the whole lifetime of the application.
73+
*
74+
* @param listener listener to be called when a deferred purchase completes.
75+
* @return builder instance for chain calls.
76+
*/
77+
setDeferredPurchasesListener(listener: DeferredPurchasesListener): QonversionConfigBuilder {
78+
this.deferredPurchasesListener = listener;
79+
return this;
80+
}
81+
6082
/**
6183
* Provide a URL to your proxy server which will redirect all the requests from the app
6284
* to our API. Please, contact us before using this feature.
@@ -93,8 +115,9 @@ class QonversionConfigBuilder {
93115
this.environment,
94116
this.entitlementsCacheLifetime,
95117
this.entitlementsUpdateListener,
118+
this.deferredPurchasesListener,
96119
this.proxyUrl,
97-
this.kidsMode
120+
this.kidsMode,
98121
)
99122
}
100123
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import PurchaseResult from './PurchaseResult';
2+
3+
export interface DeferredPurchasesListener {
4+
5+
/**
6+
* Called when a deferred purchase completes.
7+
* For example, when pending purchases like SCA, Ask to buy, etc., are approved and finalized.
8+
* Provides the full purchase result with entitlements and store transaction details.
9+
* @param purchaseResult the result of the completed deferred purchase.
10+
*/
11+
onDeferredPurchaseCompleted(purchaseResult: PurchaseResult): void;
12+
}

src/dto/EntitlementsUpdateListener.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import Entitlement from './Entitlement';
22

3+
/**
4+
* @deprecated Use {@link DeferredPurchasesListener} instead.
5+
*/
36
export interface EntitlementsUpdateListener {
47

58
/**

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { default as QonversionConfig } from './QonversionConfig';
44
export { default as QonversionConfigBuilder } from './QonversionConfigBuilder';
55

66
export type { EntitlementsUpdateListener } from './dto/EntitlementsUpdateListener';
7+
export type { DeferredPurchasesListener } from './dto/DeferredPurchasesListener';
78
export * from './dto/enums';
89
export { default as IntroEligibility } from './dto/IntroEligibility';
910
export { default as Offering } from './dto/Offering';

src/internal/QonversionInternal.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Product from "../dto/Product";
88
import PurchaseResult from "../dto/PurchaseResult";
99
import {isAndroid, isIos} from "./utils";
1010
import type {EntitlementsUpdateListener} from '../dto/EntitlementsUpdateListener';
11+
import type {DeferredPurchasesListener} from '../dto/DeferredPurchasesListener';
1112
import type {PromoPurchasesListener} from '../dto/PromoPurchasesListener';
1213
import User from '../dto/User';
1314
import PurchaseOptions from '../dto/PurchaseOptions';
@@ -29,8 +30,9 @@ export const sdkSource = "rn";
2930

3031
export default class QonversionInternal implements QonversionApi {
3132

32-
private entitlementsUpdateListener: EntitlementsUpdateListener | null = null;
33+
private deferredPurchasesListener: DeferredPurchasesListener | null = null;
3334
private promoPurchasesDelegate: PromoPurchasesListener | null = null;
35+
private deferredPurchaseEventSubscribed = false;
3436

3537
constructor(qonversionConfig: QonversionConfig) {
3638
RNQonversion.storeSDKInfo(sdkSource, sdkVersion);
@@ -46,6 +48,10 @@ export default class QonversionInternal implements QonversionApi {
4648
if (qonversionConfig.entitlementsUpdateListener) {
4749
this.setEntitlementsUpdateListener(qonversionConfig.entitlementsUpdateListener);
4850
}
51+
52+
if (qonversionConfig.deferredPurchasesListener) {
53+
this.setDeferredPurchasesListener(qonversionConfig.deferredPurchasesListener);
54+
}
4955
}
5056

5157
syncHistoricalData () {
@@ -382,9 +388,19 @@ export default class QonversionInternal implements QonversionApi {
382388
return;
383389
}
384390

385-
private entitlementsUpdatedEventHandler = (payload: Object) => {
386-
const entitlements = Mapper.convertEntitlements(payload as Record<string, QEntitlement>);
387-
this.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements);
391+
private subscribeToDeferredPurchaseEvent() {
392+
if (!this.deferredPurchaseEventSubscribed) {
393+
RNQonversion.onDeferredPurchaseCompleted(this.deferredPurchaseCompletedEventHandler);
394+
this.deferredPurchaseEventSubscribed = true;
395+
}
396+
}
397+
398+
private deferredPurchaseCompletedEventHandler = (payload: Object) => {
399+
const purchaseResult = Mapper.convertPurchaseResult(payload as Record<string, any>);
400+
401+
if (purchaseResult) {
402+
this.deferredPurchasesListener?.onDeferredPurchaseCompleted(purchaseResult);
403+
}
388404
}
389405

390406
private promoPurchaseReceivedEventHandler = (productId: string) => {
@@ -397,11 +413,18 @@ export default class QonversionInternal implements QonversionApi {
397413
}
398414

399415
setEntitlementsUpdateListener(listener: EntitlementsUpdateListener) {
400-
if (this.entitlementsUpdateListener == null) {
401-
RNQonversion.onEntitlementsUpdated(this.entitlementsUpdatedEventHandler);
402-
}
403-
404-
this.entitlementsUpdateListener = listener;
416+
this.setDeferredPurchasesListener({
417+
onDeferredPurchaseCompleted: (purchaseResult: PurchaseResult) => {
418+
if (purchaseResult.entitlements) {
419+
listener.onEntitlementsUpdated(purchaseResult.entitlements);
420+
}
421+
}
422+
});
423+
}
424+
425+
setDeferredPurchasesListener(listener: DeferredPurchasesListener) {
426+
this.subscribeToDeferredPurchaseEvent();
427+
this.deferredPurchasesListener = listener;
405428
}
406429

407430
setPromoPurchasesDelegate(delegate: PromoPurchasesListener) {

0 commit comments

Comments
 (0)