@purchasely/cordova-plugin-purchasely 6.0.0-rc.3 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,14 +13,20 @@
13
13
 
14
14
  @implementation CDVPurchasely
15
15
 
16
- - (instancetype)init {
17
- self = [super init];
16
+ // pluginInitialize is Cordova's setup hook and is called by every host, after
17
+ // viewController/webView/commandDelegate are assigned. The previous -init override was only
18
+ // reached on cordova-ios, whose -initWithWebViewEngine: calls [self init]; Capacitor's
19
+ // vendored CapacitorCordova calls [super init] instead, which dispatches to NSObject and
20
+ // never reaches a subclass override - so under Capacitor these collections stayed nil, and
21
+ // writing into a nil NSMutableDictionary/NSMutableArray is a silent no-op. That lost every
22
+ // interceptor callbackId (intercepted actions completed .notHandled without reaching JS) and
23
+ // every preloaded presentation ("Presentation not loaded" from preload/display).
24
+ - (void)pluginInitialize {
25
+ [super pluginInitialize];
18
26
 
19
27
  self.presentationsLoaded = [NSMutableArray new];
20
28
  self.actionInterceptorCallbackIds = [NSMutableDictionary new];
21
29
  self.pendingInterceptCompletions = [NSMutableDictionary new];
22
-
23
- return self;
24
30
  }
25
31
 
26
32
  - (void)start:(CDVInvokedUrlCommand*)command {
@@ -110,13 +116,22 @@
110
116
 
111
117
  - (void)userLogin:(CDVInvokedUrlCommand*)command {
112
118
  NSString *userId = [command argumentAtIndex:0];
119
+ // CDV-W-08: userLoginWith:appUserId: is _Nonnull; align with Android's guard instead of
120
+ // forwarding nil into the native call.
121
+ if (![userId isKindOfClass:[NSString class]]) {
122
+ [self successFor:command resultBool:NO];
123
+ return;
124
+ }
113
125
  [Purchasely userLoginWith:userId shouldRefresh:^(BOOL refresh) {
114
126
  [self successFor:command resultBool:refresh];
115
127
  }];
116
128
  }
117
129
 
118
130
  - (void)userLogout:(CDVInvokedUrlCommand*)command {
119
- [Purchasely userLogout:YES];
131
+ // PAR-30: clearUserAttributes defaults to true (matches the JS-side default).
132
+ NSNumber *clearUserAttributes = [command argumentAtIndex:0];
133
+ BOOL clear = [clearUserAttributes isKindOfClass:[NSNumber class]] ? clearUserAttributes.boolValue : YES;
134
+ [Purchasely userLogout:clear];
120
135
  }
121
136
 
122
137
  - (void)setThemeMode:(CDVInvokedUrlCommand *)command {
@@ -201,6 +216,9 @@
201
216
  case CordovaPLYAttributeBatchCustomUserId:
202
217
  attribute = PLYAttributeBatchCustomUserId;
203
218
  break;
219
+ case CordovaPLYAttributeOneSignalUserId:
220
+ attribute = PLYAttributeOneSignalUserId;
221
+ break;
204
222
  default:
205
223
  attributeFound = NO;
206
224
  break;
@@ -219,6 +237,11 @@
219
237
  [self successFor:command resultString:anonymousId];
220
238
  }
221
239
 
240
+ // REC-12 / PAR-04
241
+ - (void)isAnonymous:(CDVInvokedUrlCommand*)command {
242
+ [self successFor:command resultBool:[Purchasely isAnonymous]];
243
+ }
244
+
222
245
  - (void)allowDeeplink:(CDVInvokedUrlCommand*)command {
223
246
  BOOL allow = [[command argumentAtIndex:0] boolValue];
224
247
  [Purchasely allowDeeplink: allow];
@@ -408,6 +431,11 @@
408
431
 
409
432
  - (void)purchasedSubscription:(CDVInvokedUrlCommand*)command {
410
433
  self.purchasedCommand = command;
434
+ // CDV-W-14: a repeat JS call (re-subscribe/hot reload) must not stack observers, or a
435
+ // single purchase/restoration fires reloadContent: once per accumulated registration.
436
+ [[NSNotificationCenter defaultCenter] removeObserver:self
437
+ name: @"ply_purchasedSubscription"
438
+ object:nil];
411
439
  [[NSNotificationCenter defaultCenter] addObserver:self
412
440
  selector:@selector(reloadContent:)
413
441
  name: @"ply_purchasedSubscription"
@@ -455,7 +483,10 @@
455
483
  }
456
484
 
457
485
  - (void)userSubscriptions:(CDVInvokedUrlCommand*)command {
458
- [Purchasely userSubscriptions:false
486
+ // PAR-29: invalidateCache defaults to false.
487
+ NSNumber *invalidateCache = [command argumentAtIndex:0];
488
+ BOOL invalidate = [invalidateCache isKindOfClass:[NSNumber class]] ? invalidateCache.boolValue : NO;
489
+ [Purchasely userSubscriptions:invalidate
459
490
  success:^(NSArray<PLYSubscription *> * _Nullable subscriptions) {
460
491
  NSMutableArray *result = [NSMutableArray new];
461
492
  for (PLYSubscription *subscription in subscriptions) {
@@ -469,7 +500,10 @@
469
500
  }
470
501
 
471
502
  - (void)userSubscriptionsHistory:(CDVInvokedUrlCommand*)command {
472
- [Purchasely userSubscriptionsHistory:false
503
+ // PAR-29: invalidateCache defaults to false.
504
+ NSNumber *invalidateCache = [command argumentAtIndex:0];
505
+ BOOL invalidate = [invalidateCache isKindOfClass:[NSNumber class]] ? invalidateCache.boolValue : NO;
506
+ [Purchasely userSubscriptionsHistory:invalidate
473
507
  success:^(NSArray<PLYSubscription *> * _Nullable subscriptions) {
474
508
  NSMutableArray *result = [NSMutableArray new];
475
509
  for (PLYSubscription *subscription in subscriptions) {
@@ -530,7 +564,10 @@
530
564
  if (@available(iOS 12.2, *)) {
531
565
  [Purchasely signPromotionalOfferWithStoreProductId:storeProductId storeOfferId:storeOfferId success:^(PLYOfferSignature * _Nonnull signature) {
532
566
  NSDictionary* result = [self resultSignatureForSignPromoOffer:signature];
533
- [self successFor:command resultBool:result];
567
+ // CDV-W-02: was resultBool: (an NSDictionary implicitly truncated to a bare
568
+ // BOOL), discarding the whole signature payload. resultDict: is the matching
569
+ // overload (declared below) for this NSDictionary result.
570
+ [self successFor:command resultDict:result];
534
571
  } failure:^(NSError * _Nullable error) {
535
572
  [self failureFor:command resultString:error.localizedDescription];
536
573
  }];
@@ -553,6 +590,53 @@
553
590
  }];
554
591
  }
555
592
 
593
+ // PAR-05: Dynamic Offerings. Payload keys (reference/planVendorId/offerVendorId) match the
594
+ // Cordova JS↔native contract (not the native SDK's own planId/offerId property names).
595
+ - (void)setDynamicOffering:(CDVInvokedUrlCommand*)command {
596
+ NSString *reference = [command argumentAtIndex:0];
597
+ NSString *planVendorId = [command argumentAtIndex:1];
598
+ NSString *offerVendorId = [command argumentAtIndex:2];
599
+ if (![offerVendorId isKindOfClass:[NSString class]]) {
600
+ offerVendorId = nil;
601
+ }
602
+ // iOS 26.4+ Apple commitment billing plan type (0 unspecified / 1 upFront / 2 monthly);
603
+ // JS defaults it to unspecified when omitted (see Purchasely.BillingPlanType).
604
+ PLYBillingPlanType billingPlanType = [[command argumentAtIndex:3 withDefault:@(PLYBillingPlanTypeUnspecified)] integerValue];
605
+
606
+ [Purchasely setDynamicOfferingWithReference:reference
607
+ planVendorId:planVendorId
608
+ offerVendorId:offerVendorId
609
+ billingPlanType:billingPlanType
610
+ completion:^(BOOL success) {
611
+ [self successFor:command resultBool:success];
612
+ }];
613
+ }
614
+
615
+ - (void)getDynamicOfferings:(CDVInvokedUrlCommand*)command {
616
+ [Purchasely getDynamicOfferingsWithCompletion:^(NSArray<PLYOffering *> * _Nonnull offerings) {
617
+ NSMutableArray *result = [NSMutableArray new];
618
+ for (PLYOffering *offering in offerings) {
619
+ NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary new];
620
+ dict[@"reference"] = offering.reference;
621
+ dict[@"planVendorId"] = offering.planId;
622
+ dict[@"offerVendorId"] = offering.offerId ?: [NSNull null];
623
+ [result addObject:dict];
624
+ }
625
+ [self successFor:command resultArray:result];
626
+ }];
627
+ }
628
+
629
+ - (void)removeDynamicOffering:(CDVInvokedUrlCommand*)command {
630
+ NSString *reference = [command argumentAtIndex:0];
631
+ if ([reference isKindOfClass:[NSString class]]) {
632
+ [Purchasely removeDynamicOfferingWithReference:reference];
633
+ }
634
+ }
635
+
636
+ - (void)clearDynamicOfferings:(CDVInvokedUrlCommand*)command {
637
+ [Purchasely clearDynamicOfferings];
638
+ }
639
+
556
640
  // Helpers
557
641
 
558
642
  // v6: builds a PLYTransition from the JS transition object (see normalizeTransition):
@@ -890,6 +974,9 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
890
974
  completion(result);
891
975
  }
892
976
 
977
+ // PAR-19: closeAllScreens is the canonical native action; closePresentation is kept as a
978
+ // separate (deprecated, fire-and-forget) action since existing native `close` behavior is
979
+ // preserved as-is, while closeAllScreens additionally reports success/error to JS.
893
980
  - (void)closePresentation:(CDVInvokedUrlCommand*)command {
894
981
  dispatch_async(dispatch_get_main_queue(), ^{
895
982
  [Purchasely closeAllScreens];
@@ -897,6 +984,14 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
897
984
  });
898
985
  }
899
986
 
987
+ - (void)closeAllScreens:(CDVInvokedUrlCommand*)command {
988
+ dispatch_async(dispatch_get_main_queue(), ^{
989
+ [Purchasely closeAllScreens];
990
+ self.currentPresentation = nil;
991
+ [self successFor:command];
992
+ });
993
+ }
994
+
900
995
  - (void)backPresentation:(CDVInvokedUrlCommand*)command {
901
996
  dispatch_async(dispatch_get_main_queue(), ^{
902
997
  if (self.currentPresentation != nil) {
@@ -1070,6 +1165,34 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1070
1165
  }
1071
1166
  }
1072
1167
 
1168
+ // REC-12 / PAR-03: bulk read, same per-value conversion as the single-key read above.
1169
+ - (void)userAttributes:(CDVInvokedUrlCommand*)command {
1170
+ NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
1171
+ NSDictionary<NSString *, id> *attributes = [Purchasely userAttributes];
1172
+ for (NSString *key in attributes) {
1173
+ id value = [self getUserAttributeValueForCordova:attributes[key]];
1174
+ if (value != nil) {
1175
+ result[key] = value;
1176
+ }
1177
+ }
1178
+ [self successFor:command resultDict:result];
1179
+ }
1180
+
1181
+ // REC-12 / PAR-02
1182
+ - (void)incrementUserAttribute:(CDVInvokedUrlCommand*)command {
1183
+ NSString *key = [command argumentAtIndex:0];
1184
+ NSNumber *valueNumber = [command argumentAtIndex:1];
1185
+ NSInteger value = [valueNumber isKindOfClass:[NSNumber class]] ? valueNumber.integerValue : 1;
1186
+ [Purchasely incrementUserAttributeWithKey:key value:value];
1187
+ }
1188
+
1189
+ - (void)decrementUserAttribute:(CDVInvokedUrlCommand*)command {
1190
+ NSString *key = [command argumentAtIndex:0];
1191
+ NSNumber *valueNumber = [command argumentAtIndex:1];
1192
+ NSInteger value = [valueNumber isKindOfClass:[NSNumber class]] ? valueNumber.integerValue : 1;
1193
+ [Purchasely decrementUserAttributeWithKey:key value:value];
1194
+ }
1195
+
1073
1196
  - (id _Nullable) getUserAttributeValueForCordova:(id _Nullable) value {
1074
1197
  if ([value isKindOfClass:[NSDate class]]) {
1075
1198
  NSDateFormatter * dateFormatter = [NSDateFormatter new];
@@ -1095,6 +1218,31 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1095
1218
  [Purchasely clearBuiltInAttributes];
1096
1219
  }
1097
1220
 
1221
+ // PAR-07
1222
+ - (void)getBuiltInAttributes:(CDVInvokedUrlCommand*)command {
1223
+ NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
1224
+ NSDictionary<NSString *, id> *attributes = [Purchasely getBuiltInAttributes];
1225
+ for (NSString *key in attributes) {
1226
+ id value = [self getUserAttributeValueForCordova:attributes[key]];
1227
+ if (value != nil) {
1228
+ result[key] = value;
1229
+ }
1230
+ }
1231
+ [self successFor:command resultDict:result];
1232
+ }
1233
+
1234
+ - (void)getBuiltInAttribute:(CDVInvokedUrlCommand*)command {
1235
+ NSString *key = [command argumentAtIndex:0];
1236
+ id _Nullable result = [self getUserAttributeValueForCordova:[Purchasely getBuiltInAttributeWith:key]];
1237
+ if (result != nil) {
1238
+ [self successFor:command resultDict:result];
1239
+ } else {
1240
+ // No attribute for this key: resolve success with no value (undefined in JS),
1241
+ // matching Android's nullable Any? return.
1242
+ [self successFor:command];
1243
+ }
1244
+ }
1245
+
1098
1246
  - (void)fetchPresentation:(CDVInvokedUrlCommand*)command {
1099
1247
  NSString *placementId = [command argumentAtIndex:0];
1100
1248
  NSString *presentationId = [command argumentAtIndex:1];
@@ -1124,6 +1272,18 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1124
1272
  } else if (presentation != nil) {
1125
1273
  [self.presentationsLoaded addObject:presentation];
1126
1274
  [self successFor:command resultDict:[self resultDictionaryForFetchPresentation:presentation]];
1275
+ } else {
1276
+ // Neither branch taken means the Cordova command is never completed and the
1277
+ // JS promise returned by preload() stays pending for the life of the page:
1278
+ // no resolve, no reject, no error, nothing logged. Answer instead, so the
1279
+ // caller can handle it.
1280
+ //
1281
+ // Purchasely 6.0.0 does not produce this pair (PresentationRequest.swift
1282
+ // maps a success-with-nil presentation to PLYError.presentationNotLoaded),
1283
+ // so this is defence in depth against a future SDK, not a fix for a
1284
+ // reproduced failure. It costs one branch and removes a whole class of
1285
+ // silent hang.
1286
+ [self failureFor:command resultString: @"Presentation preload completed without a presentation and without an error"];
1127
1287
  }
1128
1288
  }];
1129
1289
  });
@@ -1270,7 +1430,15 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1270
1430
  if (presentation != nil) {
1271
1431
 
1272
1432
  if (presentation.screenId != nil) {
1433
+ // `screenId` is the sole authoritative, public presentation identifier (matches
1434
+ // Android's presentationToMap() key, so shared JS reads either platform the same
1435
+ // way). `id` is kept ONLY as a private/internal re-display lookup key --
1436
+ // findPresentationLoadedFor:/findIndexPresentationLoadedFor: key off it, and on
1437
+ // this platform it always equals screenId (iOS has no separate synthetic fetch
1438
+ // handle, unlike Android's `fetchId`) -- it is NOT a documented public field; the
1439
+ // JS bridge normalizes with `screenId ?? id` tolerance and never surfaces `id`.
1273
1440
  [presentationResult setObject:presentation.screenId forKey:@"id"];
1441
+ [presentationResult setObject:presentation.screenId forKey:@"screenId"];
1274
1442
  }
1275
1443
 
1276
1444
  if (presentation.placementId != nil) {
@@ -1411,7 +1579,8 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1411
1579
  }
1412
1580
 
1413
1581
 
1414
- // WARNING: This enum must be strictly identical to the one in the JS side (Purchasely.js).
1582
+ // WARNING: This enum must be strictly identical (same declaration order) to the one in
1583
+ // the JS side (Purchasely.js) and Android's CordovaPLYAttribute enum class.
1415
1584
  typedef NS_ENUM(NSInteger, CordovaPLYAttribute) {
1416
1585
  CordovaPLYAttributeFirebaseAppInstanceId,
1417
1586
  CordovaPLYAttributeAirshipChannelId,
@@ -1433,7 +1602,8 @@ typedef NS_ENUM(NSInteger, CordovaPLYAttribute) {
1433
1602
  CordovaPLYAttributeAmplitudeDeviceId,
1434
1603
  CordovaPLYAttributeMoengageUniqueId,
1435
1604
  CordovaPLYAttributeOneSignalExternalId,
1436
- CordovaPLYAttributeBatchCustomUserId
1605
+ CordovaPLYAttributeBatchCustomUserId,
1606
+ CordovaPLYAttributeOneSignalUserId
1437
1607
  };
1438
1608
 
1439
1609
  @end
@@ -79,6 +79,25 @@
79
79
  [dict setObject:introPeriod forKey:@"introPeriod"];
80
80
  }
81
81
 
82
+ // Commitment installment details (iOS 26.4+ multi-period commitments, e.g. "monthly
83
+ // subscription with 12-month commitment"). Apple-only: the array is empty for every other
84
+ // plan, so the key is omitted then. Mirrors PLYPlan.commitmentInfo: [PLYCommitmentInfo].
85
+ NSArray<PLYCommitmentInfo *> *commitmentInfo = self.commitmentInfo;
86
+ if (commitmentInfo.count > 0) {
87
+ NSMutableArray<NSDictionary *> *commitmentArray = [NSMutableArray new];
88
+ for (PLYCommitmentInfo *info in commitmentInfo) {
89
+ [commitmentArray addObject:@{
90
+ @"billingPlanType": @(info.billingPlanType),
91
+ @"billingPrice": info.billingPrice,
92
+ @"billingPeriod": info.billingPeriod,
93
+ @"totalPrice": info.totalPrice,
94
+ @"totalPeriod": info.totalPeriod,
95
+ @"totalDuration": @(info.totalDuration)
96
+ }];
97
+ }
98
+ [dict setObject:commitmentArray forKey:@"commitmentInfo"];
99
+ }
100
+
82
101
  return dict;
83
102
  }
84
103
 
@@ -28,6 +28,19 @@
28
28
  [dict setObject:[dateFormat stringFromDate:self.cancelledDate] forKey:@"cancelledDate"];
29
29
  }
30
30
 
31
+ // Commitment progress (iOS 26.4+ monthly commitment, e.g. billing period 3 of 12).
32
+ // Apple-only and nil for every non-committed subscription, so the key is omitted then.
33
+ // Mirrors PLYSubscription.commitmentProgress: PLYCommitmentProgress?
34
+ PLYCommitmentProgress *commitmentProgress = self.commitmentProgress;
35
+ if (commitmentProgress != nil) {
36
+ [dict setObject:@{
37
+ @"billingPeriodNumber": @(commitmentProgress.billingPeriodNumber),
38
+ @"totalBillingPeriods": @(commitmentProgress.totalBillingPeriods),
39
+ @"commitmentExpiresDate": [dateFormat stringFromDate:commitmentProgress.commitmentExpiresDate],
40
+ @"commitmentPrice": commitmentProgress.commitmentPrice
41
+ } forKey:@"commitmentProgress"];
42
+ }
43
+
31
44
  return dict;
32
45
  }
33
46